Parser { public static List<NativeQuery> parseJdbcSql(String query, boolean standardConformingStrings, boolean withParameters, boolean splitStatements, boolean isBatchedReWriteConfigured, String... returningColumnNames) throws SQLException { if (!withParameters && !splitStatements && returningColumnNames != null && returningColumnNames.length == 0) { return Collections.singletonList(new NativeQuery(query, SqlCommand.createStatementTypeInfo(SqlCommandType.BLANK))); } int fragmentStart = 0; int inParen = 0; char[] aChars = query.toCharArray(); StringBuilder nativeSql = new StringBuilder(query.length() + 10); List<Integer> bindPositions = null; List<NativeQuery> nativeQueries = null; boolean isCurrentReWriteCompatible = false; boolean isValuesFound = false; int valuesBraceOpenPosition = -1; int valuesBraceClosePosition = -1; boolean valuesBraceCloseFound = false; boolean isInsertPresent = false; boolean isReturningPresent = false; boolean isReturningPresentPrev = false; SqlCommandType currentCommandType = SqlCommandType.BLANK; SqlCommandType prevCommandType = SqlCommandType.BLANK; int numberOfStatements = 0; boolean whitespaceOnly = true; int keyWordCount = 0; int keywordStart = -1; int keywordEnd = -1; for (int i = 0; i < aChars.length; ++i) { char aChar = aChars[i]; boolean isKeyWordChar = false; whitespaceOnly &= aChar == ';' || Character.isWhitespace(aChar); keywordEnd = i; switch (aChar) { case '\'': i = Parser.parseSingleQuotes(aChars, i, standardConformingStrings); break; case '"': i = Parser.parseDoubleQuotes(aChars, i); break; case '-': i = Parser.parseLineComment(aChars, i); break; case '/': i = Parser.parseBlockComment(aChars, i); break; case '$': i = Parser.parseDollarQuotes(aChars, i); break; case ')': inParen--; if (inParen == 0 && isValuesFound && !valuesBraceCloseFound) { valuesBraceClosePosition = nativeSql.length() + i - fragmentStart; } break; case '?': nativeSql.append(aChars, fragmentStart, i - fragmentStart); if (i + 1 < aChars.length && aChars[i + 1] == '?') { nativeSql.append('?'); i++; } else { if (!withParameters) { nativeSql.append('?'); } else { if (bindPositions == null) { bindPositions = new ArrayList<Integer>(); } bindPositions.add(nativeSql.length()); int bindIndex = bindPositions.size(); nativeSql.append(NativeQuery.bindName(bindIndex)); } } fragmentStart = i + 1; break; case ';': if (inParen == 0) { if (!whitespaceOnly) { numberOfStatements++; nativeSql.append(aChars, fragmentStart, i - fragmentStart); whitespaceOnly = true; } fragmentStart = i + 1; if (nativeSql.length() > 0) { if (addReturning(nativeSql, currentCommandType, returningColumnNames, isReturningPresent)) { isReturningPresent = true; } if (splitStatements) { if (nativeQueries == null) { nativeQueries = new ArrayList<NativeQuery>(); } if (!isValuesFound || !isCurrentReWriteCompatible || valuesBraceClosePosition == -1 || (bindPositions != null && valuesBraceClosePosition < bindPositions.get(bindPositions.size() - 1))) { valuesBraceOpenPosition = -1; valuesBraceClosePosition = -1; } nativeQueries.add(new NativeQuery(nativeSql.toString(), toIntArray(bindPositions), false, SqlCommand.createStatementTypeInfo( currentCommandType, isBatchedReWriteConfigured, valuesBraceOpenPosition, valuesBraceClosePosition, isReturningPresent, nativeQueries.size()))); } } prevCommandType = currentCommandType; isReturningPresentPrev = isReturningPresent; currentCommandType = SqlCommandType.BLANK; isReturningPresent = false; if (splitStatements) { if (bindPositions != null) { bindPositions.clear(); } nativeSql.setLength(0); isValuesFound = false; isCurrentReWriteCompatible = false; valuesBraceOpenPosition = -1; valuesBraceClosePosition = -1; valuesBraceCloseFound = false; } } break; default: if (keywordStart >= 0) { isKeyWordChar = isIdentifierContChar(aChar); break; } isKeyWordChar = isIdentifierStartChar(aChar); if (isKeyWordChar) { keywordStart = i; if (valuesBraceOpenPosition != -1 && inParen == 0) { valuesBraceCloseFound = true; } } break; } if (keywordStart >= 0 && (i == aChars.length - 1 || !isKeyWordChar)) { int wordLength = (isKeyWordChar ? i + 1 : keywordEnd) - keywordStart; if (currentCommandType == SqlCommandType.BLANK) { if (wordLength == 6 && parseUpdateKeyword(aChars, keywordStart)) { currentCommandType = SqlCommandType.UPDATE; } else if (wordLength == 6 && parseDeleteKeyword(aChars, keywordStart)) { currentCommandType = SqlCommandType.DELETE; } else if (wordLength == 4 && parseMoveKeyword(aChars, keywordStart)) { currentCommandType = SqlCommandType.MOVE; } else if (wordLength == 6 && parseSelectKeyword(aChars, keywordStart)) { currentCommandType = SqlCommandType.SELECT; } else if (wordLength == 4 && parseWithKeyword(aChars, keywordStart)) { currentCommandType = SqlCommandType.WITH; } else if (wordLength == 6 && parseInsertKeyword(aChars, keywordStart)) { if (!isInsertPresent && (nativeQueries == null || nativeQueries.isEmpty())) { isCurrentReWriteCompatible = keyWordCount == 0; isInsertPresent = true; currentCommandType = SqlCommandType.INSERT; } else { isCurrentReWriteCompatible = false; } } } else if (currentCommandType == SqlCommandType.WITH && inParen == 0) { SqlCommandType command = parseWithCommandType(aChars, i, keywordStart, wordLength); if (command != null) { currentCommandType = command; } } if (inParen != 0 || aChar == ')') { } else if (wordLength == 9 && parseReturningKeyword(aChars, keywordStart)) { isReturningPresent = true; } else if (wordLength == 6 && parseValuesKeyword(aChars, keywordStart)) { isValuesFound = true; } keywordStart = -1; keyWordCount++; } if (aChar == '(') { inParen++; if (inParen == 1 && isValuesFound && valuesBraceOpenPosition == -1) { valuesBraceOpenPosition = nativeSql.length() + i - fragmentStart; } } } if (!isValuesFound || !isCurrentReWriteCompatible || valuesBraceClosePosition == -1 || (bindPositions != null && valuesBraceClosePosition < bindPositions.get(bindPositions.size() - 1))) { valuesBraceOpenPosition = -1; valuesBraceClosePosition = -1; } if (fragmentStart < aChars.length && !whitespaceOnly) { nativeSql.append(aChars, fragmentStart, aChars.length - fragmentStart); } else { if (numberOfStatements > 1) { isReturningPresent = false; currentCommandType = SqlCommandType.BLANK; } else if (numberOfStatements == 1) { isReturningPresent = isReturningPresentPrev; currentCommandType = prevCommandType; } } if (nativeSql.length() == 0) { return nativeQueries != null ? nativeQueries : Collections.<NativeQuery>emptyList(); } if (addReturning(nativeSql, currentCommandType, returningColumnNames, isReturningPresent)) { isReturningPresent = true; } NativeQuery lastQuery = new NativeQuery(nativeSql.toString(), toIntArray(bindPositions), !splitStatements, SqlCommand.createStatementTypeInfo(currentCommandType, isBatchedReWriteConfigured, valuesBraceOpenPosition, valuesBraceClosePosition, isReturningPresent, (nativeQueries == null ? 0 : nativeQueries.size()))); if (nativeQueries == null) { return Collections.singletonList(lastQuery); } if (!whitespaceOnly) { nativeQueries.add(lastQuery); } return nativeQueries; }  static List<NativeQuery> parseJdbcSql(String query, boolean standardConformingStrings,
      boolean withParameters, boolean splitStatements,
      boolean isBatchedReWriteConfigured,
      String... returningColumnNames); static int parseSingleQuotes(final char[] query, int offset,
      boolean standardConformingStrings); static int parseDoubleQuotes(final char[] query, int offset); static int parseDollarQuotes(final char[] query, int offset); static int parseLineComment(final char[] query, int offset); static int parseBlockComment(final char[] query, int offset); static boolean parseDeleteKeyword(final char[] query, int offset); static boolean parseInsertKeyword(final char[] query, int offset); static boolean parseMoveKeyword(final char[] query, int offset); static boolean parseReturningKeyword(final char[] query, int offset); static boolean parseSelectKeyword(final char[] query, int offset); static boolean parseUpdateKeyword(final char[] query, int offset); static boolean parseValuesKeyword(final char[] query, int offset); static long parseLong(String s, int beginIndex, int endIndex); static boolean parseWithKeyword(final char[] query, int offset); static boolean parseAsKeyword(final char[] query, int offset); static boolean isDigitAt(String s, int pos); static int digitAt(String s, int pos); static boolean isSpace(char c); static boolean isArrayWhiteSpace(char c); static boolean isOperatorChar(char c); static boolean isIdentifierStartChar(char c); static boolean isIdentifierContChar(char c); static boolean charTerminatesIdentifier(char c); static boolean isDollarQuoteStartChar(char c); static boolean isDollarQuoteContChar(char c); static JdbcCallParseInfo modifyJdbcCall(String jdbcSql, boolean stdStrings,
      int serverVersion, int protocolVersion, EscapeSyntaxCallMode escapeSyntaxCallMode); static String replaceProcessing(String sql, boolean replaceProcessingEnabled,
      boolean standardConformingStrings);  }
