Skip to content

Commit

Permalink
feat: display error position when SQL has unterminated literals, comm…
Browse files Browse the repository at this point in the history
…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 8a95d99
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 0 deletions.
19 changes: 19 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/core/Parser.java
Expand Up @@ -1018,23 +1018,31 @@ private static int parseSql(char[] p_sql, int i, StringBuilder newsql, boolean s
if (c == '$') { if (c == '$') {
int i0 = i; int i0 = i;
i = parseDollarQuotes(p_sql, 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); newsql.append(p_sql, i0, i - i0 + 1);
break; break;
} else if (c == '\'') { } else if (c == '\'') {
// start of a string? // start of a string?
int i0 = i; int i0 = i;
i = parseSingleQuotes(p_sql, i, stdStrings); 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); newsql.append(p_sql, i0, i - i0 + 1);
break; break;
} else if (c == '"') { } else if (c == '"') {
// start of a identifier? // start of a identifier?
int i0 = i; int i0 = i;
i = parseDoubleQuotes(p_sql, 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); newsql.append(p_sql, i0, i - i0 + 1);
break; break;
} else if (c == '/') { } else if (c == '/') {
int i0 = i; int i0 = i;
i = parseBlockComment(p_sql, 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); newsql.append(p_sql, i0, i - i0 + 1);
break; break;
} else if (c == '-') { } else if (c == '-') {
Expand Down Expand Up @@ -1114,6 +1122,17 @@ private static int parseSql(char[] p_sql, int i, StringBuilder newsql, boolean s
return i; 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 * generate sql for escaped functions
* *
Expand Down
41 changes: 41 additions & 0 deletions pgjdbc/src/test/java/org/postgresql/test/jdbc2/StatementTest.java
Expand Up @@ -7,8 +7,10 @@


import org.postgresql.jdbc.PgStatement; import org.postgresql.jdbc.PgStatement;
import org.postgresql.test.TestUtil; import org.postgresql.test.TestUtil;
import org.postgresql.util.PSQLState;


import junit.framework.TestCase; import junit.framework.TestCase;
import org.junit.Assert;


import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
Expand Down Expand Up @@ -673,4 +675,43 @@ public void testJavascriptFunction() throws SQLException {
TestUtil.closeQuietly(ps); 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.