diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 3076fb241..db7e3a7a9 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -3269,13 +3269,14 @@ Statement Statement() #Statement: ifElseStatement.setUsingSemicolonForIfStatement(true); } ] + ) | ( (stm = SingleStatement() | stm = Block()) - ( | ) + ( )* ) | LOOKAHEAD( { stm==null && getAsBoolean(Feature.allowUnsupportedStatements) } ) stm = UnsupportedStatement() diff --git a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java index 6de42eb6a..a1686ff2d 100644 --- a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java +++ b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java @@ -525,6 +525,36 @@ void testParseEmpty() { assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse((String) null)); } + @Test + void testParseRejectsUnconsumedInput() throws Exception { + // "\n\n\n" separates statements exactly like ";" does, so both inputs below hold + // two statements; a single statement parse must not silently return the first only + String newlineSeparated = "SELECT a FROM dual WHERE x = ?\n\n\n AND y = ?"; + String semicolonSeparated = "SELECT a FROM dual; SELECT b FROM dual"; + + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(newlineSeparated)); + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(semicolonSeparated)); + assertThrows(ParseException.class, + () -> CCJSqlParserUtil.newParser(newlineSeparated).Statement()); + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil + .parse(new ByteArrayInputStream( + newlineSeparated.getBytes(StandardCharsets.UTF_8)))); + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parseAST(newlineSeparated)); + + // trailing separators and comments do not make a statement incomplete + assertEquals("SELECT a FROM dual", + CCJSqlParserUtil.parse("SELECT a FROM dual;").toString()); + assertEquals("SELECT a FROM dual", + CCJSqlParserUtil.parse("SELECT a FROM dual\n\n\n").toString()); + assertEquals("SELECT a FROM dual", + CCJSqlParserUtil.parse("SELECT a FROM dual -- done").toString()); + + // the multi statement API keeps working + assertEquals(2, CCJSqlParserUtil.parseStatements(semicolonSeparated).size()); + assertEquals(3, CCJSqlParserUtil.parseStatements( + "SELECT * FROM DUAL\n\n\nSELECT * FROM DUAL\n\n\n\nSELECT * FROM dual").size()); + } + @Test void testSingleStatementWithEmptyLines() throws JSQLParserException { String sqlStr = "update shop_info set title=?,\n" diff --git a/src/test/java/net/sf/jsqlparser/statement/create/SqlRoutineBodyBoundaryTest.java b/src/test/java/net/sf/jsqlparser/statement/create/SqlRoutineBodyBoundaryTest.java index a6a36650f..1461d6489 100644 --- a/src/test/java/net/sf/jsqlparser/statement/create/SqlRoutineBodyBoundaryTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/create/SqlRoutineBodyBoundaryTest.java @@ -107,12 +107,16 @@ void preservesRoutineBodyAndFollowingStatement(Dialect dialect, String sql) thro } @Test - void directStatementParserLeavesFollowingStatementAvailable() throws Exception { + void directSingleStatementParserLeavesFollowingStatementAvailable() throws Exception { + // walking a script statement by statement is SingleStatement()'s job; Statement() reads + // the input as exactly one statement and rejects whatever is left over CCJSqlParser parser = parser("CREATE FUNCTION f() RETURNS int LANGUAGE SQL RETURN 1; " + FOLLOWING + ";", Dialect.POSTGRESQL); - assertThat(parser.Statement()).isInstanceOf(CreateFunction.class); + assertThat(parser.SingleStatement()).isInstanceOf(CreateFunction.class); + assertThat(parser.getNextToken().kind).isEqualTo(CCJSqlParserConstants.ST_SEMICOLON); assertThat(parser.getToken(1).kind).isEqualTo(CCJSqlParserConstants.K_SELECT); - assertThat(parser.Statement().toString()).isEqualTo(FOLLOWING); + assertThat(parser.SingleStatement().toString()).isEqualTo(FOLLOWING); + assertThat(parser.getNextToken().kind).isEqualTo(CCJSqlParserConstants.ST_SEMICOLON); assertThat(parser.getToken(1).kind).isEqualTo(CCJSqlParserConstants.EOF); }