Skip to content

Commit

Permalink
fix: PostgreSQL parser should not treat \ as an escape char
Browse files Browse the repository at this point in the history
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
  • Loading branch information
olavloite committed Jun 22, 2022
1 parent be8b50b commit 571b0b4
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -250,8 +247,6 @@ private int skipQuoted(
appendIfNotNull(result, currentChar);
return currentIndex + 1;
}
} else {
lastCharWasEscapeChar = currentChar == '\\';
}
currentIndex++;
appendIfNotNull(result, currentChar);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,25 @@ 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);
Expand Down Expand Up @@ -1109,25 +1128,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$$?")
Expand All @@ -1144,13 +1163,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(
Expand Down

0 comments on commit 571b0b4

Please sign in to comment.