From 842494bb36062fbb3d4303ac778ee8083fca139e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sun, 8 May 2022 17:43:47 +0200 Subject: [PATCH] fix: split statement did not correctly parse escaped quotes --- .../statements/IntermediateStatement.java | 44 +++--- .../pgadapter/utils/StatementParser.java | 17 +-- .../cloud/spanner/pgadapter/PSQLTest.java | 129 +++++++++--------- .../pgadapter/StatementParserTest.java | 9 +- .../statements/IntermediateStatementTest.java | 29 ++++ 5 files changed, 128 insertions(+), 100 deletions(-) diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatement.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatement.java index 182fa1157..be07d0d79 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatement.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatement.java @@ -69,6 +69,7 @@ public class IntermediateStatement { private static final char STATEMENT_DELIMITER = ';'; private static final char SINGLE_QUOTE = '\''; + private static final char DOUBLE_QUOTE = '"'; public IntermediateStatement( OptionsMetadata options, @@ -134,7 +135,7 @@ protected static ImmutableList determineStatementTypes( return builder.build(); } - private static ImmutableList splitStatements(String sql) { + static ImmutableList splitStatements(String sql) { // First check trivial cases with only one statement. int firstIndexOfDelimiter = sql.indexOf(STATEMENT_DELIMITER); if (firstIndexOfDelimiter == -1) { @@ -147,26 +148,33 @@ private static ImmutableList splitStatements(String sql) { ImmutableList.Builder builder = ImmutableList.builder(); // TODO: Fix this parsing, as it does not take all types of quotes into consideration. boolean insideQuotes = false; - boolean currentCharacterIsEscaped = false; + char quoteChar = 0; int index = 0; for (int i = 0; i < sql.length(); ++i) { - // ignore escaped quotes - if (sql.charAt(i) == SINGLE_QUOTE && !currentCharacterIsEscaped) { - insideQuotes = !insideQuotes; - } - // skip semicolon inside quotes - if (sql.charAt(i) == STATEMENT_DELIMITER && !insideQuotes) { - String stmt = sql.substring(index, i).trim(); - // Statements with only ';' character are empty and dropped. - if (stmt.length() > 0) { - builder.add(stmt); + if (insideQuotes) { + if (sql.charAt(i) == quoteChar) { + if (sql.length() > (i + 1) && sql.charAt(i + 1) == quoteChar) { + // This is an escaped quote. Skip one ahead. + i++; + } else { + insideQuotes = false; + } + } + } else { + if (sql.charAt(i) == SINGLE_QUOTE || sql.charAt(i) == DOUBLE_QUOTE) { + quoteChar = sql.charAt(i); + insideQuotes = true; + } else { + // skip semicolon inside quotes + if (sql.charAt(i) == STATEMENT_DELIMITER) { + String stmt = sql.substring(index, i).trim(); + // Statements with only ';' character are empty and dropped. + if (stmt.length() > 0) { + builder.add(stmt); + } + index = i + 1; + } } - index = i + 1; - } - if (currentCharacterIsEscaped) { - currentCharacterIsEscaped = false; - } else if (sql.charAt(i) == '\\') { - currentCharacterIsEscaped = true; } } diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/utils/StatementParser.java b/src/main/java/com/google/cloud/spanner/pgadapter/utils/StatementParser.java index 45d0aff0c..92d4f0501 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/utils/StatementParser.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/utils/StatementParser.java @@ -26,23 +26,10 @@ public class StatementParser { /** * Simple method to escape SQL. Ultimately it is preferred that a user uses PreparedStatements but * for the case of psql emulation, we apply this to provide a simple layer of protection to the - * user. Here we simple escape the single quote if it is not currently escaped. Note that the only - * reason we are doing it manually is because StringEscapeUtils deprecated escapeSql. + * user. This method simply duplicates all single quotes (i.e. ' becomes ''). */ public static String singleQuoteEscape(String sql) { - StringBuilder result = new StringBuilder(); - boolean currentCharacterIsEscaped = false; - for (char currentCharacter : sql.toCharArray()) { - if (currentCharacterIsEscaped) { - currentCharacterIsEscaped = false; - } else if (currentCharacter == '\\') { - currentCharacterIsEscaped = true; - } else if (currentCharacter == '\'') { - result.append('\\'); - } - result.append(currentCharacter); - } - return result.toString(); + return sql.replace("'", "''"); } /** Determines the (update) command that was received from the sql string. */ diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/PSQLTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/PSQLTest.java index 902ef17a6..f35a8fa7b 100644 --- a/src/test/java/com/google/cloud/spanner/pgadapter/PSQLTest.java +++ b/src/test/java/com/google/cloud/spanner/pgadapter/PSQLTest.java @@ -14,6 +14,8 @@ package com.google.cloud.spanner.pgadapter; +import static org.junit.Assert.assertEquals; + import com.google.cloud.spanner.Dialect; import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.connection.AbstractStatementParser; @@ -24,7 +26,6 @@ import com.google.cloud.spanner.pgadapter.statements.MatcherStatement; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -82,7 +83,7 @@ public void testDescribeTranslates() { + " AND n.nspname !~ '^pg_toast'\n" + " AND pg_catalog.pg_table_is_visible(c.oid)\n" + "ORDER BY 1,2;"; - String result = + String expected = "SELECT" + " t.table_schema as \"Schema\"," + " t.table_name as \"Name\"," @@ -96,7 +97,7 @@ public void testDescribeTranslates() { MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -111,7 +112,7 @@ public void testDescribeTableMatchTranslates() { + "WHERE c.relname OPERATOR(pg_catalog.~) '^(users)$'\n" + " AND pg_catalog.pg_table_is_visible(c.oid)\n" + "ORDER BY 2, 3;"; - String result = + String expected = "SELECT" + " t.table_name as oid," + " 'public' as nspname," @@ -126,7 +127,7 @@ public void testDescribeTableMatchTranslates() { MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -141,7 +142,7 @@ public void testDescribeTableMatchHandlesBobbyTables() { + "WHERE c.relname OPERATOR(pg_catalog.~) '^(bobby'; DROP TABLE USERS; SELECT')$'\n" + " AND pg_catalog.pg_table_is_visible(c.oid)\n" + "ORDER BY 2, 3;"; - String result = + String expected = "SELECT" + " t.table_name as oid," + " 'public' as nspname," @@ -151,12 +152,12 @@ public void testDescribeTableMatchHandlesBobbyTables() { + " WHERE" + " t.table_schema='public'" + " AND" - + " LOWER(t.table_name) = LOWER('bobby\\'; DROP TABLE USERS; SELECT\\'')"; + + " LOWER(t.table_name) = LOWER('bobby''; DROP TABLE USERS; SELECT''')"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -166,7 +167,7 @@ public void testDescribeTableCatalogTranslates() { "SELECT relchecks, relkind, relhasindex, relhasrules, reltriggers <> 0, false, false," + " relhasoids, false as relispartition, '', ''\n" + "FROM pg_catalog.pg_class WHERE oid = '-2264987671676060158';"; - String result = + String expected = "SELECT" + " 0 as relcheck," + " 'r' as relkind," @@ -182,7 +183,7 @@ public void testDescribeTableCatalogTranslates() { MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -201,7 +202,7 @@ public void testDescribeTableMetadataTranslates() { + "FROM pg_catalog.pg_attribute a\n" + "WHERE a.attrelid = '-1' AND a.attnum > 0 AND NOT a.attisdropped\n" + "ORDER BY a.attnum;"; - String result = + String expected = "SELECT" + " t.column_name as attname," + " t.data_type as format_type," @@ -219,7 +220,7 @@ public void testDescribeTableMetadataTranslates() { MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -239,7 +240,7 @@ public void testDescribeTableMetadataHandlesBobbyTables() { + "WHERE a.attrelid = 'bobby'; DROP TABLE USERS; SELECT'' AND a.attnum > 0 AND NOT" + " a.attisdropped\n" + "ORDER BY a.attnum;"; - String result = + String expected = "SELECT" + " t.column_name as attname," + " t.data_type as format_type," @@ -252,12 +253,12 @@ public void testDescribeTableMetadataHandlesBobbyTables() { + " information_schema.columns AS t" + " WHERE" + " t.table_schema='public'" - + " AND t.table_name = 'bobby\\'; DROP TABLE USERS; SELECT\\''"; + + " AND t.table_name = 'bobby''; DROP TABLE USERS; SELECT'''"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -267,12 +268,12 @@ public void testDescribeTableAttributesTranslates() { "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i" + " WHERE c.oid=i.inhparent AND i.inhrelid = '-2264987671676060158' AND c.relkind !=" + " 'p' ORDER BY inhseqno;"; - String result = "SELECT 1 LIMIT 0"; + String expected = "SELECT 1 LIMIT 0"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -282,12 +283,12 @@ public void testDescribeMoreTableAttributesTranslates() { "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i" + " WHERE c.oid=i.inhrelid AND i.inhparent = '-2264987671676060158' ORDER BY" + " c.relname;"; - String result = "SELECT 1 LIMIT 0"; + String expected = "SELECT 1 LIMIT 0"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -300,12 +301,12 @@ public void testListCommandTranslates() { + " pg_catalog.array_to_string(d.datacl, '\\n') AS \"Access privileges\"\n" + "FROM pg_catalog.pg_database d\n" + "ORDER BY 1;"; - String result = "SELECT '' AS Name"; + String expected = "SELECT '' AS Name"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -319,12 +320,12 @@ public void testListDatabaseCommandTranslates() { + "FROM pg_catalog.pg_database d\n" + "WHERE d.datname OPERATOR(pg_catalog.~) '^(users)$'\n" + "ORDER BY 1;"; - String result = "SELECT '' AS Name"; + String expected = "SELECT '' AS Name"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -338,14 +339,14 @@ public void testListDatabaseCommandFailsButPrintsUnknown() { + "FROM pg_catalog.pg_database d\n" + "WHERE d.datname OPERATOR(pg_catalog.~) '^(users)$'\n" + "ORDER BY 1;"; - String result = "SELECT '' AS Name"; + String expected = "SELECT '' AS Name"; // TODO: Add Connection#getDatabase() to Connection API and test here what happens if that // method throws an exception. MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -367,12 +368,12 @@ public void testDescribeAllTableMetadataTranslates() { + " AND n.nspname !~ '^pg_toast'\n" + " AND pg_catalog.pg_table_is_visible(c.oid)\n" + "ORDER BY 1,2;"; - String result = "SELECT * FROM information_schema.tables"; + String expected = "SELECT * FROM information_schema.tables"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -393,13 +394,13 @@ public void testDescribeSelectedTableMetadataTranslates() { + " AND c.relname OPERATOR(pg_catalog.~) '^(users)$'\n" + " AND pg_catalog.pg_table_is_visible(c.oid)\n" + "ORDER BY 1,2;"; - String result = + String expected = "SELECT * FROM information_schema.tables WHERE LOWER(table_name) = LOWER('users')"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -420,14 +421,14 @@ public void testDescribeSelectedTableMetadataHandlesBobbyTables() { + " AND c.relname OPERATOR(pg_catalog.~) '^(bobby'; DROP TABLE USERS; SELECT')$'\n" + " AND pg_catalog.pg_table_is_visible(c.oid)\n" + "ORDER BY 1,2;"; - String result = + String expected = "SELECT * FROM information_schema.tables WHERE LOWER(table_name) =" - + " LOWER('bobby\\'; DROP TABLE USERS; SELECT\\'')"; + + " LOWER('bobby''; DROP TABLE USERS; SELECT''')"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -452,13 +453,13 @@ public void testDescribeAllIndexMetadataTranslates() { + " AND n.nspname !~ '^pg_toast'\n" + " AND pg_catalog.pg_table_is_visible(c.oid)\n" + "ORDER BY 1,2;"; - String result = + String expected = "SELECT table_catalog, table_schema, table_name, index_name, index_type, parent_table_name, is_unique, is_null_filtered, index_state, spanner_is_managed FROM information_schema.indexes"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -482,14 +483,14 @@ public void testDescribeSelectedIndexMetadataTranslates() { + " AND c.relname OPERATOR(pg_catalog.~) '^(index)$'\n" + " AND pg_catalog.pg_table_is_visible(c.oid)\n" + "ORDER BY 1,2;"; - String result = + String expected = "SELECT table_catalog, table_schema, table_name, index_name, index_type, parent_table_name, is_unique, is_null_filtered, index_state, spanner_is_managed FROM information_schema.indexes WHERE LOWER(index_name) =" + " LOWER('index')"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -513,14 +514,14 @@ public void testDescribeSelectedIndexMetadataHandlesBobbyTables() { + " AND c.relname OPERATOR(pg_catalog.~) '^(bobby'; DROP TABLE USERS; SELECT')$'\n" + " AND pg_catalog.pg_table_is_visible(c.oid)\n" + "ORDER BY 1,2;"; - String result = + String expected = "SELECT table_catalog, table_schema, table_name, index_name, index_type, parent_table_name, is_unique, is_null_filtered, index_state, spanner_is_managed FROM information_schema.indexes WHERE LOWER(index_name) =" - + " LOWER('bobby\\'; DROP TABLE USERS; SELECT\\'')"; + + " LOWER('bobby''; DROP TABLE USERS; SELECT''')"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -532,12 +533,12 @@ public void testDescribeAllSchemaMetadataTranslates() { + "FROM pg_catalog.pg_namespace n\n" + "WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n" + "ORDER BY 1;"; - String result = "SELECT * FROM information_schema.schemata"; + String expected = "SELECT * FROM information_schema.schemata"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -549,13 +550,13 @@ public void testDescribeSelectedSchemaMetadataTranslates() { + "FROM pg_catalog.pg_namespace n\n" + "WHERE n.nspname OPERATOR(pg_catalog.~) '^(schema)$'\n" + "ORDER BY 1;"; - String result = + String expected = "SELECT * FROM information_schema.schemata WHERE LOWER(schema_name) = LOWER('schema')"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -567,14 +568,14 @@ public void testDescribeSelectedSchemaMetadataHandlesBobbyTables() { + "FROM pg_catalog.pg_namespace n\n" + "WHERE n.nspname OPERATOR(pg_catalog.~) '^(bobby'; DROP TABLE USERS; SELECT')$'\n" + "ORDER BY 1;"; - String result = - "SELECT * FROM information_schema.schemata WHERE LOWER(schema_name) = LOWER('bobby\\'; DROP" - + " TABLE USERS; SELECT\\'')"; + String expected = + "SELECT * FROM information_schema.schemata WHERE LOWER(schema_name) = LOWER('bobby''; DROP" + + " TABLE USERS; SELECT''')"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -604,7 +605,7 @@ public void testTableSelectAutocomplete() { + " substring(pg_catalog.quote_ident(nspname) || '.',1,0) =" + " substring('',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) = 1\n" + "LIMIT 1000"; - String result = + String expected = "SELECT table_name AS quote_ident FROM information_schema.tables WHERE" + " table_schema = 'public' and STARTS_WITH(LOWER(table_name)," + " LOWER('user')) LIMIT 1000"; @@ -612,7 +613,7 @@ public void testTableSelectAutocomplete() { MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -641,7 +642,7 @@ public void testTableInsertAutocomplete() { + " substring(pg_catalog.quote_ident(nspname) || '.',1,4) =" + " substring('user',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) = 1\n" + "LIMIT 1000"; - String result = + String expected = "SELECT table_name AS quote_ident FROM information_schema.tables WHERE" + " table_schema = 'public' and STARTS_WITH(LOWER(table_name)," + " LOWER('user')) LIMIT 1000"; @@ -649,7 +650,7 @@ public void testTableInsertAutocomplete() { MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -663,14 +664,14 @@ public void testTableAttributesAutocomplete() { + " (pg_catalog.quote_ident(relname)='user' OR '\"' || relname || '\"'='user') " + " AND pg_catalog.pg_table_is_visible(c.oid)\n" + "LIMIT 1000"; - String result = + String expected = "SELECT column_name AS quote_ident FROM information_schema.columns WHERE" + " table_name = 'user' AND STARTS_WITH(LOWER(COLUMN_NAME), LOWER('age')) LIMIT 1000"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -698,14 +699,14 @@ public void testDescribeTableAutocomplete() { + " substring(pg_catalog.quote_ident(nspname) || '.',1,4) =" + " substring('user',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) = 1\n" + "LIMIT 1000"; - String result = + String expected = "SELECT table_name AS quote_ident FROM information_schema.tables WHERE " + "table_schema = 'public' AND STARTS_WITH(LOWER(table_name), LOWER('user')) LIMIT 1000"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -733,14 +734,14 @@ public void testDescribeTableMetadataAutocomplete() { + " substring(pg_catalog.quote_ident(nspname) || '.',1,4) =" + " substring('user',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) = 1\n" + "LIMIT 1000"; - String result = + String expected = "SELECT table_name AS quote_ident FROM INFORMATION_SCHEMA.TABLES WHERE" + " STARTS_WITH(LOWER(table_name), LOWER('user')) LIMIT 1000"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -768,14 +769,14 @@ public void testDescribeIndexMetadataAutocomplete() { + " substring(pg_catalog.quote_ident(nspname) || '.',1,5) =" + " substring('index',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) = 1\n" + "LIMIT 1000"; - String result = + String expected = "SELECT index_name AS quote_ident FROM INFORMATION_SCHEMA.INDEXES WHERE" + " STARTS_WITH(LOWER(index_name), LOWER('index')) LIMIT 1000"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -785,14 +786,14 @@ public void testDescribeSchemaMetadataAutocomplete() { "SELECT pg_catalog.quote_ident(nspname) FROM pg_catalog.pg_namespace WHERE" + " substring(pg_catalog.quote_ident(nspname),1,6)='schema'\n" + "LIMIT 1000"; - String result = + String expected = "SELECT schema_name AS quote_ident FROM INFORMATION_SCHEMA.SCHEMATA WHERE" + " STARTS_WITH(LOWER(schema_name), LOWER('schema')) LIMIT 1000"; MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), result); + assertEquals(expected, matcherStatement.getSql()); } @Test @@ -826,10 +827,10 @@ public void testDynamicCommands() throws Exception { MatcherStatement matcherStatement = new MatcherStatement(options, parse(firstSQL), connectionHandler); - Assert.assertEquals(expectedFirstResult, matcherStatement.getSql()); + assertEquals(expectedFirstResult, matcherStatement.getSql()); matcherStatement = new MatcherStatement(options, parse(secondSQL), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), expectedSecondResult); + assertEquals(expectedSecondResult, matcherStatement.getSql()); } @Test @@ -856,6 +857,6 @@ public void testMatcherGroupInPlaceReplacements() throws Exception { MatcherStatement matcherStatement = new MatcherStatement(options, parse(sql), connectionHandler); - Assert.assertEquals(matcherStatement.getSql(), expectedResult); + assertEquals(expectedResult, matcherStatement.getSql()); } } diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/StatementParserTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/StatementParserTest.java index 6de3e89dc..79909468e 100644 --- a/src/test/java/com/google/cloud/spanner/pgadapter/StatementParserTest.java +++ b/src/test/java/com/google/cloud/spanner/pgadapter/StatementParserTest.java @@ -14,6 +14,7 @@ package com.google.cloud.spanner.pgadapter; +import static com.google.cloud.spanner.pgadapter.utils.StatementParser.singleQuoteEscape; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; @@ -21,7 +22,6 @@ import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.connection.AbstractStatementParser; -import com.google.cloud.spanner.pgadapter.utils.StatementParser; import org.junit.Assert; import org.junit.Test; @@ -148,7 +148,10 @@ public void testRemoveCommentsAndTrimWithUnterminatedComment() { @Test public void testEscapes() { String sql = "Bobby\\'O\\'Bob'; DROP TABLE USERS; select'"; - String expectedSql = "Bobby\\'O\\'Bob\\'; DROP TABLE USERS; select\\'"; - Assert.assertEquals(StatementParser.singleQuoteEscape(sql), expectedSql); + String expectedSql = "Bobby\\''O\\''Bob''; DROP TABLE USERS; select''"; + assertEquals(expectedSql, singleQuoteEscape(sql)); + + assertEquals("''test''", singleQuoteEscape("'test'")); + assertEquals("''''test''''", singleQuoteEscape("''test''")); } } diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatementTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatementTest.java index 72599d5c1..697510be0 100644 --- a/src/test/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatementTest.java +++ b/src/test/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatementTest.java @@ -14,6 +14,7 @@ package com.google.cloud.spanner.pgadapter.statements; +import static com.google.cloud.spanner.pgadapter.statements.IntermediateStatement.splitStatements; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -32,6 +33,7 @@ import com.google.cloud.spanner.connection.StatementResult.ResultType; import com.google.cloud.spanner.pgadapter.ConnectionHandler; import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata; +import com.google.common.collect.ImmutableList; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -55,6 +57,33 @@ private static ParsedStatement parse(String sql) { @Mock private Connection connection; + @Test + public void testSplitStatements() { + assertEquals(ImmutableList.of(""), splitStatements("")); + assertEquals(ImmutableList.of("select 1"), splitStatements("select 1")); + assertEquals(ImmutableList.of("select 1"), splitStatements("select 1;")); + assertEquals(ImmutableList.of("select 1", "select 2"), splitStatements("select 1; select 2;")); + assertEquals( + ImmutableList.of("select '1;2'", "select 2"), splitStatements("select '1;2'; select 2;")); + assertEquals( + ImmutableList.of("select \"bobby;drop table\"", "select 2"), + splitStatements("select \"bobby;drop table\"; select 2;")); + assertEquals( + ImmutableList.of("select '1'';\"2'", "select 2"), + splitStatements("select '1'';\"2'; select 2;")); + assertEquals( + ImmutableList.of("select \"a\"\";b\"", "select 2"), + splitStatements("select \"a\"\";b\"; select 2;")); + assertEquals( + ImmutableList.of("select '1'''", "select '2'"), + splitStatements("select '1'''; select '2'")); + assertEquals( + ImmutableList.of("select '1'''", "select '2'''"), + splitStatements("select '1'''; select '2'''")); + assertEquals( + ImmutableList.of("select 1", "select '2'';'"), splitStatements("select 1; select '2'';'")); + } + @Test public void testUpdateResultCount_ResultSet() { when(connectionHandler.getSpannerConnection()).thenReturn(connection);