Skip to content

Commit c00c633

Browse files
authored
fix: fix unclosed literal error for consecutive backslashes (#4387)
1 parent 2dafe77 commit c00c633

File tree

2 files changed

+57
-2
lines changed

2 files changed

+57
-2
lines changed

java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerStatementParser.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ String removeCommentsAndTrimInternal(String sql) {
145145
startQuote = 0;
146146
}
147147
} else if (c == '\\') {
148-
lastCharWasEscapeChar = true;
148+
lastCharWasEscapeChar = !lastCharWasEscapeChar;
149149
} else {
150150
lastCharWasEscapeChar = false;
151151
}
@@ -294,7 +294,7 @@ protected boolean checkReturningClauseInternal(String rawSql) {
294294
startQuote = 0;
295295
}
296296
} else if (c == '\\') {
297-
lastCharWasEscapeChar = true;
297+
lastCharWasEscapeChar = !lastCharWasEscapeChar;
298298
} else {
299299
lastCharWasEscapeChar = false;
300300
}

java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SpannerStatementParserTest.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@
1616

1717
package com.google.cloud.spanner.connection;
1818

19+
import static com.google.cloud.spanner.ErrorCode.INVALID_ARGUMENT;
1920
import static com.google.cloud.spanner.connection.StatementParserTest.assertUnclosedLiteral;
2021
import static org.junit.Assert.assertEquals;
22+
import static org.junit.Assert.assertFalse;
23+
import static org.junit.Assert.assertTrue;
24+
import static org.junit.Assert.fail;
2125

2226
import com.google.cloud.spanner.Dialect;
27+
import com.google.cloud.spanner.SpannerException;
28+
import com.google.cloud.spanner.Statement;
2329
import com.google.cloud.spanner.connection.StatementParserTest.CommentInjector;
2430
import org.junit.Test;
2531
import org.junit.runner.RunWith;
@@ -39,6 +45,55 @@ static String skip(String sql, int currentIndex) {
3945
return sql.substring(currentIndex, position);
4046
}
4147

48+
@Test
49+
public void testRemoveCommentsAndTrim() {
50+
AbstractStatementParser parser =
51+
AbstractStatementParser.getInstance(Dialect.GOOGLE_STANDARD_SQL);
52+
53+
// Statements that should parse correctly
54+
String[] validStatements =
55+
new String[] {
56+
"SELECT '\\\\'", // SELECT '\\' (escaped backslash, followed by quote)
57+
"SELECT '\\''", // SELECT '\'' (escaped quote, followed by an actual closing quote)
58+
"SELECT '\\\\\\\\'" // SELECT '\\\\' (two escaped backslashes)
59+
};
60+
for (String sql : validStatements) {
61+
assertEquals(sql, parser.removeCommentsAndTrim(sql));
62+
}
63+
64+
// Statements that contain an unclosed literal because the final quote is
65+
// escaped
66+
String[] invalidStatements =
67+
new String[] {
68+
"SELECT '\\'" // SELECT '\' (escaped closing quote)
69+
};
70+
71+
for (String sql : invalidStatements) {
72+
try {
73+
parser.removeCommentsAndTrim(sql);
74+
fail("Expected SpannerException for unclosed literal: " + sql);
75+
} catch (SpannerException e) {
76+
assertEquals(INVALID_ARGUMENT, e.getErrorCode());
77+
}
78+
}
79+
}
80+
81+
@Test
82+
public void testReturningClauseWithBackslashes() {
83+
AbstractStatementParser parser =
84+
AbstractStatementParser.getInstance(Dialect.GOOGLE_STANDARD_SQL);
85+
86+
// Valid returning clause, double backslash in string literal should be handled
87+
// correctly.
88+
String sqlWithReturning = "INSERT INTO my_table (value) VALUES ('foo \\\\ bar') THEN RETURN id";
89+
assertTrue(parser.parse(Statement.of(sqlWithReturning)).hasReturningClause());
90+
91+
// No returning clause, `then return` is inside a string literal with a double
92+
// backslash.
93+
String sqlWithoutReturning = "INSERT INTO my_table (value) VALUES ('then \\\\ return')";
94+
assertFalse(parser.parse(Statement.of(sqlWithoutReturning)).hasReturningClause());
95+
}
96+
4297
@Test
4398
public void testSkip() {
4499
assertEquals("", skip(""));

0 commit comments

Comments
 (0)