in src/main/java/com/amazon/redshift/core/Parser.java [48:373]
public static List<NativeQuery> parseJdbcSql(String query, boolean standardConformingStrings,
boolean withParameters, boolean splitStatements,
boolean isBatchedReWriteConfigured,
boolean isMultiSqlSupport,
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; // initialized on demand
Set<String> redshiftBindNames = null; // initialized on demand
List<Integer> redshiftParamMarkers = null; // initialized on demand
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;
boolean jdbcParameterMarker = false;
boolean redshiftParameterMarker = false;
for (int i = 0; i < aChars.length; ++i) {
char aChar = aChars[i];
boolean isKeyWordChar = false;
// ';' is ignored as it splits the queries
whitespaceOnly &= aChar == ';' || Character.isWhitespace(aChar);
keywordEnd = i; // parseSingleQuotes, parseDoubleQuotes, etc move index so we keep old value
switch (aChar) {
case '\'': // single-quotes
i = Parser.parseSingleQuotes(aChars, i, standardConformingStrings);
break;
case '"': // double-quotes
i = Parser.parseDoubleQuotes(aChars, i);
break;
case '-': // possibly -- style comment
i = Parser.parseLineComment(aChars, i);
break;
case '/': // possibly /* */ style comment
i = Parser.parseBlockComment(aChars, i);
break;
case '$': // possibly dollar quote start
int savPos = i;
i = Parser.parseDollarQuotes(aChars, i);
// PREPARE SQL command has own way of replacing $ marker.
// Those are not JDBC Bind values but values pass during EXECUTE SQL call.
// Also check for whether it is part of an identifier or not.
if (savPos == i
&& withParameters
&& currentCommandType != SqlCommandType.PREPARE
&& keywordStart == -1) {
i = Parser.parseDollarParam(aChars, i);
if (i != savPos) {
if(jdbcParameterMarker) {
// Throw an exception, if application uses $ and ? both as parameter marker.
throw new RedshiftException(GT.tr("Redshift parameter marker and JDBC parameter marker in same SQL command is not allowed."),
RedshiftState.UNEXPECTED_ERROR);
}
redshiftParameterMarker = true;
// Get $ and all digits
String paramName = new String(aChars, savPos, i - savPos + 1);
nativeSql.append(aChars, fragmentStart, (i + 1) - fragmentStart);
fragmentStart = i + 1; // Point at after the last digit
// We found $n parameter marker
if (redshiftBindNames == null) {
redshiftBindNames = new HashSet<String>();
}
if (bindPositions == null) {
bindPositions = new ArrayList<Integer>();
}
if (redshiftParamMarkers == null) {
redshiftParamMarkers = new ArrayList<Integer>();
}
// is it unique?
if (!redshiftBindNames.contains(paramName)) {
redshiftBindNames.add(paramName);
int dollarSignPos = nativeSql.length() - (i - savPos) - 1;
bindPositions.add(dollarSignPos); // Point at $
redshiftParamMarkers.add(Integer.parseInt(paramName.substring(1)));
}
}
}
break;
// case '(' moved below to parse "values(" properly
case ')':
inParen--;
if (inParen == 0 && isValuesFound && !valuesBraceCloseFound) {
// If original statement is multi-values like VALUES (...), (...), ... then
// search for the latest closing paren
valuesBraceClosePosition = nativeSql.length() + i - fragmentStart;
}
break;
case '?':
nativeSql.append(aChars, fragmentStart, i - fragmentStart);
if (i + 1 < aChars.length && aChars[i + 1] == '?') /* replace ?? with ? */ {
nativeSql.append('?');
i++; // make sure the coming ? is not treated as a bind
} else {
if (!withParameters) {
nativeSql.append('?');
} else {
if(redshiftParameterMarker) {
// Throw an exception, if application uses $ and ? both as parameter marker.
throw new RedshiftException(GT.tr("Redshift parameter marker and JDBC parameter marker in same SQL command is not allowed."),
RedshiftState.UNEXPECTED_ERROR);
}
jdbcParameterMarker = true;
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);
nativeSql.append(';');
whitespaceOnly = true;
}
fragmentStart = i + 1;
if (nativeSql.length() > 0) {
if (addReturning(nativeSql, currentCommandType, returningColumnNames, isReturningPresent)) {
isReturningPresent = true;
}
if(!isMultiSqlSupport) {
// Throw an exception, if application doesn't need multiple SQL commands support.
throw new RedshiftException(GT.tr("Multiple SQL commands support is disabled."),
RedshiftState.UNEXPECTED_ERROR);
}
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()),
(redshiftParamMarkers != null) ? toIntArray(redshiftParamMarkers) : null));
}
}
prevCommandType = currentCommandType;
isReturningPresentPrev = isReturningPresent;
currentCommandType = SqlCommandType.BLANK;
isReturningPresent = false;
if (splitStatements) {
// Prepare for next query
if (bindPositions != null) {
bindPositions.clear();
}
nativeSql.setLength(0);
isValuesFound = false;
isCurrentReWriteCompatible = false;
valuesBraceOpenPosition = -1;
valuesBraceClosePosition = -1;
valuesBraceCloseFound = false;
}
}
break;
default:
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;
if (valuesBraceOpenPosition != -1 && inParen == 0) {
// When the statement already has multi-values, stop looking for more of them
// Since values(?,?),(?,?),... should not contain keywords in the middle
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())) {
// Only allow rewrite for insert command starting with the insert keyword.
// Else, too many risks of wrong interpretation.
isCurrentReWriteCompatible = keyWordCount == 0;
isInsertPresent = true;
currentCommandType = SqlCommandType.INSERT;
} else {
isCurrentReWriteCompatible = false;
}
}
else if (wordLength == 7 && parsePrepareKeyword(aChars, keywordStart)) {
currentCommandType = SqlCommandType.PREPARE;
}
} else if (currentCommandType == SqlCommandType.WITH
&& inParen == 0) {
SqlCommandType command = parseWithCommandType(aChars, i, keywordStart, wordLength);
if (command != null) {
currentCommandType = command;
}
}
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;
}
keywordStart = -1;
keyWordCount++;
}
if (aChar == '(') {
inParen++;
if (inParen == 1 && isValuesFound && valuesBraceOpenPosition == -1) {
valuesBraceOpenPosition = nativeSql.length() + i - fragmentStart;
}
}
} // Loop for each char
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())),
(redshiftParamMarkers != null) ? toIntArray(redshiftParamMarkers) : null);
if (nativeQueries == null) {
return Collections.singletonList(lastQuery);
}
if (!whitespaceOnly) {
nativeQueries.add(lastQuery);
}
return nativeQueries;
}