提交 b90b9770 authored 作者: noelgrandin's avatar noelgrandin

When the H2 parser fails to parse a SQL statement, throw a more specific exception that

contains extra information to make it possible for smart editors to do autocomplete.
Patch from Nicolas Fortin.
上级 6eb510c1
...@@ -44,6 +44,8 @@ Change Log ...@@ -44,6 +44,8 @@ Change Log
</li><li>Add a CONTAINS_UNCOMMITTED column to the SESSIONS metadata table, to allow detecting when rogue </li><li>Add a CONTAINS_UNCOMMITTED column to the SESSIONS metadata table, to allow detecting when rogue
sessions are creating large transactions. sessions are creating large transactions.
</li><li>Some small fixes to the GEOMETRY support, patches by Nicolas Fortin. </li><li>Some small fixes to the GEOMETRY support, patches by Nicolas Fortin.
</li><li>When the H2 parser fails to parse a SQL statement, throw a more specific exception that
contains extra information to make it possible for smart editors to do autocomplete. Patch from Nicolas Fortin.
</li></ul> </li></ul>
<h2>Version 1.3.173 (2013-07-28)</h2> <h2>Version 1.3.173 (2013-07-28)</h2>
......
/*
* Copyright 2004-2013 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.api;
import java.util.List;
import org.h2.jdbc.JdbcSQLException;
/**
* A JDBC SQL Exception with additional parameters, it provides the syntax error
* position and expected token.
* <p>
* When the H2 parser encounters an error, it normally throws one of these
* exceptions. Clients may use this information to implement things like
* autocomplete in editors, or just better display of errors.
*
* @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
*/
public class JdbcParseSQLException extends JdbcSQLException {
private final List<String> expectedTokens;
private final int syntaxErrorPosition;
/**
* Creates a JdbcParseSQLException.
*
* @param message the reason
* @param sql the SQL statement
* @param state the SQL state
* @param errorCode the error code
* @param cause the exception that was the reason for this exception
* @param stackTrace the stack trace
* @param expectedTokens H2 parser expected tokens
* @param syntaxErrorPosition Syntax error character index
*/
public JdbcParseSQLException(String message, String sql, String state, int errorCode, Throwable cause,
String stackTrace, List<String> expectedTokens, int syntaxErrorPosition) {
super(message, sql, state, errorCode, cause, stackTrace);
this.expectedTokens = expectedTokens;
this.syntaxErrorPosition = syntaxErrorPosition;
}
/**
* H2 parser expected tokens
*/
public List<String> getExpectedTokens() {
return expectedTokens;
}
/**
* Syntax error character position
*/
public int getSyntaxErrorPosition() {
return syntaxErrorPosition;
}
}
...@@ -499,7 +499,7 @@ public class Parser { ...@@ -499,7 +499,7 @@ public class Parser {
buff.appendExceptFirst(", "); buff.appendExceptFirst(", ");
buff.append(e); buff.append(e);
} }
return DbException.getSyntaxError(sqlCommand, parseIndex, buff.toString()); return DbException.getSyntaxError(sqlCommand, parseIndex, buff.toString(), expectedList);
} }
private Prepared parseBackup() { private Prepared parseBackup() {
......
...@@ -11,9 +11,11 @@ import java.io.IOException; ...@@ -11,9 +11,11 @@ import java.io.IOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException; import java.sql.SQLException;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Properties;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Properties;
import org.h2.api.JdbcParseSQLException;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.jdbc.JdbcSQLException; import org.h2.jdbc.JdbcSQLException;
import org.h2.util.SortedProperties; import org.h2.util.SortedProperties;
...@@ -91,7 +93,7 @@ public class DbException extends RuntimeException { ...@@ -91,7 +93,7 @@ public class DbException extends RuntimeException {
/** /**
* Get the SQLException object. * Get the SQLException object.
* *
* @return the exception * @return the exception
*/ */
public SQLException getSQLException() { public SQLException getSQLException() {
...@@ -100,7 +102,7 @@ public class DbException extends RuntimeException { ...@@ -100,7 +102,7 @@ public class DbException extends RuntimeException {
/** /**
* Get the error code. * Get the error code.
* *
* @return the error code * @return the error code
*/ */
public int getErrorCode() { public int getErrorCode() {
...@@ -110,7 +112,7 @@ public class DbException extends RuntimeException { ...@@ -110,7 +112,7 @@ public class DbException extends RuntimeException {
/** /**
* Set the SQL statement of the given exception. * Set the SQL statement of the given exception.
* This method may create a new object. * This method may create a new object.
* *
* @param sql the SQL statement * @param sql the SQL statement
* @return the exception * @return the exception
*/ */
...@@ -129,7 +131,7 @@ public class DbException extends RuntimeException { ...@@ -129,7 +131,7 @@ public class DbException extends RuntimeException {
/** /**
* Create a database exception for a specific error code. * Create a database exception for a specific error code.
* *
* @param errorCode the error code * @param errorCode the error code
* @return the exception * @return the exception
*/ */
...@@ -139,7 +141,7 @@ public class DbException extends RuntimeException { ...@@ -139,7 +141,7 @@ public class DbException extends RuntimeException {
/** /**
* Create a database exception for a specific error code. * Create a database exception for a specific error code.
* *
* @param errorCode the error code * @param errorCode the error code
* @param p1 the first parameter of the message * @param p1 the first parameter of the message
* @return the exception * @return the exception
...@@ -150,7 +152,7 @@ public class DbException extends RuntimeException { ...@@ -150,7 +152,7 @@ public class DbException extends RuntimeException {
/** /**
* Create a database exception for a specific error code. * Create a database exception for a specific error code.
* *
* @param errorCode the error code * @param errorCode the error code
* @param cause the cause of the exception * @param cause the cause of the exception
* @param params the list of parameters of the message * @param params the list of parameters of the message
...@@ -162,7 +164,7 @@ public class DbException extends RuntimeException { ...@@ -162,7 +164,7 @@ public class DbException extends RuntimeException {
/** /**
* Create a database exception for a specific error code. * Create a database exception for a specific error code.
* *
* @param errorCode the error code * @param errorCode the error code
* @param params the list of parameters of the message * @param params the list of parameters of the message
* @return the exception * @return the exception
...@@ -173,7 +175,7 @@ public class DbException extends RuntimeException { ...@@ -173,7 +175,7 @@ public class DbException extends RuntimeException {
/** /**
* Create a syntax error exception. * Create a syntax error exception.
* *
* @param sql the SQL statement * @param sql the SQL statement
* @param index the position of the error in the SQL statement * @param index the position of the error in the SQL statement
* @return the exception * @return the exception
...@@ -185,20 +187,32 @@ public class DbException extends RuntimeException { ...@@ -185,20 +187,32 @@ public class DbException extends RuntimeException {
/** /**
* Create a syntax error exception. * Create a syntax error exception.
* *
* @param sql the SQL statement * @param sql the SQL statement
* @param index the position of the error in the SQL statement * @param index the position of the error in the SQL statement
* @param expected the expected keyword at the given position * @param expected the expected keyword at the given position
* @return the exception * @return the exception
*/ */
public static DbException getSyntaxError(String sql, int index, String expected) { public static DbException getSyntaxError(String sql, int index, String expected) {
return getSyntaxError(sql, index, expected, java.util.Arrays.asList(expected));
}
/**
* Create a syntax error exception.
*
* @param sql the SQL statement
* @param index the position of the error in the SQL statement
* @param expected the expected keyword(s) at the given position
* @return the exception
*/
public static DbException getSyntaxError(String sql, int index, String message, List<String> expected) {
sql = StringUtils.addAsterisk(sql, index); sql = StringUtils.addAsterisk(sql, index);
return get(ErrorCode.SYNTAX_ERROR_2, sql, expected); return new DbException(getParseJdbcSQLException(ErrorCode.SYNTAX_ERROR_2, null, index, expected, sql, message));
} }
/** /**
* Gets a SQL exception meaning this feature is not supported. * Gets a SQL exception meaning this feature is not supported.
* *
* @param message what exactly is not supported * @param message what exactly is not supported
* @return the exception * @return the exception
*/ */
...@@ -208,7 +222,7 @@ public class DbException extends RuntimeException { ...@@ -208,7 +222,7 @@ public class DbException extends RuntimeException {
/** /**
* Gets a SQL exception meaning this value is invalid. * Gets a SQL exception meaning this value is invalid.
* *
* @param param the name of the parameter * @param param the name of the parameter
* @param value the value passed * @param value the value passed
* @return the IllegalArgumentException object * @return the IllegalArgumentException object
...@@ -221,7 +235,7 @@ public class DbException extends RuntimeException { ...@@ -221,7 +235,7 @@ public class DbException extends RuntimeException {
* Throw an internal error. This method seems to return an exception object, * Throw an internal error. This method seems to return an exception object,
* so that it can be used instead of 'return', but in fact it always throws * so that it can be used instead of 'return', but in fact it always throws
* the exception. * the exception.
* *
* @param s the message * @param s the message
* @return the RuntimeException object * @return the RuntimeException object
* @throws RuntimeException the exception * @throws RuntimeException the exception
...@@ -236,7 +250,7 @@ public class DbException extends RuntimeException { ...@@ -236,7 +250,7 @@ public class DbException extends RuntimeException {
* Throw an internal error. This method seems to return an exception object, * Throw an internal error. This method seems to return an exception object,
* so that it can be used instead of 'return', but in fact it always throws * so that it can be used instead of 'return', but in fact it always throws
* the exception. * the exception.
* *
* @return the RuntimeException object * @return the RuntimeException object
*/ */
public static RuntimeException throwInternalError() { public static RuntimeException throwInternalError() {
...@@ -245,7 +259,7 @@ public class DbException extends RuntimeException { ...@@ -245,7 +259,7 @@ public class DbException extends RuntimeException {
/** /**
* Convert an exception to a SQL exception using the default mapping. * Convert an exception to a SQL exception using the default mapping.
* *
* @param e the root cause * @param e the root cause
* @return the SQL exception object * @return the SQL exception object
*/ */
...@@ -260,7 +274,7 @@ public class DbException extends RuntimeException { ...@@ -260,7 +274,7 @@ public class DbException extends RuntimeException {
* Convert a throwable to an SQL exception using the default mapping. All * Convert a throwable to an SQL exception using the default mapping. All
* errors except the following are re-thrown: StackOverflowError, * errors except the following are re-thrown: StackOverflowError,
* LinkageError. * LinkageError.
* *
* @param e the root cause * @param e the root cause
* @return the exception object * @return the exception object
*/ */
...@@ -285,7 +299,7 @@ public class DbException extends RuntimeException { ...@@ -285,7 +299,7 @@ public class DbException extends RuntimeException {
/** /**
* Convert an InvocationTarget exception to a database exception. * Convert an InvocationTarget exception to a database exception.
* *
* @param te the root cause * @param te the root cause
* @param message the added message or null * @param message the added message or null
* @return the database exception object * @return the database exception object
...@@ -301,7 +315,7 @@ public class DbException extends RuntimeException { ...@@ -301,7 +315,7 @@ public class DbException extends RuntimeException {
/** /**
* Convert an IO exception to a database exception. * Convert an IO exception to a database exception.
* *
* @param e the root cause * @param e the root cause
* @param message the message or null * @param message the message or null
* @return the database exception object * @return the database exception object
...@@ -319,21 +333,41 @@ public class DbException extends RuntimeException { ...@@ -319,21 +333,41 @@ public class DbException extends RuntimeException {
/** /**
* Gets the SQL exception object for a specific error code. * Gets the SQL exception object for a specific error code.
* *
* @param errorCode the error code * @param errorCode the error code
* @param cause the cause of the exception * @param cause the cause of the exception
* @param params the list of parameters of the message * @param params the list of parameters of the message
* @return the SQLException object * @return the SQLException object
*/ */
private static JdbcSQLException getJdbcSQLException(int errorCode, Throwable cause, String... params) { private static JdbcSQLException getJdbcSQLException(int errorCode, Throwable cause, String... params) {
return getParseJdbcSQLException(errorCode, cause, 0, null, params);
}
/**
* Gets the SQL exception object for a specific error code.
*
* @param errorCode the error code
* @param cause the cause of the exception
* @param syntaxErrorPosition Syntax error character index
* @param expectedTokens H2 parser expected tokens
* @param params the list of parameters of the message
* @return the SQLException object
*/
private static JdbcSQLException getParseJdbcSQLException(int errorCode, Throwable cause, int syntaxErrorPosition,
List<String> expectedTokens, String... params) {
String sqlstate = ErrorCode.getState(errorCode); String sqlstate = ErrorCode.getState(errorCode);
String message = translate(sqlstate, params); String message = translate(sqlstate, params);
return new JdbcSQLException(message, null, sqlstate, errorCode, cause, null); if (expectedTokens == null) {
return new JdbcSQLException(message, null, sqlstate, errorCode, cause, null);
} else {
return new JdbcParseSQLException(message, null, sqlstate, errorCode, cause, null, expectedTokens,
syntaxErrorPosition);
}
} }
/** /**
* Convert an exception to an IO exception. * Convert an exception to an IO exception.
* *
* @param e the root cause * @param e the root cause
* @return the IO exception * @return the IO exception
*/ */
......
...@@ -26,6 +26,7 @@ import java.util.GregorianCalendar; ...@@ -26,6 +26,7 @@ import java.util.GregorianCalendar;
import java.util.SimpleTimeZone; import java.util.SimpleTimeZone;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.UUID; import java.util.UUID;
import org.h2.api.JdbcParseSQLException;
import org.h2.api.Trigger; import org.h2.api.Trigger;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.test.TestBase; import org.h2.test.TestBase;
...@@ -86,6 +87,7 @@ public class TestPreparedStatement extends TestBase { ...@@ -86,6 +87,7 @@ public class TestPreparedStatement extends TestBase {
testBlob(conn); testBlob(conn);
testClob(conn); testClob(conn);
testParameterMetaData(conn); testParameterMetaData(conn);
testParseException(conn);
conn.close(); conn.close();
deleteDb("preparedStatement"); deleteDb("preparedStatement");
} }
...@@ -1182,4 +1184,26 @@ public class TestPreparedStatement extends TestBase { ...@@ -1182,4 +1184,26 @@ public class TestPreparedStatement extends TestBase {
assertTrue(!rs.next()); assertTrue(!rs.next());
} }
private void testParseException(Connection conn) {
try {
conn.prepareStatement("SELECT * FROM");
fail();
} catch (JdbcParseSQLException ex) {
assertEquals(14, ex.getSyntaxErrorPosition());
assertEquals(new Object[] { "identifier" }, ex.getExpectedTokens().toArray());
} catch (SQLException ex) {
fail();
}
try {
conn.prepareStatement("ALTER");
fail();
} catch (JdbcParseSQLException ex) {
assertEquals(6, ex.getSyntaxErrorPosition());
assertEquals(new Object[] { "TABLE", "USER", "INDEX", "SCHEMA", "SEQUENCE", "VIEW" }, ex
.getExpectedTokens().toArray());
} catch (SQLException ex) {
fail();
}
}
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论