Skip to content

Commit

Permalink
fix: split statement did not correctly parse escaped quotes (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
olavloite authored May 23, 2022
1 parent 7d0aef6 commit cfbec96
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,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,
Expand Down Expand Up @@ -135,7 +136,7 @@ protected static ImmutableList<StatementType> determineStatementTypes(
return builder.build();
}

private static ImmutableList<String> splitStatements(String sql) {
static ImmutableList<String> splitStatements(String sql) {
// First check trivial cases with only one statement.
int firstIndexOfDelimiter = sql.indexOf(STATEMENT_DELIMITER);
if (firstIndexOfDelimiter == -1) {
Expand All @@ -148,26 +149,33 @@ private static ImmutableList<String> splitStatements(String sql) {
ImmutableList.Builder<String> 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;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
Loading

0 comments on commit cfbec96

Please sign in to comment.