@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); 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); boolean returningKeywordPresent = qry.get(0).command.isReturningKeywordPresent(); Assert.assertTrue("Query has a returning clause " + query, returningKeywordPresent); }
@Test public void insertReturningInWith() throws SQLException { String query = "with x as (insert into mytab(x) values(1) returning x) insert test(id, name) select 1, 'value' from test2"; List<NativeQuery> qry = Parser.parseJdbcSql( query, true, true, true, true); boolean returningKeywordPresent = qry.get(0).command.isReturningKeywordPresent(); Assert.assertFalse("There's no top-level <<returning>> clause " + query, returningKeywordPresent); }
@Test public void insertBatchedReWriteOnConflict() throws SQLException { String query = "insert into test(id, name) values (:id,:name) ON CONFLICT (id) DO NOTHING"; List<NativeQuery> qry = Parser.parseJdbcSql(query, true, true, true, true); SqlCommand command = qry.get(0).getCommand(); Assert.assertEquals(34, command.getBatchRewriteValuesBraceOpenPosition()); Assert.assertEquals(44, command.getBatchRewriteValuesBraceClosePosition()); }
@Test public void insertBatchedReWriteOnConflictUpdateBind() throws SQLException { String query = "insert into test(id, name) values (?,?) ON CONFLICT (id) UPDATE SET name=?"; List<NativeQuery> qry = Parser.parseJdbcSql(query, true, true, true, true); SqlCommand command = qry.get(0).getCommand(); Assert.assertFalse("update set name=? is NOT compatible with insert rewrite", command.isBatchedReWriteCompatible()); }
@Test public void insertBatchedReWriteOnConflictUpdateConstant() throws SQLException { String query = "insert into test(id, name) values (?,?) ON CONFLICT (id) UPDATE SET name='default'"; List<NativeQuery> qry = Parser.parseJdbcSql(query, true, true, true, true); SqlCommand command = qry.get(0).getCommand(); Assert.assertTrue("update set name='default' is compatible with insert rewrite", command.isBatchedReWriteCompatible()); }
@Test public void insertMultiInsert() throws SQLException { String query = "insert into test(id, name) values (:id,:name),(:id,:name) ON CONFLICT (id) DO NOTHING"; List<NativeQuery> qry = Parser.parseJdbcSql(query, true, true, true, true); SqlCommand command = qry.get(0).getCommand(); Assert.assertEquals(34, command.getBatchRewriteValuesBraceOpenPosition()); Assert.assertEquals(56, command.getBatchRewriteValuesBraceClosePosition()); }
@Test public void valuesTableParse() throws SQLException { String query = "insert into values_table (id, name) values (?,?)"; List<NativeQuery> qry = Parser.parseJdbcSql(query, true, true, true, true); SqlCommand command = qry.get(0).getCommand(); Assert.assertEquals(43,command.getBatchRewriteValuesBraceOpenPosition()); Assert.assertEquals(49,command.getBatchRewriteValuesBraceClosePosition()); query = "insert into table_values (id, name) values (?,?)"; qry = Parser.parseJdbcSql(query, true, true, true, true); command = qry.get(0).getCommand(); Assert.assertEquals(43,command.getBatchRewriteValuesBraceOpenPosition()); Assert.assertEquals(49,command.getBatchRewriteValuesBraceClosePosition()); }