From ad5f5b68c35fc33e42ee7d8a9b31e8e89b7cc126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Fri, 28 Nov 2025 10:47:20 +0100 Subject: [PATCH] fix: backslash at end of string literal was misinterpreted SQL strings that contained a string literal with a backslash as the last character were misinterpreted by the statement parser as containing unclosed string literals. Fixes https://github.com/googleapis/java-spanner-jdbc/issues/2303 --- .../cloud/spanner/connection/AbstractStatementParser.java | 5 +++-- .../cloud/spanner/connection/SpannerStatementParserTest.java | 1 + .../google/cloud/spanner/connection/StatementParserTest.java | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java index 4bf6609110f..fea032e2f52 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java @@ -1033,8 +1033,9 @@ int skipQuoted( } else if (supportsBackslashEscape() && currentChar == BACKSLASH && length > currentIndex + 1 - && sql.charAt(currentIndex + 1) == startQuote) { - // This is an escaped quote (e.g. 'foo\'bar'). + && (sql.charAt(currentIndex + 1) == startQuote + || sql.charAt(currentIndex + 1) == BACKSLASH)) { + // This is an escaped quote (e.g. 'foo\'bar') or an escaped backslash (e.g. 'test\\'). // Note that in raw strings, the \ officially does not start an escape sequence, but the // result is still the same, as in a raw string 'both characters are preserved'. appendIfNotNull(result, currentChar); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SpannerStatementParserTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SpannerStatementParserTest.java index 035ab72959b..10f01df937a 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SpannerStatementParserTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SpannerStatementParserTest.java @@ -54,6 +54,7 @@ public void testSkip() { assertEquals("'foo\"bar\"'", skip("'foo\"bar\"' ", 0)); assertEquals("\"foo'bar'\"", skip("\"foo'bar'\" ", 0)); assertEquals("`foo'bar'`", skip("`foo'bar'` ", 0)); + assertEquals("'test\\\\'", skip("'test\\\\'", 0)); assertEquals("'''foo'bar'''", skip("'''foo'bar''' ", 0)); assertEquals("'''foo\\'bar'''", skip("'''foo\\'bar''' ", 0)); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java index c37b769419c..300517faaf0 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java @@ -1268,6 +1268,10 @@ public void testGoogleStandardSQLDialectConvertPositionalParametersToNamedParame "@p1'''?it\\'?s \n ?it\\'?s'''@p2", parser.convertPositionalParametersToNamedParameters('?', "?'''?it\\'?s \n ?it\\'?s'''?") .sqlWithNamedParameters); + assertEquals( + "@p1'?test?\\\\'@p2", + parser.convertPositionalParametersToNamedParameters('?', "?'?test?\\\\'?") + .sqlWithNamedParameters); assertUnclosedLiteral(parser, "?'?it\\'?s \n ?it\\'?s'?"); assertUnclosedLiteral(parser, "?'?it\\'?s \n ?it\\'?s?");