Permalink
Browse files

fix: better parsing for returning keyword

Note: things like  insert into ... select 1 returning from ... are still considered as "having returning keyword"

fixes #824
  • Loading branch information...
vlsi committed Jul 1, 2017
1 parent 551d71b commit 201daf1dc916bbc35e2bbec961aebfd1b1e30bfc
@@ -173,9 +173,16 @@
break;
default:
isKeyWordChar =
aChars[i] >= 'a' && aChars[i] <= 'z' || aChars[i] >= 'A' && aChars[i] <= 'Z';
if (isKeyWordChar && keywordStart < 0) {
if (keywordStart >= 0) {
// When we are inside a keyword, we need to detect keyword end boundary
// Note that isKeyWordChar is initialized to false before the switch, so
// all other characters would result in isKeyWordChar=false
isKeyWordChar = isIdentifierContChar(aChar);
break;
}
// Not in keyword, so just detect next keyword start
isKeyWordChar = isIdentifierStartChar(aChar);
if (isKeyWordChar) {
keywordStart = i;
}
break;
@@ -205,7 +212,9 @@
}
}
}
if (wordLength == 9 && parseReturningKeyword(aChars, keywordStart)) {
if (inParen != 0 || aChar == ')') {
// RETURNING and VALUES cannot be present in braces
} else if (wordLength == 9 && parseReturningKeyword(aChars, keywordStart)) {
isReturningPresent = true;
} else if (wordLength == 6 && parseValuesKeyword(aChars, keywordStart)) {
isValuesFound = true;
@@ -659,19 +668,23 @@ public static boolean isOperatorChar(char c) {
/**
* Checks if a character is valid as the start of an identifier.
* PostgreSQL 9.4 allows column names like _, ‿, ⁀, ⁔, ︳, ︴, ﹍, ﹎, ﹏, _, so
* it is assumed isJavaIdentifierPart is good enough for PostgreSQL.
*
* @see <a href="https://www.postgresql.org/docs/9.6/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS">Identifiers and Key Words</a>
*
* @param c the character to check
* @return true if valid as first character of an identifier; false if not
*/
public static boolean isIdentifierStartChar(char c) {
/*
* Extracted from {ident_start} and {ident_cont} in
* PostgreSQL's implmementation is located in
* pgsql/src/backend/parser/scan.l:
* ident_start [A-Za-z\200-\377_]
* ident_cont [A-Za-z\200-\377_0-9\$]
* however is is not clear how that interacts with unicode, so we just use Java's implementation.
*/
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|| c == '_' || c > 127;
return Character.isJavaIdentifierStart(c);
}
/**
@@ -681,10 +694,7 @@ public static boolean isIdentifierStartChar(char c) {
* @return true if valid as second or later character of an identifier; false if not
*/
public static boolean isIdentifierContChar(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|| c == '_' || c > 127
|| (c >= '0' && c <= '9')
|| c == '$';
return Character.isJavaIdentifierPart(c);
}
/**
@@ -706,9 +716,13 @@ public static boolean isDollarQuoteStartChar(char c) {
* The allowed dollar quote start and continuation characters
* must stay in sync with what the backend defines in
* pgsql/src/backend/parser/scan.l
*
* The quoted string starts with $foo$ where "foo" is an optional string
* in the form of an identifier, except that it may not contain "$",
* and extends to the first occurrence of an identical string.
* There is *no* processing of the quoted text.
*/
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|| c == '_' || c > 127;
return c != '$' && isIdentifierStartChar(c);
}
/**
@@ -718,9 +732,7 @@ public static boolean isDollarQuoteStartChar(char c) {
* @return true if valid as second or later character of a dollar quoting tag; false if not
*/
public static boolean isDollarQuoteContChar(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|| c == '_' || c > 127
|| (c >= '0' && c <= '9');
return c != '$' && isIdentifierContChar(c);
}
/**
@@ -8,8 +8,13 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.sql.SQLException;
import java.util.List;
/**
* Test cases for the Parser.
* @author Jeremy Whiting jwhiting@redhat.com
@@ -133,4 +138,27 @@ public void testEscapeProcessing() throws Exception {
public void testUnterminatedEscape() throws Exception {
assertEquals("{oj ", Parser.replaceProcessing("{oj ", true, false));
}
@Test
@Ignore(value = "returning in the select clause is hard to distinguish from insert ... returning *")
public void insertSelectFakeReturning() throws SQLException {
String query =
"insert test(id, name) select 1, 'value' as RETURNING from test2";
List<NativeQuery> qry =
Parser.parseJdbcSql(
query, true, true, true, true, new String[0]);
boolean returningKeywordPresent = qry.get(0).command.isReturningKeywordPresent();
Assert.assertFalse("Query does not have returning clause " + query, returningKeywordPresent);
}
@Test
public void insertSelectReturning() throws SQLException {
String query =
"insert test(id, name) select 1, 'value' from test2 RETURNING id";
List<NativeQuery> qry =
Parser.parseJdbcSql(
query, true, true, true, true, new String[0]);
boolean returningKeywordPresent = qry.get(0).command.isReturningKeywordPresent();
Assert.assertTrue("Query has a returning clause " + query, returningKeywordPresent);
}
}
@@ -0,0 +1,71 @@
/*
* Copyright (c) 2003, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package org.postgresql.core;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@RunWith(Parameterized.class)
public class ReturningParserTest {
private final String columnName;
private final String returning;
private final String prefix;
private final String suffix;
public ReturningParserTest(String columnName, String returning, String prefix, String suffix) {
this.columnName = columnName;
this.returning = returning;
this.prefix = prefix;
this.suffix = suffix;
}
@Parameterized.Parameters(name = "columnName={2} {0} {3}, returning={2} {1} {3}")
public static Iterable<Object[]> data() {
Collection<Object[]> ids = new ArrayList<Object[]>();
String[] delimiters = {"", "_", "3", "*", " "};
for (String columnName : new String[]{"returning", "returningreturning"}) {
for (String prefix : delimiters) {
for (String suffix : delimiters) {
for (String returning : new String[]{"returning", "returningreturning"}) {
ids.add(new Object[]{columnName, returning, prefix, suffix});
}
}
}
}
return ids;
}
@Test
public void test() throws SQLException {
String query =
"insert into\"prep\"(a, " + prefix + columnName + suffix + ")values(1,2)" + prefix
+ returning + suffix;
List<NativeQuery> qry =
Parser.parseJdbcSql(
query, true, true, true, true, new String[0]);
boolean returningKeywordPresent = qry.get(0).command.isReturningKeywordPresent();
boolean expectedReturning = this.returning.equalsIgnoreCase("returning")
&& (prefix.length() == 0 || !Character.isJavaIdentifierStart(prefix.charAt(0)))
&& (suffix.length() == 0 || !Character.isJavaIdentifierPart(suffix.charAt(0)));
if (expectedReturning != returningKeywordPresent) {
Assert.assertEquals("Wrong <returning_clause> detected in SQL " + query,
expectedReturning,
returningKeywordPresent);
}
}
}
@@ -6,6 +6,7 @@
package org.postgresql.test.jdbc2;
import org.postgresql.core.ParserTest;
import org.postgresql.core.ReturningParserTest;
import org.postgresql.core.ServerVersion;
import org.postgresql.core.v3.V3ParameterListTests;
import org.postgresql.jdbc.DeepBatchedInsertStatementTest;
@@ -77,6 +78,7 @@ public static TestSuite suite() throws Exception {
suite.addTest(new JUnit4TestAdapter(PGTimestampTest.class));
suite.addTest(new JUnit4TestAdapter(TimezoneCachingTest.class));
suite.addTest(new JUnit4TestAdapter(ParserTest.class));
suite.addTest(new JUnit4TestAdapter(ReturningParserTest.class));
// PreparedStatement
suite.addTest(new JUnit4TestAdapter(PreparedStatementTest.class));
@@ -21,7 +21,9 @@
@RunWith(Parameterized.class)
public class QuotationTest extends BaseTest4 {
private enum QuoteStyle {
SIMPLE("'"), DOLLAR_NOTAG("$$"), DOLLAR_A("$a$"), DOLLAR_DEF("$DEF$");
SIMPLE("'"), DOLLAR_NOTAG("$$"), DOLLAR_A("$a$"), DOLLAR_DEF("$DEF$"),
SMILING_FACE("$o‿o$")
;
private final String quote;

0 comments on commit 201daf1

Please sign in to comment.