From 260bbe3cb78e0583975d7085ae5a95dbfd3efd73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Wed, 22 Jun 2022 12:38:13 +0200 Subject: [PATCH] fix: PostgreSQL parser should not treat \ as an escape char (#1921) The PostgreSQL parser that removes comments and checks the type of statement should not consider a backslash inside a quoted literal or identifier as an escape character. Instead, only double occurrences of the same quotes as the begin/end quote should be considered as an escape inside a quoted literal or identifier. Fixes #1920 --- .../connection/PostgreSQLStatementParser.java | 5 -- .../connection/StatementParserTest.java | 54 ++++++++++++++----- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/PostgreSQLStatementParser.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/PostgreSQLStatementParser.java index 38f8ed08f9..6eba0ce1b2 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/PostgreSQLStatementParser.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/PostgreSQLStatementParser.java @@ -226,7 +226,6 @@ private int skipQuoted( char startQuote, String dollarTag, @Nullable StringBuilder result) { - boolean lastCharWasEscapeChar = false; int currentIndex = startIndex + 1; while (currentIndex < sql.length()) { char currentChar = sql.charAt(currentIndex); @@ -238,8 +237,6 @@ private int skipQuoted( appendIfNotNull(result, currentChar, dollarTag, currentChar); return currentIndex + tag.length() + 2; } - } else if (lastCharWasEscapeChar) { - lastCharWasEscapeChar = false; } else if (sql.length() > currentIndex + 1 && sql.charAt(currentIndex + 1) == startQuote) { // This is an escaped quote (e.g. 'foo''bar') appendIfNotNull(result, currentChar); @@ -250,8 +247,6 @@ private int skipQuoted( appendIfNotNull(result, currentChar); return currentIndex + 1; } - } else { - lastCharWasEscapeChar = currentChar == '\\'; } currentIndex++; appendIfNotNull(result, currentChar); 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 b45f6cb3ea..319180894d 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 @@ -270,6 +270,34 @@ public void testPostgresSQLDialectDollarQuoted() { .isEqualTo("SELECT FOO, $BAR \nFROM SOME_TABLE"); } + @Test + public void testPostgreSQLDialectUnicodeEscapedIdentifiers() { + assumeTrue(dialect == Dialect.POSTGRESQL); + + assertEquals( + "SELECT 'tricky' AS \"\\\"", parser.removeCommentsAndTrim("SELECT 'tricky' AS \"\\\"")); + assertEquals( + "SELECT 'tricky' AS U&\"\\\" UESCAPE '!'", + parser.removeCommentsAndTrim("SELECT 'tricky' AS U&\"\\\" UESCAPE '!'")); + assertEquals( + "SELECT '\\' AS \"tricky\"", parser.removeCommentsAndTrim("SELECT '\\' AS \"tricky\"")); + assertEquals("SELECT 'foo''bar'", parser.removeCommentsAndTrim("SELECT 'foo''bar'")); + assertEquals("SELECT 'foo\"bar'", parser.removeCommentsAndTrim("SELECT 'foo\"bar'")); + assertEquals("SELECT 'foo\"\"bar'", parser.removeCommentsAndTrim("SELECT 'foo\"\"bar'")); + assertEquals( + "SELECT 'foo'", parser.removeCommentsAndTrim("SELECT /* This is a 'comment' */ 'foo'")); + assertEquals( + "SELECT 'foo'", + parser.removeCommentsAndTrim("SELECT /* This is a '''comment''' */ 'foo'")); + assertEquals( + "SELECT '''foo''' FROM bar", + parser.removeCommentsAndTrim("SELECT /* This is a '''comment''' */ '''foo''' FROM bar")); + assertEquals( + "SELECT '''foo''' FROM \"\"\"\\bar\\\"\"\"", + parser.removeCommentsAndTrim( + "SELECT /* This is a '''comment''' */ '''foo''' FROM \"\"\"\\bar\\\"\"\"")); + } + @Test public void testPostgreSQLDialectSupportsEmbeddedComments() { assumeTrue(dialect == Dialect.POSTGRESQL); @@ -1109,25 +1137,25 @@ public void testPostgreSQLDialectDialectConvertPositionalParametersToNamedParame .sqlWithNamedParameters) .isEqualTo("$1'?test?\"?test?\"?'$2"); assertThat( - parser.convertPositionalParametersToNamedParameters('?', "?'?it\\'?s'?") + parser.convertPositionalParametersToNamedParameters('?', "?'?it\\''?s'?") .sqlWithNamedParameters) - .isEqualTo("$1'?it\\'?s'$2"); + .isEqualTo("$1'?it\\''?s'$2"); assertThat( parser.convertPositionalParametersToNamedParameters('?', "?'?it\\\"?s'?") .sqlWithNamedParameters) .isEqualTo("$1'?it\\\"?s'$2"); assertThat( - parser.convertPositionalParametersToNamedParameters('?', "?\"?it\\\"?s\"?") + parser.convertPositionalParametersToNamedParameters('?', "?\"?it\\\"\"?s\"?") .sqlWithNamedParameters) - .isEqualTo("$1\"?it\\\"?s\"$2"); + .isEqualTo("$1\"?it\\\"\"?s\"$2"); assertThat( - parser.convertPositionalParametersToNamedParameters('?', "?'''?it\\'?s'''?") + parser.convertPositionalParametersToNamedParameters('?', "?'''?it\\''?s'''?") .sqlWithNamedParameters) - .isEqualTo("$1'''?it\\'?s'''$2"); + .isEqualTo("$1'''?it\\''?s'''$2"); assertThat( - parser.convertPositionalParametersToNamedParameters('?', "?\"\"\"?it\\\"?s\"\"\"?") + parser.convertPositionalParametersToNamedParameters('?', "?\"\"\"?it\\\"\"?s\"\"\"?") .sqlWithNamedParameters) - .isEqualTo("$1\"\"\"?it\\\"?s\"\"\"$2"); + .isEqualTo("$1\"\"\"?it\\\"\"?s\"\"\"$2"); assertThat( parser.convertPositionalParametersToNamedParameters('?', "?$$?it$?s$$?") @@ -1144,13 +1172,13 @@ public void testPostgreSQLDialectDialectConvertPositionalParametersToNamedParame // Note: PostgreSQL allows a single-quoted string literal to contain line feeds. assertEquals( - "$1'?it\\'?s \n ?it\\'?s'$2", - parser.convertPositionalParametersToNamedParameters('?', "?'?it\\'?s \n ?it\\'?s'?") + "$1'?it\\''?s \n ?it\\''?s'$2", + parser.convertPositionalParametersToNamedParameters('?', "?'?it\\''?s \n ?it\\''?s'?") .sqlWithNamedParameters); - assertUnclosedLiteral("?'?it\\'?s \n ?it\\'?s?"); + assertUnclosedLiteral("?'?it\\''?s \n ?it\\''?s?"); assertEquals( - "$1'''?it\\'?s \n ?it\\'?s'$2", - parser.convertPositionalParametersToNamedParameters('?', "?'''?it\\'?s \n ?it\\'?s'?") + "$1'''?it\\''?s \n ?it\\''?s'$2", + parser.convertPositionalParametersToNamedParameters('?', "?'''?it\\''?s \n ?it\\''?s'?") .sqlWithNamedParameters); assertThat(