Skip to content

Commit d27b346

Browse files
authored
fix: require end of input when parsing a single statement (#2733)
- `Statement()` lost its `<EOF>` validation in 063d244 ("feat: return any `UnsupportedStatement` content"), which replaced `[ <ST_SEMICOLON> ] <EOF>` with `( <ST_SEMICOLON> | <EOF> )`; ever since, input holding more than one statement silently returned a truncated AST instead of failing, so callers such as PageHelper's count SQL parser reuse the full parameter mappings against a partial statement - `ST_SEMICOLON` also matches blank line runs such as `"\n\n\n"`, which makes the silent truncation reachable from ordinary formatted SQL that has no semicolon at all - restore the pre-063d2442 contract with `( <ST_SEMICOLON> )* <EOF>` in both `Statement()` branches, keeping trailing separators and comments legal while rejecting any unconsumed statement - `Statements()` (which kept its trailing `<EOF>`) and `SingleStatement()` are untouched; walking a script statement by statement stays the incremental API's job - rewrite the routine boundary test that asserted the lenient behaviour to drive the parser with `SingleStatement()`, which is how `streamStatements` consumes multi statement scripts
1 parent 87e8814 commit d27b346

3 files changed

Lines changed: 39 additions & 4 deletions

File tree

‎src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3293,13 +3293,14 @@ Statement Statement() #Statement:
32933293
ifElseStatement.setUsingSemicolonForIfStatement(true);
32943294
}
32953295
]
3296+
<EOF>
32963297
)
32973298
|
32983299
(
32993300
(stm = SingleStatement()
33003301
| stm = Block())
33013302

3302-
( <ST_SEMICOLON> | <EOF> )
3303+
( <ST_SEMICOLON> )* <EOF>
33033304
)
33043305
|
33053306
LOOKAHEAD( { stm==null && getAsBoolean(Feature.allowUnsupportedStatements) } ) stm = UnsupportedStatement()

‎src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,36 @@ void testParseEmpty() {
525525
assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse((String) null));
526526
}
527527

528+
@Test
529+
void testParseRejectsUnconsumedInput() throws Exception {
530+
// "\n\n\n" separates statements exactly like ";" does, so both inputs below hold
531+
// two statements; a single statement parse must not silently return the first only
532+
String newlineSeparated = "SELECT a FROM dual WHERE x = ?\n\n\n AND y = ?";
533+
String semicolonSeparated = "SELECT a FROM dual; SELECT b FROM dual";
534+
535+
assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(newlineSeparated));
536+
assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(semicolonSeparated));
537+
assertThrows(ParseException.class,
538+
() -> CCJSqlParserUtil.newParser(newlineSeparated).Statement());
539+
assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil
540+
.parse(new ByteArrayInputStream(
541+
newlineSeparated.getBytes(StandardCharsets.UTF_8))));
542+
assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parseAST(newlineSeparated));
543+
544+
// trailing separators and comments do not make a statement incomplete
545+
assertEquals("SELECT a FROM dual",
546+
CCJSqlParserUtil.parse("SELECT a FROM dual;").toString());
547+
assertEquals("SELECT a FROM dual",
548+
CCJSqlParserUtil.parse("SELECT a FROM dual\n\n\n").toString());
549+
assertEquals("SELECT a FROM dual",
550+
CCJSqlParserUtil.parse("SELECT a FROM dual -- done").toString());
551+
552+
// the multi statement API keeps working
553+
assertEquals(2, CCJSqlParserUtil.parseStatements(semicolonSeparated).size());
554+
assertEquals(3, CCJSqlParserUtil.parseStatements(
555+
"SELECT * FROM DUAL\n\n\nSELECT * FROM DUAL\n\n\n\nSELECT * FROM dual").size());
556+
}
557+
528558
@Test
529559
void testSingleStatementWithEmptyLines() throws JSQLParserException {
530560
String sqlStr = "update shop_info set title=?,\n"

‎src/test/java/net/sf/jsqlparser/statement/create/SqlRoutineBodyBoundaryTest.java‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,16 @@ void preservesRoutineBodyAndFollowingStatement(Dialect dialect, String sql) thro
107107
}
108108

109109
@Test
110-
void directStatementParserLeavesFollowingStatementAvailable() throws Exception {
110+
void directSingleStatementParserLeavesFollowingStatementAvailable() throws Exception {
111+
// walking a script statement by statement is SingleStatement()'s job; Statement() reads
112+
// the input as exactly one statement and rejects whatever is left over
111113
CCJSqlParser parser = parser("CREATE FUNCTION f() RETURNS int LANGUAGE SQL RETURN 1; "
112114
+ FOLLOWING + ";", Dialect.POSTGRESQL);
113-
assertThat(parser.Statement()).isInstanceOf(CreateFunction.class);
115+
assertThat(parser.SingleStatement()).isInstanceOf(CreateFunction.class);
116+
assertThat(parser.getNextToken().kind).isEqualTo(CCJSqlParserConstants.ST_SEMICOLON);
114117
assertThat(parser.getToken(1).kind).isEqualTo(CCJSqlParserConstants.K_SELECT);
115-
assertThat(parser.Statement().toString()).isEqualTo(FOLLOWING);
118+
assertThat(parser.SingleStatement().toString()).isEqualTo(FOLLOWING);
119+
assertThat(parser.getNextToken().kind).isEqualTo(CCJSqlParserConstants.ST_SEMICOLON);
116120
assertThat(parser.getToken(1).kind).isEqualTo(CCJSqlParserConstants.EOF);
117121
}
118122

0 commit comments

Comments
 (0)