Permalink
Browse files

feat: display error position when SQL has unterminated literals, comm…

…ents, etc

pgjdbc does internal SQL parsing, and it used to fail with ArrayIndexOutOfBounds exceptions
or alike when SQL was not valid. This commit makes those exceptions nicer.

fixes #688
  • Loading branch information...
vlsi committed Nov 14, 2016
1 parent 14e64be commit 8a95d991e2032cf0406be8e54e70cfafaad794b5
@@ -1018,23 +1018,31 @@ private static int parseSql(char[] p_sql, int i, StringBuilder newsql, boolean s
if (c == '$') {
int i0 = i;
i = parseDollarQuotes(p_sql, i);
checkParsePosition(i, len, i0, p_sql,
"Unterminated dollar quote started at position {0} in SQL {1}. Expected terminating $$");
newsql.append(p_sql, i0, i - i0 + 1);
break;
} else if (c == '\'') {
// start of a string?
int i0 = i;
i = parseSingleQuotes(p_sql, i, stdStrings);
checkParsePosition(i, len, i0, p_sql,
"Unterminated string literal started at position {0} in SQL {1}. Expected ' char");
newsql.append(p_sql, i0, i - i0 + 1);
break;
} else if (c == '"') {
// start of a identifier?
int i0 = i;
i = parseDoubleQuotes(p_sql, i);
checkParsePosition(i, len, i0, p_sql,
"Unterminated identifier started at position {0} in SQL {1}. Expected \" char");
newsql.append(p_sql, i0, i - i0 + 1);
break;
} else if (c == '/') {
int i0 = i;
i = parseBlockComment(p_sql, i);
checkParsePosition(i, len, i0, p_sql,
"Unterminated block comment started at position {0} in SQL {1}. Expected */ sequence");
newsql.append(p_sql, i0, i - i0 + 1);
break;
} else if (c == '-') {
@@ -1114,6 +1122,17 @@ private static int parseSql(char[] p_sql, int i, StringBuilder newsql, boolean s
return i;
}
private static void checkParsePosition(int i, int len, int i0, char[] p_sql,
String message)
throws PSQLException {
if (i < len) {
return;
}
throw new PSQLException(
GT.tr(message, i0, new String(p_sql)),
PSQLState.SYNTAX_ERROR);
}
/**
* generate sql for escaped functions
*
@@ -7,8 +7,10 @@
import org.postgresql.jdbc.PgStatement;
import org.postgresql.test.TestUtil;
import org.postgresql.util.PSQLState;
import junit.framework.TestCase;
import org.junit.Assert;
import java.sql.Connection;
import java.sql.PreparedStatement;
@@ -673,4 +675,43 @@ public void testJavascriptFunction() throws SQLException {
TestUtil.closeQuietly(ps);
}
}
public void testUnterminatedDollarQuotes() throws SQLException {
ensureSyntaxException("dollar quotes", "CREATE OR REPLACE FUNCTION update_on_change() RETURNS TRIGGER AS $$\n"
+ "BEGIN");
}
public void testUnterminatedNamedDollarQuotes() throws SQLException {
ensureSyntaxException("dollar quotes", "CREATE OR REPLACE FUNCTION update_on_change() RETURNS TRIGGER AS $ABC$\n"
+ "BEGIN");
}
public void testUnterminatedComment() throws SQLException {
ensureSyntaxException("block comment", "CREATE OR REPLACE FUNCTION update_on_change() RETURNS TRIGGER AS /* $$\n"
+ "BEGIN $$");
}
public void testUnterminatedLiteral() throws SQLException {
ensureSyntaxException("string literal", "CREATE OR REPLACE FUNCTION update_on_change() 'RETURNS TRIGGER AS $$\n"
+ "BEGIN $$");
}
public void testUnterminatedIdentifier() throws SQLException {
ensureSyntaxException("string literal", "CREATE OR REPLACE FUNCTION \"update_on_change() RETURNS TRIGGER AS $$\n"
+ "BEGIN $$");
}
private void ensureSyntaxException(String errorType, String sql) throws SQLException {
PreparedStatement ps = null;
try {
ps = con.prepareStatement(sql);
ps.executeUpdate();
Assert.fail("Query with unterminated " + errorType + " should fail");
} catch (SQLException e) {
Assert.assertEquals("Query should fail with unterminated " + errorType,
PSQLState.SYNTAX_ERROR.getState(), e.getSQLState());
} finally {
TestUtil.closeQuietly(ps);
}
}
}

0 comments on commit 8a95d99

Please sign in to comment.