From e4bf248853b090c7ac9c6b2ac7d9952ee7ef6e13 Mon Sep 17 00:00:00 2001 From: Jules Ivanic Date: Tue, 13 Oct 2026 18:19:25 +1100 Subject: [PATCH] Make dialect-specific clause words non-reserved CSV, FBV, FILE, SCRIPT and VERIFY (Exasol IMPORT/EXPORT), PREWHERE and SETTINGS (ClickHouse) and RETURNS (CREATE FUNCTION) were reserved for every dialect, so `SELECT settings, file FROM t` failed in PostgreSQL or DuckDB, where they are ordinary names. They are now non-reserved. PREWHERE and SETTINGS never start an implicit alias, so the ClickHouse clauses still follow a table name. TypeDdlName's SETTINGS special case and FILE's entry in the reserved-as-identifier list are no longer needed. --- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 32 +++++++++++-------- src/site/sphinx/keywords.rst | 16 ---------- .../sf/jsqlparser/statement/KeywordsTest.java | 19 +++++++++++ 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 64172751a..ea248fab4 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -806,7 +806,7 @@ public class CCJSqlParser extends AbstractJSqlParser { case K_IIF: case K_IGNORE: case K_IN: case K_INTERVAL: case K_LEFT: case K_LIMIT: case K_NEXTVAL: case K_OFFSET: case K_ON: case K_OPTIMIZE: case K_ORDER: case K_PROCEDURE: case K_PUBLIC: case K_QUALIFY: case K_RIGHT: - case K_FILE: case K_SET: case K_SOME: case K_START: case K_TABLES: + case K_SET: case K_SOME: case K_START: case K_TABLES: case K_TOP: case K_VALUE: case K_VALUES: return true; default: @@ -1595,6 +1595,11 @@ public class CCJSqlParser extends AbstractJSqlParser { return false; } + // ClickHouse's PREWHERE and SETTINGS clauses, not aliases + if (kind == K_PREWHERE || kind == K_SETTINGS) { + return false; + } + // For reserved keywords in alias position, skip the structural- // keyword whitelist and go directly to follower disambiguation. // Only check keywords that are actually in RelObjectName's token @@ -2564,6 +2569,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -2623,6 +2629,8 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= + | tk= | tk= | tk= | tk= @@ -2762,6 +2770,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -2780,6 +2789,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -2811,6 +2821,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -2819,6 +2830,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -2881,6 +2893,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -2931,7 +2944,6 @@ TOKEN: /* Reserved SQL Keywords and structural tokens */ | | | -| | | | @@ -2943,9 +2955,7 @@ TOKEN: /* Reserved SQL Keywords and structural tokens */ | /* Salesforce SOQL */ | | -| | -| | | | @@ -2992,20 +3002,16 @@ TOKEN: /* Reserved SQL Keywords and structural tokens */ | | | -| | | | | -| | | | -| | | | -| | | | @@ -3025,7 +3031,6 @@ TOKEN: /* Reserved SQL Keywords and structural tokens */ | | | -| | | | @@ -6934,7 +6939,7 @@ String RelObjectName() : | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= - | tk= | tk= | tk= | tk= + | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= ) ) @@ -20465,11 +20470,10 @@ Synonym Synonym() #Synonym : String TypeDdlName(): -{ String name; String part; Token keyword; } +{ String name; String part; } { - ( name=RelObjectName() | keyword= { name = keyword.image; } ) - ( LOOKAHEAD(2) "." ( part=RelObjectNameExt() | keyword= { part = keyword.image; } ) - { name += "." + part; } )* + name=RelObjectName() + ( LOOKAHEAD(2) "." part=RelObjectNameExt() { name += "." + part; } )* { return name; } } diff --git a/src/site/sphinx/keywords.rst b/src/site/sphinx/keywords.rst index ed27b6060..473148b23 100644 --- a/src/site/sphinx/keywords.rst +++ b/src/site/sphinx/keywords.rst @@ -37,8 +37,6 @@ The following Keywords are **reserved** in JSQLParser-|JSQLPARSER_VERSION| and m +---------------------------+ | CROSS | +---------------------------+ -| CSV | -+---------------------------+ | CURRENT | +---------------------------+ | DISTINCT | @@ -57,12 +55,8 @@ The following Keywords are **reserved** in JSQLParser-|JSQLPARSER_VERSION| and m +---------------------------+ | FALSE | +---------------------------+ -| FBV | -+---------------------------+ | FETCH | +---------------------------+ -| FILE | -+---------------------------+ | FINAL | +---------------------------+ | FOR | @@ -143,8 +137,6 @@ The following Keywords are **reserved** in JSQLParser-|JSQLPARSER_VERSION| and m +---------------------------+ | PREFERRING | +---------------------------+ -| PREWHERE | -+---------------------------+ | PRIOR | +---------------------------+ | PROCEDURE | @@ -155,18 +147,12 @@ The following Keywords are **reserved** in JSQLParser-|JSQLPARSER_VERSION| and m +---------------------------+ | RETURNING | +---------------------------+ -| RETURNS | -+---------------------------+ | RIGHT | +---------------------------+ | SAMPLE | +---------------------------+ -| SCRIPT | -+---------------------------+ | SET | +---------------------------+ -| SETTINGS | -+---------------------------+ | SOME | +---------------------------+ | SQL_CACHE | @@ -209,8 +195,6 @@ The following Keywords are **reserved** in JSQLParser-|JSQLPARSER_VERSION| and m +---------------------------+ | VALUES | +---------------------------+ -| VERIFY | -+---------------------------+ | WHEN | +---------------------------+ | WHERE | diff --git a/src/test/java/net/sf/jsqlparser/statement/KeywordsTest.java b/src/test/java/net/sf/jsqlparser/statement/KeywordsTest.java index c349e92c9..0e29f2e3b 100644 --- a/src/test/java/net/sf/jsqlparser/statement/KeywordsTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/KeywordsTest.java @@ -72,4 +72,23 @@ public void testErrorsAsIdentifier() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("WITH errors AS (SELECT 1) SELECT * FROM errors", true); assertSqlCanBeParsedAndDeparsed("CREATE TABLE mytable (errors VARCHAR)", true); } + + // Dialect-specific clause words (Exasol IMPORT/EXPORT, ClickHouse PREWHERE/SETTINGS, CREATE + // FUNCTION ... RETURNS) are ordinary names in other dialects, e.g. PostgreSQL or DuckDB. + @Test + public void testDialectClauseWordsAsIdentifiers() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "SELECT csv, fbv, file, prewhere, returns, script, settings, verify FROM mytable", + true); + assertSqlCanBeParsedAndDeparsed("SELECT t.file, t.settings FROM mytable t", true); + assertSqlCanBeParsedAndDeparsed("SELECT * FROM settings", true); + assertSqlCanBeParsedAndDeparsed("WITH file AS (SELECT 1) SELECT * FROM file", true); + assertSqlCanBeParsedAndDeparsed("CREATE TABLE mytable (csv VARCHAR, settings JSON)", true); + } + + @Test + public void testClickHouseClausesAfterTable() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT a FROM mytable PREWHERE b = 1 WHERE c = 2", true); + assertSqlCanBeParsedAndDeparsed("SELECT a FROM mytable SETTINGS max_threads = 8", true); + } }