Skip to content

Commit fc2fdd5

Browse files
authored
fix: backslash at end of string was misinterpreted (googleapis#651)
* fix: backslash at end of string was misinterpreted A SQL string with a string literal that ended with an escaped backslash was misinterpreted by the statement parser as an unclosed literal. E.g. the string `'test\\'` would be seen as an invalid literal. * chore: address review comments
1 parent 7059f99 commit fc2fdd5

File tree

2 files changed

+40
-2
lines changed

2 files changed

+40
-2
lines changed

parser/statement_parser.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,8 +458,8 @@ func (p *StatementParser) skipQuoted(sql []byte, pos int, quote byte) (int, int,
458458
// This was the end quote.
459459
return pos + 1, quoteLength, nil
460460
}
461-
} else if (p.supportsBackslashEscape() || isEscapeString) && len(sql) > pos+1 && c == '\\' && sql[pos+1] == quote {
462-
// This is an escaped quote (e.g. 'foo\'bar').
461+
} else if (p.supportsBackslashEscape() || isEscapeString) && len(sql) > pos+1 && c == '\\' && (sql[pos+1] == quote || sql[pos+1] == '\\') {
462+
// This is an escaped quote (e.g. 'foo\'bar') or an escaped backslash (e.g 'test\\').
463463
// Note that in raw strings, the \ officially does not start an
464464
// escape sequence, but the result is still the same, as in a raw
465465
// string 'both characters are preserved'.

parser/statement_parser_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,17 @@ SELECT * FROM PersonsTable WHERE id=@id`,
740740
databasepb.DatabaseDialect_POSTGRESQL: spanner.ToSpannerError(status.Errorf(codes.InvalidArgument, "SQL statement contains an unclosed literal: %s", `?"?it\"?s"?`)),
741741
},
742742
},
743+
"backslash at end of string": {
744+
input: `?'test\\'?`,
745+
wantSQL: map[databasepb.DatabaseDialect]string{
746+
databasepb.DatabaseDialect_GOOGLE_STANDARD_SQL: `@p1'test\\'@p2`,
747+
databasepb.DatabaseDialect_POSTGRESQL: `$1'test\\'$2`,
748+
},
749+
want: map[databasepb.DatabaseDialect][]string{
750+
databasepb.DatabaseDialect_GOOGLE_STANDARD_SQL: {"p1", "p2"},
751+
databasepb.DatabaseDialect_POSTGRESQL: {"p1", "p2"},
752+
},
753+
},
743754
"triple-quoted string": {
744755
input: `?'''?it\'?s'''?`,
745756
wantSQL: map[databasepb.DatabaseDialect]string{
@@ -1092,6 +1103,16 @@ SELECT * FROM PersonsTable WHERE id=$1`,
10921103
input: `?"?it\"?s"?`,
10931104
wantErr: spanner.ToSpannerError(status.Errorf(codes.InvalidArgument, "SQL statement contains an unclosed literal: %s", `?"?it\"?s"?`)),
10941105
},
1106+
"backslash at end of string": {
1107+
input: `?'test\\'?`,
1108+
wantSQL: `$1'test\\'$2`,
1109+
want: []string{"p1", "p2"},
1110+
},
1111+
"backslash at end of double-quoted string": {
1112+
input: `?"test\\"?`,
1113+
wantSQL: `$1"test\\"$2`,
1114+
want: []string{"p1", "p2"},
1115+
},
10951116
"triple-quoted string": {
10961117
input: `?'''?it\'?s'''?`,
10971118
wantErr: spanner.ToSpannerError(status.Errorf(codes.InvalidArgument, "SQL statement contains an unclosed literal: %s", `?'''?it\'?s'''?`)),
@@ -1263,6 +1284,16 @@ func TestFindParamsWithCommentsPostgreSQL(t *testing.T) {
12631284
wantSQL: `$1\"?it\\"\"?s\"%s$2`,
12641285
want: []string{"p1", "p2"},
12651286
},
1287+
"backslash at end of string": {
1288+
input: `?'test\\'%s?`,
1289+
wantSQL: `$1'test\\'%s$2`,
1290+
want: []string{"p1", "p2"},
1291+
},
1292+
"backslash at end of double-quoted string": {
1293+
input: `?"test\\"%s?`,
1294+
wantSQL: `$1"test\\"%s$2`,
1295+
want: []string{"p1", "p2"},
1296+
},
12661297
"triple-quotes": {
12671298
input: `?%s'''?it\''?s'''?`,
12681299
wantSQL: `$1%s'''?it\''?s'''$2`,
@@ -2028,6 +2059,13 @@ func TestSkip(t *testing.T) {
20282059
databasepb.DatabaseDialect_POSTGRESQL: "'''foo\\'",
20292060
},
20302061
},
2062+
"escaped backslash at end of string literal": {
2063+
input: "'test\\\\' as foo",
2064+
skipped: map[databasepb.DatabaseDialect]string{
2065+
databasepb.DatabaseDialect_GOOGLE_STANDARD_SQL: "'test\\\\'",
2066+
databasepb.DatabaseDialect_POSTGRESQL: "'test\\\\'",
2067+
},
2068+
},
20312069
"string with linefeed": {
20322070
input: "'foo\n' ",
20332071
skipped: map[databasepb.DatabaseDialect]string{

0 commit comments

Comments
 (0)