diff --git a/char/is.go b/char/is.go index b5944e18..71bd9883 100644 --- a/char/is.go +++ b/char/is.go @@ -1,7 +1,7 @@ package char func IsPrint(b byte) bool { - return 0x21 <= b && b <= 0x7D + return 0x20 <= b && b <= 0x7E // ' ' to '~' } func IsDigit(c byte) bool { diff --git a/char/is_test.go b/char/is_test.go index c3738e01..73ddc70f 100644 --- a/char/is_test.go +++ b/char/is_test.go @@ -5,8 +5,12 @@ import ( ) func TestIsPrint(t *testing.T) { - if IsPrint(' ') { - t.Errorf("IsPrint(' ') != false") + if IsPrint('\x19') { + t.Errorf(`IsPrint('\x19') != false`) + } + + if IsPrint('\x80') { + t.Errorf(`IsPrint('\x80') != false`) } if !IsPrint('a') { diff --git a/testdata/input/expr/bytes_literal_seven_bits.sql b/testdata/input/expr/bytes_literal_seven_bits.sql new file mode 100644 index 00000000..e6edb149 --- /dev/null +++ b/testdata/input/expr/bytes_literal_seven_bits.sql @@ -0,0 +1 @@ +b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f" \ No newline at end of file diff --git a/testdata/input/expr/string_literal_seven_bits.sql b/testdata/input/expr/string_literal_seven_bits.sql new file mode 100644 index 00000000..799f906a --- /dev/null +++ b/testdata/input/expr/string_literal_seven_bits.sql @@ -0,0 +1 @@ +"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f" \ No newline at end of file diff --git a/testdata/result/ddl/create_table_for_format_test.sql.txt b/testdata/result/ddl/create_table_for_format_test.sql.txt index fe292d4f..bba51007 100644 --- a/testdata/result/ddl/create_table_for_format_test.sql.txt +++ b/testdata/result/ddl/create_table_for_format_test.sql.txt @@ -109,8 +109,8 @@ create table if not exists foo ( Value: "255", }, }, - NotNull: true, - GeneratedExpr: &ast.GeneratedColumnExpr{ + NotNull: true, + DefaultSemantics: &ast.GeneratedColumnExpr{ As: 172, Stored: 194, Rparen: 192, @@ -152,8 +152,8 @@ create table if not exists foo ( NamePos: 394, Name: "TIMESTAMP", }, - NotNull: true, - DefaultExpr: &ast.ColumnDefaultExpr{ + NotNull: true, + DefaultSemantics: &ast.ColumnDefaultExpr{ Default: 413, Rparen: 441, Expr: &ast.CallExpr{ diff --git a/testdata/result/ddl/create_table_with_identity_columns.sql.txt b/testdata/result/ddl/create_table_with_identity_columns.sql.txt index a88f5ba6..41dbf3eb 100644 --- a/testdata/result/ddl/create_table_with_identity_columns.sql.txt +++ b/testdata/result/ddl/create_table_with_identity_columns.sql.txt @@ -176,4 +176,9 @@ create table foo ( } --- SQL -CREATE TABLE foo (id INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE START COUNTER WITH 1000 SKIP RANGE 1, 12345), startCount INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE START COUNTER WITH 1000), skipRange INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE SKIP RANGE 1000, 2000), simple INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE)) PRIMARY KEY (id) +CREATE TABLE foo ( + id INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE START COUNTER WITH 1000 SKIP RANGE 1, 12345), + startCount INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE START COUNTER WITH 1000), + skipRange INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE SKIP RANGE 1000, 2000), + simple INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE) +) PRIMARY KEY (id) diff --git a/testdata/result/expr/bytes_literal_seven_bits.sql.txt b/testdata/result/expr/bytes_literal_seven_bits.sql.txt new file mode 100644 index 00000000..24dd8a7a --- /dev/null +++ b/testdata/result/expr/bytes_literal_seven_bits.sql.txt @@ -0,0 +1,19 @@ +--- bytes_literal_seven_bits.sql +b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f" +--- AST +&ast.BytesLiteral{ + ValueEnd: 232, + Value: []uint8{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, + }, +} + +--- SQL +b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f" diff --git a/testdata/result/expr/cast_as_typename.sql.txt b/testdata/result/expr/cast_as_typename.sql.txt index 6f45397d..295f0ef1 100644 --- a/testdata/result/expr/cast_as_typename.sql.txt +++ b/testdata/result/expr/cast_as_typename.sql.txt @@ -30,4 +30,4 @@ CAST('order_number: "123"' AS examples.shipping.`Order`) } --- SQL -CAST("order_number: \"123\"" AS examples.shipping.`Order`) +CAST('order_number: "123"' AS examples.shipping.`Order`) diff --git a/testdata/result/expr/json_literal.sql.txt b/testdata/result/expr/json_literal.sql.txt index 6e358b4a..ade4d746 100644 --- a/testdata/result/expr/json_literal.sql.txt +++ b/testdata/result/expr/json_literal.sql.txt @@ -10,4 +10,4 @@ JSON '{"s": "foo", "n": 42}' } --- SQL -JSON "{\"s\": \"foo\", \"n\": 42}" +JSON '{"s": "foo", "n": 42}' diff --git a/testdata/result/expr/string_literal_seven_bits.sql.txt b/testdata/result/expr/string_literal_seven_bits.sql.txt new file mode 100644 index 00000000..04fee57c --- /dev/null +++ b/testdata/result/expr/string_literal_seven_bits.sql.txt @@ -0,0 +1,10 @@ +--- string_literal_seven_bits.sql +"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f" +--- AST +&ast.StringLiteral{ + ValueEnd: 225, + Value: "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f", +} + +--- SQL +"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f" diff --git a/testdata/result/query/select_from_ml_predict_hint.sql.txt b/testdata/result/query/select_from_ml_predict_hint.sql.txt index cc99b9a8..78b338b2 100644 --- a/testdata/result/query/select_from_ml_predict_hint.sql.txt +++ b/testdata/result/query/select_from_ml_predict_hint.sql.txt @@ -172,4 +172,4 @@ FROM ML.PREDICT( } --- SQL -SELECT content FROM ML.PREDICT(MODEL TextBison, (SELECT "Is 13 prime\?" AS prompt), STRUCT(256 AS maxOutputTokens, 0.2 AS temperature, 40 AS topK, 0.95 AS topP)) @{remote_udf_max_rows_per_rpc=1} +SELECT content FROM ML.PREDICT(MODEL TextBison, (SELECT "Is 13 prime?" AS prompt), STRUCT(256 AS maxOutputTokens, 0.2 AS temperature, 40 AS topK, 0.95 AS topP)) @{remote_udf_max_rows_per_rpc=1} diff --git a/testdata/result/query/select_from_ml_predict_textbison.sql.txt b/testdata/result/query/select_from_ml_predict_textbison.sql.txt index 1d267c79..9d2cd598 100644 --- a/testdata/result/query/select_from_ml_predict_textbison.sql.txt +++ b/testdata/result/query/select_from_ml_predict_textbison.sql.txt @@ -367,4 +367,4 @@ FROM ML.PREDICT( } --- SQL -SELECT product_id, product_name, content FROM ML.PREDICT(MODEL TextBison, (SELECT product.id AS product_id, product.name AS product_name, CONCAT("Is this product safe for infants\?", "\n", "Product Name: ", product.name, "\n", "Category Name: ", category.name, "\n", "Product Description:", product.description) AS prompt FROM Products AS product INNER JOIN Categories AS category ON product.category_id = category.id), STRUCT(100 AS maxOutputTokens)) @{remote_udf_max_rows_per_rpc=1} +SELECT product_id, product_name, content FROM ML.PREDICT(MODEL TextBison, (SELECT product.id AS product_id, product.name AS product_name, CONCAT("Is this product safe for infants?", "\n", "Product Name: ", product.name, "\n", "Category Name: ", category.name, "\n", "Product Description:", product.description) AS prompt FROM Products AS product INNER JOIN Categories AS category ON product.category_id = category.id), STRUCT(100 AS maxOutputTokens)) @{remote_udf_max_rows_per_rpc=1} diff --git a/testdata/result/query/select_literals_all.sql.txt b/testdata/result/query/select_literals_all.sql.txt index da902945..2e488079 100644 --- a/testdata/result/query/select_literals_all.sql.txt +++ b/testdata/result/query/select_literals_all.sql.txt @@ -597,4 +597,4 @@ lines''', } --- SQL -SELECT "abc", "it\'s", "it\'s", "Title: \"Boy\"", "abc", "it\'s", "Title:\"Boy\"", "two\nlines", "why\?", "abc+", "abc+", "abc+", "f\\(abc,(.*),def\\)", B"abc", B"abc", B"abc", B"abc+", B"abc+", B"abc", 123, 0xABC, -123, -0xABC, 123.456e-67, .1E4, 58., 4e2, [1, 2, 3], ["x", "y", "xy"], ARRAY[1, 2, 3], ARRAY["x", "y", "xy"], ARRAY[], ARRAY(SELECT STRUCT(1, 2, 3)), ARRAY(SELECT (1, 2, 3)), DATE "2014-09-27", DATE("2014-09-27"), TIMESTAMP "2014-09-27 12:30:00.45-08", TIMESTAMP "2014-09-27 12:30:00.45 America/Los_Angeles", TIMESTAMP "2014-09-27", TIMESTAMP("2014-09-27"), JSON "1", JSON "[1, 2]", JSON "{}", NUMERIC "0", NUMERIC "0", TRUE, FALSE +SELECT "abc", "it's", "it's", 'Title: "Boy"', "abc", "it's", 'Title:"Boy"', "two\nlines", "why?", "abc+", "abc+", "abc+", "f\\(abc,(.*),def\\)", b"abc", b"abc", b"abc", b"abc+", b"abc+", b"abc", 123, 0xABC, -123, -0xABC, 123.456e-67, .1E4, 58., 4e2, [1, 2, 3], ["x", "y", "xy"], ARRAY[1, 2, 3], ARRAY["x", "y", "xy"], ARRAY[], ARRAY(SELECT STRUCT(1, 2, 3)), ARRAY(SELECT (1, 2, 3)), DATE "2014-09-27", DATE("2014-09-27"), TIMESTAMP "2014-09-27 12:30:00.45-08", TIMESTAMP "2014-09-27 12:30:00.45 America/Los_Angeles", TIMESTAMP "2014-09-27", TIMESTAMP("2014-09-27"), JSON "1", JSON "[1, 2]", JSON "{}", NUMERIC "0", NUMERIC "0", TRUE, FALSE diff --git a/testdata/result/query/select_literals_bytes.sql.txt b/testdata/result/query/select_literals_bytes.sql.txt index 498b1214..75cc4af4 100644 --- a/testdata/result/query/select_literals_bytes.sql.txt +++ b/testdata/result/query/select_literals_bytes.sql.txt @@ -110,4 +110,4 @@ SELECT } --- SQL -SELECT B"abc", B"abc", B"abc", B"abc\n", B"abc+", B"abc+", B"abc", B"abc\\n", B"\x00", B"\?\?\?" +SELECT b"abc", b"abc", b"abc", b"abc\x0a", b"abc+", b"abc+", b"abc", b"abc\\n", b"\x00", b"???" diff --git a/testdata/result/query/select_literals_string.sql.txt b/testdata/result/query/select_literals_string.sql.txt index 8686fd58..3ee18903 100644 --- a/testdata/result/query/select_literals_string.sql.txt +++ b/testdata/result/query/select_literals_string.sql.txt @@ -179,4 +179,4 @@ lines''', } --- SQL -SELECT "abc!", "a\nb!", "=!", "=!", "=!", "=!", "=!", "‼!", "🐈!", "it\'s", "it\'s", "Title: \"Boy\"", "abc", "it\'s", "Title:\"Boy\"", "two\nlines", "why\?", "abc+", "abc+", "abc+", "f\\(abc,(.*),def\\)" +SELECT "abc!", "a\nb!", "=!", "=!", "=!", "=!", "=!", "‼!", "🐈!", "it's", "it's", 'Title: "Boy"', "abc", "it's", 'Title:"Boy"', "two\nlines", "why?", "abc+", "abc+", "abc+", "f\\(abc,(.*),def\\)" diff --git a/testdata/result/query/select_subscript_operators.sql.txt b/testdata/result/query/select_subscript_operators.sql.txt index 0ee39c7b..54276770 100644 --- a/testdata/result/query/select_subscript_operators.sql.txt +++ b/testdata/result/query/select_subscript_operators.sql.txt @@ -492,4 +492,4 @@ select } --- SQL -SELECT [1, 2, 3][OFFSET(1)], [1, 2, 3][ORDINAL(1)], [1, 2, 3][SAFE_OFFSET(1)], [1, 2, 3][ORDINAL(1)], [1, 2, 3][1], STRUCT(1, 2, 3)[OFFSET(1)], STRUCT(1, 2, 3)[ORDINAL(1)], STRUCT(1, 2, 3)[SAFE_OFFSET(1)], STRUCT(1, 2, 3)[ORDINAL(1)], STRUCT(1, 2, 3)[1], JSON "[1, 2, 3]"[1], JSON "{\"a\": 1, \"b\": 2, \"c\": 3}"["a"] +SELECT [1, 2, 3][OFFSET(1)], [1, 2, 3][ORDINAL(1)], [1, 2, 3][SAFE_OFFSET(1)], [1, 2, 3][ORDINAL(1)], [1, 2, 3][1], STRUCT(1, 2, 3)[OFFSET(1)], STRUCT(1, 2, 3)[ORDINAL(1)], STRUCT(1, 2, 3)[SAFE_OFFSET(1)], STRUCT(1, 2, 3)[ORDINAL(1)], STRUCT(1, 2, 3)[1], JSON "[1, 2, 3]"[1], JSON '{"a": 1, "b": 2, "c": 3}'["a"] diff --git a/testdata/result/statement/create_table_for_format_test.sql.txt b/testdata/result/statement/create_table_for_format_test.sql.txt index fe292d4f..bba51007 100644 --- a/testdata/result/statement/create_table_for_format_test.sql.txt +++ b/testdata/result/statement/create_table_for_format_test.sql.txt @@ -109,8 +109,8 @@ create table if not exists foo ( Value: "255", }, }, - NotNull: true, - GeneratedExpr: &ast.GeneratedColumnExpr{ + NotNull: true, + DefaultSemantics: &ast.GeneratedColumnExpr{ As: 172, Stored: 194, Rparen: 192, @@ -152,8 +152,8 @@ create table if not exists foo ( NamePos: 394, Name: "TIMESTAMP", }, - NotNull: true, - DefaultExpr: &ast.ColumnDefaultExpr{ + NotNull: true, + DefaultSemantics: &ast.ColumnDefaultExpr{ Default: 413, Rparen: 441, Expr: &ast.CallExpr{ diff --git a/testdata/result/statement/create_table_with_identity_columns.sql.txt b/testdata/result/statement/create_table_with_identity_columns.sql.txt index a88f5ba6..41dbf3eb 100644 --- a/testdata/result/statement/create_table_with_identity_columns.sql.txt +++ b/testdata/result/statement/create_table_with_identity_columns.sql.txt @@ -176,4 +176,9 @@ create table foo ( } --- SQL -CREATE TABLE foo (id INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE START COUNTER WITH 1000 SKIP RANGE 1, 12345), startCount INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE START COUNTER WITH 1000), skipRange INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE SKIP RANGE 1000, 2000), simple INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE)) PRIMARY KEY (id) +CREATE TABLE foo ( + id INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE START COUNTER WITH 1000 SKIP RANGE 1, 12345), + startCount INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE START COUNTER WITH 1000), + skipRange INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE SKIP RANGE 1000, 2000), + simple INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE) +) PRIMARY KEY (id) diff --git a/testdata/result/statement/select_from_ml_predict_hint.sql.txt b/testdata/result/statement/select_from_ml_predict_hint.sql.txt index cc99b9a8..78b338b2 100644 --- a/testdata/result/statement/select_from_ml_predict_hint.sql.txt +++ b/testdata/result/statement/select_from_ml_predict_hint.sql.txt @@ -172,4 +172,4 @@ FROM ML.PREDICT( } --- SQL -SELECT content FROM ML.PREDICT(MODEL TextBison, (SELECT "Is 13 prime\?" AS prompt), STRUCT(256 AS maxOutputTokens, 0.2 AS temperature, 40 AS topK, 0.95 AS topP)) @{remote_udf_max_rows_per_rpc=1} +SELECT content FROM ML.PREDICT(MODEL TextBison, (SELECT "Is 13 prime?" AS prompt), STRUCT(256 AS maxOutputTokens, 0.2 AS temperature, 40 AS topK, 0.95 AS topP)) @{remote_udf_max_rows_per_rpc=1} diff --git a/testdata/result/statement/select_from_ml_predict_textbison.sql.txt b/testdata/result/statement/select_from_ml_predict_textbison.sql.txt index 1d267c79..9d2cd598 100644 --- a/testdata/result/statement/select_from_ml_predict_textbison.sql.txt +++ b/testdata/result/statement/select_from_ml_predict_textbison.sql.txt @@ -367,4 +367,4 @@ FROM ML.PREDICT( } --- SQL -SELECT product_id, product_name, content FROM ML.PREDICT(MODEL TextBison, (SELECT product.id AS product_id, product.name AS product_name, CONCAT("Is this product safe for infants\?", "\n", "Product Name: ", product.name, "\n", "Category Name: ", category.name, "\n", "Product Description:", product.description) AS prompt FROM Products AS product INNER JOIN Categories AS category ON product.category_id = category.id), STRUCT(100 AS maxOutputTokens)) @{remote_udf_max_rows_per_rpc=1} +SELECT product_id, product_name, content FROM ML.PREDICT(MODEL TextBison, (SELECT product.id AS product_id, product.name AS product_name, CONCAT("Is this product safe for infants?", "\n", "Product Name: ", product.name, "\n", "Category Name: ", category.name, "\n", "Product Description:", product.description) AS prompt FROM Products AS product INNER JOIN Categories AS category ON product.category_id = category.id), STRUCT(100 AS maxOutputTokens)) @{remote_udf_max_rows_per_rpc=1} diff --git a/testdata/result/statement/select_literals_all.sql.txt b/testdata/result/statement/select_literals_all.sql.txt index da902945..2e488079 100644 --- a/testdata/result/statement/select_literals_all.sql.txt +++ b/testdata/result/statement/select_literals_all.sql.txt @@ -597,4 +597,4 @@ lines''', } --- SQL -SELECT "abc", "it\'s", "it\'s", "Title: \"Boy\"", "abc", "it\'s", "Title:\"Boy\"", "two\nlines", "why\?", "abc+", "abc+", "abc+", "f\\(abc,(.*),def\\)", B"abc", B"abc", B"abc", B"abc+", B"abc+", B"abc", 123, 0xABC, -123, -0xABC, 123.456e-67, .1E4, 58., 4e2, [1, 2, 3], ["x", "y", "xy"], ARRAY[1, 2, 3], ARRAY["x", "y", "xy"], ARRAY[], ARRAY(SELECT STRUCT(1, 2, 3)), ARRAY(SELECT (1, 2, 3)), DATE "2014-09-27", DATE("2014-09-27"), TIMESTAMP "2014-09-27 12:30:00.45-08", TIMESTAMP "2014-09-27 12:30:00.45 America/Los_Angeles", TIMESTAMP "2014-09-27", TIMESTAMP("2014-09-27"), JSON "1", JSON "[1, 2]", JSON "{}", NUMERIC "0", NUMERIC "0", TRUE, FALSE +SELECT "abc", "it's", "it's", 'Title: "Boy"', "abc", "it's", 'Title:"Boy"', "two\nlines", "why?", "abc+", "abc+", "abc+", "f\\(abc,(.*),def\\)", b"abc", b"abc", b"abc", b"abc+", b"abc+", b"abc", 123, 0xABC, -123, -0xABC, 123.456e-67, .1E4, 58., 4e2, [1, 2, 3], ["x", "y", "xy"], ARRAY[1, 2, 3], ARRAY["x", "y", "xy"], ARRAY[], ARRAY(SELECT STRUCT(1, 2, 3)), ARRAY(SELECT (1, 2, 3)), DATE "2014-09-27", DATE("2014-09-27"), TIMESTAMP "2014-09-27 12:30:00.45-08", TIMESTAMP "2014-09-27 12:30:00.45 America/Los_Angeles", TIMESTAMP "2014-09-27", TIMESTAMP("2014-09-27"), JSON "1", JSON "[1, 2]", JSON "{}", NUMERIC "0", NUMERIC "0", TRUE, FALSE diff --git a/testdata/result/statement/select_literals_bytes.sql.txt b/testdata/result/statement/select_literals_bytes.sql.txt index 498b1214..75cc4af4 100644 --- a/testdata/result/statement/select_literals_bytes.sql.txt +++ b/testdata/result/statement/select_literals_bytes.sql.txt @@ -110,4 +110,4 @@ SELECT } --- SQL -SELECT B"abc", B"abc", B"abc", B"abc\n", B"abc+", B"abc+", B"abc", B"abc\\n", B"\x00", B"\?\?\?" +SELECT b"abc", b"abc", b"abc", b"abc\x0a", b"abc+", b"abc+", b"abc", b"abc\\n", b"\x00", b"???" diff --git a/testdata/result/statement/select_literals_string.sql.txt b/testdata/result/statement/select_literals_string.sql.txt index 8686fd58..3ee18903 100644 --- a/testdata/result/statement/select_literals_string.sql.txt +++ b/testdata/result/statement/select_literals_string.sql.txt @@ -179,4 +179,4 @@ lines''', } --- SQL -SELECT "abc!", "a\nb!", "=!", "=!", "=!", "=!", "=!", "‼!", "🐈!", "it\'s", "it\'s", "Title: \"Boy\"", "abc", "it\'s", "Title:\"Boy\"", "two\nlines", "why\?", "abc+", "abc+", "abc+", "f\\(abc,(.*),def\\)" +SELECT "abc!", "a\nb!", "=!", "=!", "=!", "=!", "=!", "‼!", "🐈!", "it's", "it's", 'Title: "Boy"', "abc", "it's", 'Title:"Boy"', "two\nlines", "why?", "abc+", "abc+", "abc+", "f\\(abc,(.*),def\\)" diff --git a/testdata/result/statement/select_subscript_operators.sql.txt b/testdata/result/statement/select_subscript_operators.sql.txt index 0ee39c7b..54276770 100644 --- a/testdata/result/statement/select_subscript_operators.sql.txt +++ b/testdata/result/statement/select_subscript_operators.sql.txt @@ -492,4 +492,4 @@ select } --- SQL -SELECT [1, 2, 3][OFFSET(1)], [1, 2, 3][ORDINAL(1)], [1, 2, 3][SAFE_OFFSET(1)], [1, 2, 3][ORDINAL(1)], [1, 2, 3][1], STRUCT(1, 2, 3)[OFFSET(1)], STRUCT(1, 2, 3)[ORDINAL(1)], STRUCT(1, 2, 3)[SAFE_OFFSET(1)], STRUCT(1, 2, 3)[ORDINAL(1)], STRUCT(1, 2, 3)[1], JSON "[1, 2, 3]"[1], JSON "{\"a\": 1, \"b\": 2, \"c\": 3}"["a"] +SELECT [1, 2, 3][OFFSET(1)], [1, 2, 3][ORDINAL(1)], [1, 2, 3][SAFE_OFFSET(1)], [1, 2, 3][ORDINAL(1)], [1, 2, 3][1], STRUCT(1, 2, 3)[OFFSET(1)], STRUCT(1, 2, 3)[ORDINAL(1)], STRUCT(1, 2, 3)[SAFE_OFFSET(1)], STRUCT(1, 2, 3)[ORDINAL(1)], STRUCT(1, 2, 3)[1], JSON "[1, 2, 3]"[1], JSON '{"a": 1, "b": 2, "c": 3}'["a"] diff --git a/token/quote.go b/token/quote.go index 6ded3213..78187b2f 100644 --- a/token/quote.go +++ b/token/quote.go @@ -10,34 +10,56 @@ import ( // QuoteSQLString returns quoted string with SQL string escaping. func QuoteSQLString(s string) string { + quote := suitableQuote([]byte(s)) + var buf bytes.Buffer - buf.WriteByte('"') - quoteSQLStringContent(s, &buf) - buf.WriteByte('"') + buf.WriteRune(quote) + quoteSQLStringContent(s, quote, &buf) + buf.WriteRune(quote) return buf.String() } -// QuoteSQLString returns quoted string with SQL bytes escaping. +func suitableQuote(b []byte) rune { + var hasSingle, hasDouble bool + for _, b := range b { + switch b { + case '\'': + hasSingle = true + case '"': + hasDouble = true + } + } + if !hasSingle && hasDouble { + return '\'' + } + return '"' +} + +// QuoteSQLBytes returns quoted string with SQL bytes escaping. func QuoteSQLBytes(bs []byte) string { + quote := suitableQuote(bs) + var buf bytes.Buffer - buf.WriteString("B\"") + buf.WriteString("b") + buf.WriteRune(quote) for _, b := range bs { - q := quoteSingleEscape(rune(b)) + q := quoteSingleEscape(rune(b), quote, /* isString */ false) if q != "" { buf.WriteString(q) continue } + if char.IsPrint(b) { buf.WriteByte(b) continue } - fmt.Fprintf(&buf, "\\x%02X", uint64(b)) + fmt.Fprintf(&buf, `\x%02x`, uint64(b)) } - buf.WriteRune('"') + buf.WriteRune(quote) return buf.String() } -// QuoteSQLString returns quoted string with SQL bytes escaping if needed, +// QuoteSQLIdent returns quoted identifier if needed, // otherwise it returns the input string. func QuoteSQLIdent(s string) string { if !needQuoteSQLIdent(s) { @@ -46,14 +68,14 @@ func QuoteSQLIdent(s string) string { var buf bytes.Buffer buf.WriteByte('`') - quoteSQLStringContent(s, &buf) + quoteSQLStringContent(s, '`', &buf) buf.WriteByte('`') return buf.String() } -func quoteSQLStringContent(s string, buf *bytes.Buffer) { +func quoteSQLStringContent(s string, quote rune, buf *bytes.Buffer) { for _, r := range s { - q := quoteSingleEscape(r) + q := quoteSingleEscape(r, quote, /* isString */ true) if q != "" { buf.WriteString(q) continue @@ -62,40 +84,29 @@ func quoteSQLStringContent(s string, buf *bytes.Buffer) { buf.WriteRune(r) continue } - if r > 0xFFFF { - fmt.Fprintf(buf, "\\U%08X", uint64(r)) - } else { - fmt.Fprintf(buf, "\\u%04X", uint64(r)) + switch { + case r < 0x80: + fmt.Fprintf(buf, `\x%02x`, uint64(r)) + case r > 0xFFFF: + fmt.Fprintf(buf, `\U%08x`, uint64(r)) + default: + fmt.Fprintf(buf, `\u%04x`, uint64(r)) } } } -func quoteSingleEscape(r rune) string { - switch r { - case '\a': - return "\\a" - case '\b': - return "\\b" - case '\f': - return "\\f" - case '\n': - return "\\n" - case '\r': - return "\\r" - case '\t': - return "\\t" - case '\v': - return "\\v" - case '"': - return "\\\"" - case '\'': - return "\\'" - case '`': - return "\\`" - case '?': - return "\\?" - case '\\': - return "\\\\" +func quoteSingleEscape(r, quote rune, isString bool) string { + switch { + case r == quote: + return `\` + string(r) + case isString && r == '\n': + return `\n` + case isString && r == '\r': + return `\r` + case isString && r == '\t': + return `\t` + case r == '\\': + return `\\` } return "" } diff --git a/token/quote_test.go b/token/quote_test.go index 051f4973..adcad8c5 100644 --- a/token/quote_test.go +++ b/token/quote_test.go @@ -8,13 +8,13 @@ var quoteTestCases = []struct { input string str, bytes, id string }{ - {"foo", `"foo"`, `B"foo"`, "foo"}, - {"if", `"if"`, `B"if"`, "`if`"}, - {"\u0000", `"\u0000"`, `B"\x00"`, "`\\u0000`"}, - {"\U0010FFFF", `"\U0010FFFF"`, `B"\xF4\x8F\xBF\xBF"`, "`\\U0010FFFF`"}, - {"a\u2060b", `"a\u2060b"`, `B"a\xE2\x81\xA0b"`, "`a\\u2060b`"}, - {"\a\b\f\n\r\t\v\"'?\\", `"\a\b\f\n\r\t\v\"\'\?\\"`, `B"\a\b\f\n\r\t\v\"\'\?\\"`, "`\\a\\b\\f\\n\\r\\t\\v\\\"\\'\\?\\\\`"}, - {"`", "\"\\`\"", "B\"\\`\"", "`\\``"}, + {"foo", `"foo"`, `b"foo"`, "foo"}, + {"if", `"if"`, `b"if"`, "`if`"}, + {"\u0000", `"\x00"`, `b"\x00"`, "`\\x00`"}, + {"\U0010FFFF", `"\U0010ffff"`, `b"\xf4\x8f\xbf\xbf"`, "`\\U0010ffff`"}, + {"a\u2060b", `"a\u2060b"`, `b"a\xe2\x81\xa0b"`, "`a\\u2060b`"}, + {"\a\b\f\n\r\t\v\"'?\\", `"\x07\x08\x0c\n\r\t\x0b\"'?\\"`, `b"\x07\x08\x0c\x0a\x0d\x09\x0b\"'?\\"`, "`\\x07\\x08\\x0c\\n\\r\\t\\x0b\"'?\\\\`"}, + {"`", "\"`\"", "b\"`\"", "`\\``"}, } func TestQuote(t *testing.T) {