From ab8f39496c330a0d4e370abce9c6394b728f6291 Mon Sep 17 00:00:00 2001 From: Ellie Eadington Date: Tue, 13 Oct 2026 09:12:53 +0100 Subject: [PATCH 1/2] Add an option to stop treating blank lines as a statement separator Three consecutive newlines end a statement, like `;`. Add Feature.allowBlankLinesAsStatementSeparator (enabled by default) and withBlankLinesAsSeparator(boolean) so callers can parse blank lines as plain whitespace instead. Default behaviour is unchanged. Co-authored-by: Claude Opus 5.5 --- .../jsqlparser/parser/AbstractJSqlParser.java | 5 +++ .../jsqlparser/parser/SimpleCharStream.java | 8 ++++ .../sf/jsqlparser/parser/feature/Feature.java | 7 ++++ .../parser/CCJSqlParserUtilTest.java | 39 +++++++++++++++++++ 4 files changed, 59 insertions(+) diff --git a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java index 9a1b4100b..c84ca4a85 100644 --- a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java +++ b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java @@ -194,6 +194,11 @@ public P withHashLineComments(boolean allowHashLineComments) { return withFeature(Feature.allowHashLineComments, allowHashLineComments); } + public P withBlankLinesAsSeparator(boolean allowBlankLinesAsStatementSeparator) { + return withFeature(Feature.allowBlankLinesAsStatementSeparator, + allowBlankLinesAsStatementSeparator); + } + public P withUnparenthesizedSubSelects() { return withFeature(Feature.allowUnparenthesizedSubSelects, true); } diff --git a/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java b/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java index 0cbafed61..db87121df 100644 --- a/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java +++ b/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java @@ -208,6 +208,14 @@ public final char BeginToken() throws java.io.IOException { && AbstractJSqlParser.Dialect.MYSQL.name() .equals(featureConfiguration.getValue(Feature.dialect))) { c = mySqlCommentBoundary(c); + } else if (c == '\n' && featureConfiguration != null + && !featureConfiguration + .getAsBoolean(Feature.allowBlankLinesAsStatementSeparator) + && peekCharacters(2).equals("\n\n")) { + // Blanking the first newline of the run stops ST_SEMICOLON matching "\n\n\n"; + // line and column tracking was already updated when the newline was read. + buffer[bufpos] = ' '; + c = ' '; } return c; } diff --git a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java index 9e0bd5ce1..da970b31a 100644 --- a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java +++ b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java @@ -889,6 +889,13 @@ public enum Feature { */ allowHashLineComments(false), + /** + * treats three consecutive newlines as a statement separator, like `;`; enabled by default. + * When disabled, blank lines are plain whitespace, so a statement containing them is parsed + * whole instead of being cut off at the first run of blank lines + */ + allowBlankLinesAsStatementSeparator(true), + /** * allows sub selects without parentheses, e.g. `select * from dual where 1 = select 1` */ diff --git a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java index a1686ff2d..3cef18a92 100644 --- a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java +++ b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java @@ -41,6 +41,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -578,6 +579,44 @@ void testSingleStatementWithEmptyLines() throws JSQLParserException { + "where id=?", true); } + @Test + void testBlankLinesAsSeparatorDisabled() throws JSQLParserException { + Consumer blankLinesAsWhitespace = p -> p.withBlankLinesAsSeparator(false); + + assertEquals("SELECT a FROM dual WHERE x = ? AND y = ?", + CCJSqlParserUtil.parse("SELECT a FROM dual WHERE x = ?\n\n\n AND y = ?", + blankLinesAsWhitespace).toString()); + assertEquals("SELECT a, b FROM t", + CCJSqlParserUtil.parse("SELECT a,\n\n\n\nb FROM t", blankLinesAsWhitespace) + .toString()); + assertEquals("SELECT a FROM t WHERE a > 1", + CCJSqlParserUtil.parse("SELECT a FROM t\n\n\nWHERE a > 1", blankLinesAsWhitespace) + .toString()); + assertEquals("SELECT a FROM t UNION SELECT a FROM u", + CCJSqlParserUtil.parse("SELECT a FROM t\n\n\n\n\nUNION\n\n\nSELECT a FROM u", + blankLinesAsWhitespace).toString()); + assertEquals("SELECT a FROM t JOIN u ON t.id = u.id", + CCJSqlParserUtil.parse("SELECT a FROM t\n\n\nJOIN u ON t.id = u.id", + blankLinesAsWhitespace).toString()); + assertEquals("SELECT a FROM dual", + CCJSqlParserUtil.parse("SELECT a FROM dual\n\n\n", blankLinesAsWhitespace) + .toString()); + assertEquals("SELECT 'a\n\n\nb' FROM dual", + CCJSqlParserUtil.parse("SELECT 'a\n\n\nb' FROM dual", blankLinesAsWhitespace) + .toString()); + + assertEquals(1, CCJSqlParserUtil.parseStatements( + "SELECT a FROM t\n\n\nWHERE a > 1", blankLinesAsWhitespace).size()); + assertEquals(3, CCJSqlParserUtil.parseStatements( + "SELECT 1 FROM dual;\n\n\nSELECT 2 FROM dual\ngo\nSELECT 3 FROM dual\n/\n", + blankLinesAsWhitespace).size()); + + JSQLParserException exception = assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("SELECT a\n\n\n\nFROM", blankLinesAsWhitespace)); + assertTrue(exception.getCause().getMessage().contains("at line 5, column 1"), + exception.getCause().getMessage()); + } + @Test public void testHashLineCommentsFeature() throws Exception { // with the flag: MySQL line comments, `#` to end of line, From e4c1e6018e2cd758c7d87e82adb0a1868a14b497 Mon Sep 17 00:00:00 2001 From: Ellie Eadington Date: Tue, 13 Oct 2026 09:50:23 +0100 Subject: [PATCH 2/2] Document the blank line separator option Co-authored-by: Claude Opus 5.5 --- .../java/net/sf/jsqlparser/parser/SimpleCharStream.java | 7 ++++--- .../java/net/sf/jsqlparser/parser/feature/Feature.java | 6 +++--- src/site/sphinx/usage.rst | 3 +++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java b/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java index db87121df..7530045e5 100644 --- a/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java +++ b/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java @@ -10,9 +10,10 @@ /* Generated By:JavaCC: Do not edit this line by hand. SimpleCharStream.java Version 8.0.0 */ /* * Warning: this file is maintained by hand on top of the JavaCC template. Among other manual - * adjustments it carries the in-buffer '#' rewrite in BeginToken(), which - * Feature.allowHashLineComments depends on. Never overwrite it with a freshly generated - * SimpleCharStream, that would silently drop the rewrite and break allowHashLineComments. + * adjustments it carries the in-buffer '#' and blank line rewrites in BeginToken(), which + * Feature.allowHashLineComments and Feature.allowBlankLinesAsStatementSeparator depend on. Never + * overwrite it with a freshly generated SimpleCharStream, that would silently drop the rewrites and + * break both features. */ package net.sf.jsqlparser.parser; diff --git a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java index da970b31a..0d2ada464 100644 --- a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java +++ b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java @@ -890,9 +890,9 @@ public enum Feature { allowHashLineComments(false), /** - * treats three consecutive newlines as a statement separator, like `;`; enabled by default. - * When disabled, blank lines are plain whitespace, so a statement containing them is parsed - * whole instead of being cut off at the first run of blank lines + * treats three consecutive newlines as a statement separator (enabled by default). When + * disabled, blank lines are plain whitespace, so a statement containing them is parsed whole + * instead of being cut off at the first run of blank lines */ allowBlankLinesAsStatementSeparator(true), diff --git a/src/site/sphinx/usage.rst b/src/site/sphinx/usage.rst index 1d204c6e9..c7eab4894 100644 --- a/src/site/sphinx/usage.rst +++ b/src/site/sphinx/usage.rst @@ -204,6 +204,7 @@ For several statements at once, use ``CCJSqlParserUtil.parseStatements()``, whic .. note:: Supported statement separators are semicolon ``;``, ``GO``, slash ``/`` and two empty lines ``\n\n\n``. + Use ``withBlankLinesAsSeparator(false)`` to treat empty lines as plain whitespace instead. If parsing fails on syntax JSQLParser does not know, see :ref:`Handle Parse Errors` — and please `open an issue `_, missing syntax gets added on demand. @@ -1571,6 +1572,8 @@ The individual features - ``".."`` reads as a string literal instead of a quoted identifier (BigQuery, Spark/Databricks, MySQL default ``sql_mode``) * - ``withHashLineComments`` - ``#`` starts a line comment + * - ``withBlankLinesAsSeparator`` + - ``false`` stops two empty lines (``\n\n\n``) ending a statement, so they read as plain whitespace * - ``withAdjacentStringLiterals`` - adjacent string literals concatenate: ``NEWLINE`` (SQL standard, PostgreSQL) or ``WHITESPACE`` (GoogleSQL, Spark/Databricks); ``true`` selects ``NEWLINE``, ``false`` switches it off * - ``withAllowComplexParsing``