From 41a5a265f208bf0aec177c734963290dafc68342 Mon Sep 17 00:00:00 2001 From: Jules Ivanic Date: Tue, 13 Oct 2026 17:46:19 +1100 Subject: [PATCH] Let a dialect own its reserved keywords, starting with DuckDB Reserved keywords are global: a keyword reserved for one dialect's syntax (ClickHouse's PREWHERE and SETTINGS, Exasol's FILE and SCRIPT, MySQL's XOR, ...) is rejected as a name in every dialect, and words a dialect reserves (DuckDB's CASE, TABLE, DESC, ...) are accepted as names whatever the dialect. - `KeywordPolicy`: a dialect's own rules, whether a word is an unquoted name in a `NamePosition` (an object name, a function name, a label after AS or ".", a bare alias). `Dialect#getKeywordPolicy()` is null for the default rules. - `Dialect.DUCKDB` (dollar-quoted string tags, adjacent string literals on a new line) uses `DuckDbKeywordPolicy`: DuckDB 1.5.5's 489 keywords with their categories, from `duckdb_keywords()`, and PostgreSQL's rules for them. Function names only reject reserved keywords, as DuckDB special-cases COALESCE, NULLIF, IF, ... - The grammar consults the policy in both directions: `RelObjectName()` rejects the names it rejects and `DialectUnreservedWord()` accepts the reserved-by-default keywords it accepts; `DialectLabel()` accepts any keyword after AS or "."; a select item's bare alias follows the bare-alias rule; the XOR operator, INCLUDES/EXCLUDES, EXISTS, CONNECT_BY_ROOT, PRIOR, XMLSERIALIZE and the window-frame BETWEEN/UNBOUNDED step aside when the word is a name, with explicit lookaheads (no new choice conflict). - Without a policy nothing changes. - `DuckDbKeywordPolicyTest` checks each DuckDB keyword in 7 name positions against what DuckDB 1.5.5 parses (`duckdb-keyword-names.csv`), except 32 documented cells where JSqlParser parses the word as dedicated syntax (MAP, STRUCT, TRY_CAST, COLUMNS, WITH RECURSIVE, ...) or as a date unit (`SELECT 1 day`). - `TaggedDollarStringTest` treats every dialect enabling dollar-quoted string tags, now including DuckDB, like PostgreSQL. --- src/main/java/module-info.java | 1 + .../jsqlparser/parser/AbstractJSqlParser.java | 14 +- .../parser/dialect/DuckDbKeywordPolicy.java | 550 ++++++++++++++++++ .../parser/dialect/KeywordCategory.java | 22 + .../parser/dialect/KeywordPolicy.java | 31 + .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 374 +++++++++++- .../expression/TaggedDollarStringTest.java | 5 +- .../dialect/DuckDbKeywordPolicyTest.java | 161 +++++ .../parser/dialect/duckdb-keyword-names.csv | 492 ++++++++++++++++ 9 files changed, 1629 insertions(+), 21 deletions(-) create mode 100644 src/main/java/net/sf/jsqlparser/parser/dialect/DuckDbKeywordPolicy.java create mode 100644 src/main/java/net/sf/jsqlparser/parser/dialect/KeywordCategory.java create mode 100644 src/main/java/net/sf/jsqlparser/parser/dialect/KeywordPolicy.java create mode 100644 src/test/java/net/sf/jsqlparser/parser/dialect/DuckDbKeywordPolicyTest.java create mode 100644 src/test/resources/net/sf/jsqlparser/parser/dialect/duckdb-keyword-names.csv diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 14ed418f0..2900d2bb5 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -18,6 +18,7 @@ exports net.sf.jsqlparser.expression.operators.conditional; exports net.sf.jsqlparser.expression.operators.relational; exports net.sf.jsqlparser.parser; + exports net.sf.jsqlparser.parser.dialect; exports net.sf.jsqlparser.parser.feature; exports net.sf.jsqlparser.schema; exports net.sf.jsqlparser.statement; diff --git a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java index 9a1b4100b..9a5eccafb 100644 --- a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java +++ b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java @@ -9,6 +9,8 @@ */ package net.sf.jsqlparser.parser; +import net.sf.jsqlparser.parser.dialect.DuckDbKeywordPolicy; +import net.sf.jsqlparser.parser.dialect.KeywordPolicy; import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.parser.feature.FeatureConfiguration; @@ -47,7 +49,9 @@ public enum Dialect { AdjacentStringLiterals.WHITESPACE, Feature.allowDoubleQuotedStrings, Feature.allowBackslashEscapeCharacter), SNOWFLAKE( - Feature.allowBackslashEscapeCharacter), INFORMIX, SPANNER, DORIS, COCKROACHDB, TERADATA; + Feature.allowBackslashEscapeCharacter), INFORMIX, SPANNER, DORIS, COCKROACHDB, TERADATA, DUCKDB( + AdjacentStringLiterals.NEWLINE, + Feature.allowDollarQuotedStringTags); private final Set lexerFeatures; private final AdjacentStringLiterals adjacentStringLiterals; @@ -70,6 +74,14 @@ public Set getLexerFeatures() { public AdjacentStringLiterals getAdjacentStringLiterals() { return adjacentStringLiterals; } + + /** + * The dialect's own reserved-keyword rules, or {@code null} for the default ones (see + * {@code keywords.rst}). + */ + public KeywordPolicy getKeywordPolicy() { + return this == DUCKDB ? DuckDbKeywordPolicy.INSTANCE : null; + } } public P withSquareBracketQuotation() { diff --git a/src/main/java/net/sf/jsqlparser/parser/dialect/DuckDbKeywordPolicy.java b/src/main/java/net/sf/jsqlparser/parser/dialect/DuckDbKeywordPolicy.java new file mode 100644 index 000000000..555e3b802 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/parser/dialect/DuckDbKeywordPolicy.java @@ -0,0 +1,550 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser.dialect; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * DuckDB's keywords, from DuckDB 1.5.5's + * {@code SELECT keyword_name, keyword_category FROM duckdb_keywords()}, and the PostgreSQL-style + * rules DuckDB applies to them: a column, table or alias name cannot be a reserved or type/function + * keyword, a function name cannot be a reserved keyword (DuckDB special-cases the functions named + * by column-name keywords, such as COALESCE), any keyword can follow {@code AS} or {@code .}, and a + * bare alias cannot be a keyword at all. + */ +public final class DuckDbKeywordPolicy implements KeywordPolicy { + public static final DuckDbKeywordPolicy INSTANCE = new DuckDbKeywordPolicy(); + + private static final Map KEYWORDS = new HashMap<>(); + + static { + KEYWORDS.put("ABORT", KeywordCategory.UNRESERVED); + KEYWORDS.put("ABSOLUTE", KeywordCategory.UNRESERVED); + KEYWORDS.put("ACCESS", KeywordCategory.UNRESERVED); + KEYWORDS.put("ACTION", KeywordCategory.UNRESERVED); + KEYWORDS.put("ADD", KeywordCategory.UNRESERVED); + KEYWORDS.put("ADMIN", KeywordCategory.UNRESERVED); + KEYWORDS.put("AFTER", KeywordCategory.UNRESERVED); + KEYWORDS.put("AGGREGATE", KeywordCategory.UNRESERVED); + KEYWORDS.put("ALL", KeywordCategory.RESERVED); + KEYWORDS.put("ALSO", KeywordCategory.UNRESERVED); + KEYWORDS.put("ALTER", KeywordCategory.UNRESERVED); + KEYWORDS.put("ALWAYS", KeywordCategory.UNRESERVED); + KEYWORDS.put("ANALYSE", KeywordCategory.RESERVED); + KEYWORDS.put("ANALYZE", KeywordCategory.RESERVED); + KEYWORDS.put("AND", KeywordCategory.RESERVED); + KEYWORDS.put("ANTI", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("ANY", KeywordCategory.RESERVED); + KEYWORDS.put("ARRAY", KeywordCategory.RESERVED); + KEYWORDS.put("AS", KeywordCategory.RESERVED); + KEYWORDS.put("ASC", KeywordCategory.RESERVED); + KEYWORDS.put("ASOF", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("ASSERTION", KeywordCategory.UNRESERVED); + KEYWORDS.put("ASSIGNMENT", KeywordCategory.UNRESERVED); + KEYWORDS.put("ASYMMETRIC", KeywordCategory.RESERVED); + KEYWORDS.put("AT", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("ATTACH", KeywordCategory.UNRESERVED); + KEYWORDS.put("ATTRIBUTE", KeywordCategory.UNRESERVED); + KEYWORDS.put("AUTHORIZATION", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("BACKWARD", KeywordCategory.UNRESERVED); + KEYWORDS.put("BEFORE", KeywordCategory.UNRESERVED); + KEYWORDS.put("BEGIN", KeywordCategory.UNRESERVED); + KEYWORDS.put("BETWEEN", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("BIGINT", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("BINARY", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("BIT", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("BOOLEAN", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("BOTH", KeywordCategory.RESERVED); + KEYWORDS.put("BY", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("CACHE", KeywordCategory.UNRESERVED); + KEYWORDS.put("CALL", KeywordCategory.UNRESERVED); + KEYWORDS.put("CALLED", KeywordCategory.UNRESERVED); + KEYWORDS.put("CASCADE", KeywordCategory.UNRESERVED); + KEYWORDS.put("CASCADED", KeywordCategory.UNRESERVED); + KEYWORDS.put("CASE", KeywordCategory.RESERVED); + KEYWORDS.put("CAST", KeywordCategory.RESERVED); + KEYWORDS.put("CATALOG", KeywordCategory.UNRESERVED); + KEYWORDS.put("CENTURIES", KeywordCategory.UNRESERVED); + KEYWORDS.put("CENTURY", KeywordCategory.UNRESERVED); + KEYWORDS.put("CHAIN", KeywordCategory.UNRESERVED); + KEYWORDS.put("CHAR", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("CHARACTER", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("CHARACTERISTICS", KeywordCategory.UNRESERVED); + KEYWORDS.put("CHECK", KeywordCategory.RESERVED); + KEYWORDS.put("CHECKPOINT", KeywordCategory.UNRESERVED); + KEYWORDS.put("CLASS", KeywordCategory.UNRESERVED); + KEYWORDS.put("CLOSE", KeywordCategory.UNRESERVED); + KEYWORDS.put("CLUSTER", KeywordCategory.UNRESERVED); + KEYWORDS.put("COALESCE", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("COLLATE", KeywordCategory.RESERVED); + KEYWORDS.put("COLLATION", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("COLUMN", KeywordCategory.RESERVED); + KEYWORDS.put("COLUMNS", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("COMMENT", KeywordCategory.UNRESERVED); + KEYWORDS.put("COMMENTS", KeywordCategory.UNRESERVED); + KEYWORDS.put("COMMIT", KeywordCategory.UNRESERVED); + KEYWORDS.put("COMMITTED", KeywordCategory.UNRESERVED); + KEYWORDS.put("COMPRESSION", KeywordCategory.UNRESERVED); + KEYWORDS.put("CONCURRENTLY", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("CONFIGURATION", KeywordCategory.UNRESERVED); + KEYWORDS.put("CONFLICT", KeywordCategory.UNRESERVED); + KEYWORDS.put("CONNECTION", KeywordCategory.UNRESERVED); + KEYWORDS.put("CONSTRAINT", KeywordCategory.RESERVED); + KEYWORDS.put("CONSTRAINTS", KeywordCategory.UNRESERVED); + KEYWORDS.put("CONTENT", KeywordCategory.UNRESERVED); + KEYWORDS.put("CONTINUE", KeywordCategory.UNRESERVED); + KEYWORDS.put("CONVERSION", KeywordCategory.UNRESERVED); + KEYWORDS.put("COPY", KeywordCategory.UNRESERVED); + KEYWORDS.put("COST", KeywordCategory.UNRESERVED); + KEYWORDS.put("CREATE", KeywordCategory.RESERVED); + KEYWORDS.put("CROSS", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("CSV", KeywordCategory.UNRESERVED); + KEYWORDS.put("CUBE", KeywordCategory.UNRESERVED); + KEYWORDS.put("CURRENT", KeywordCategory.UNRESERVED); + KEYWORDS.put("CURSOR", KeywordCategory.UNRESERVED); + KEYWORDS.put("CYCLE", KeywordCategory.UNRESERVED); + KEYWORDS.put("DATA", KeywordCategory.UNRESERVED); + KEYWORDS.put("DATABASE", KeywordCategory.UNRESERVED); + KEYWORDS.put("DAY", KeywordCategory.UNRESERVED); + KEYWORDS.put("DAYS", KeywordCategory.UNRESERVED); + KEYWORDS.put("DEALLOCATE", KeywordCategory.UNRESERVED); + KEYWORDS.put("DEC", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("DECADE", KeywordCategory.UNRESERVED); + KEYWORDS.put("DECADES", KeywordCategory.UNRESERVED); + KEYWORDS.put("DECIMAL", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("DECLARE", KeywordCategory.UNRESERVED); + KEYWORDS.put("DEFAULT", KeywordCategory.RESERVED); + KEYWORDS.put("DEFAULTS", KeywordCategory.UNRESERVED); + KEYWORDS.put("DEFERRABLE", KeywordCategory.RESERVED); + KEYWORDS.put("DEFERRED", KeywordCategory.UNRESERVED); + KEYWORDS.put("DEFINER", KeywordCategory.UNRESERVED); + KEYWORDS.put("DELETE", KeywordCategory.UNRESERVED); + KEYWORDS.put("DELIMITER", KeywordCategory.UNRESERVED); + KEYWORDS.put("DELIMITERS", KeywordCategory.UNRESERVED); + KEYWORDS.put("DEPENDS", KeywordCategory.UNRESERVED); + KEYWORDS.put("DESC", KeywordCategory.RESERVED); + KEYWORDS.put("DESCRIBE", KeywordCategory.RESERVED); + KEYWORDS.put("DETACH", KeywordCategory.UNRESERVED); + KEYWORDS.put("DICTIONARY", KeywordCategory.UNRESERVED); + KEYWORDS.put("DISABLE", KeywordCategory.UNRESERVED); + KEYWORDS.put("DISCARD", KeywordCategory.UNRESERVED); + KEYWORDS.put("DISTINCT", KeywordCategory.RESERVED); + KEYWORDS.put("DO", KeywordCategory.RESERVED); + KEYWORDS.put("DOCUMENT", KeywordCategory.UNRESERVED); + KEYWORDS.put("DOMAIN", KeywordCategory.UNRESERVED); + KEYWORDS.put("DOUBLE", KeywordCategory.UNRESERVED); + KEYWORDS.put("DROP", KeywordCategory.UNRESERVED); + KEYWORDS.put("EACH", KeywordCategory.UNRESERVED); + KEYWORDS.put("ELSE", KeywordCategory.RESERVED); + KEYWORDS.put("ENABLE", KeywordCategory.UNRESERVED); + KEYWORDS.put("ENCODING", KeywordCategory.UNRESERVED); + KEYWORDS.put("ENCRYPTED", KeywordCategory.UNRESERVED); + KEYWORDS.put("END", KeywordCategory.RESERVED); + KEYWORDS.put("ENUM", KeywordCategory.UNRESERVED); + KEYWORDS.put("ERROR", KeywordCategory.UNRESERVED); + KEYWORDS.put("ESCAPE", KeywordCategory.UNRESERVED); + KEYWORDS.put("EVENT", KeywordCategory.UNRESERVED); + KEYWORDS.put("EXCEPT", KeywordCategory.RESERVED); + KEYWORDS.put("EXCLUDE", KeywordCategory.UNRESERVED); + KEYWORDS.put("EXCLUDING", KeywordCategory.UNRESERVED); + KEYWORDS.put("EXCLUSIVE", KeywordCategory.UNRESERVED); + KEYWORDS.put("EXECUTE", KeywordCategory.UNRESERVED); + KEYWORDS.put("EXISTS", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("EXPLAIN", KeywordCategory.UNRESERVED); + KEYWORDS.put("EXPORT", KeywordCategory.UNRESERVED); + KEYWORDS.put("EXPORT_STATE", KeywordCategory.UNRESERVED); + KEYWORDS.put("EXTENSION", KeywordCategory.UNRESERVED); + KEYWORDS.put("EXTENSIONS", KeywordCategory.UNRESERVED); + KEYWORDS.put("EXTERNAL", KeywordCategory.UNRESERVED); + KEYWORDS.put("EXTRACT", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("FALSE", KeywordCategory.RESERVED); + KEYWORDS.put("FAMILY", KeywordCategory.UNRESERVED); + KEYWORDS.put("FETCH", KeywordCategory.RESERVED); + KEYWORDS.put("FILTER", KeywordCategory.UNRESERVED); + KEYWORDS.put("FIRST", KeywordCategory.UNRESERVED); + KEYWORDS.put("FLOAT", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("FOLLOWING", KeywordCategory.UNRESERVED); + KEYWORDS.put("FOR", KeywordCategory.RESERVED); + KEYWORDS.put("FORCE", KeywordCategory.UNRESERVED); + KEYWORDS.put("FOREIGN", KeywordCategory.RESERVED); + KEYWORDS.put("FORWARD", KeywordCategory.UNRESERVED); + KEYWORDS.put("FREEZE", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("FROM", KeywordCategory.RESERVED); + KEYWORDS.put("FULL", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("FUNCTION", KeywordCategory.UNRESERVED); + KEYWORDS.put("FUNCTIONS", KeywordCategory.UNRESERVED); + KEYWORDS.put("GENERATED", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("GLOB", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("GLOBAL", KeywordCategory.UNRESERVED); + KEYWORDS.put("GRANT", KeywordCategory.UNRESERVED); + KEYWORDS.put("GRANTED", KeywordCategory.UNRESERVED); + KEYWORDS.put("GROUP", KeywordCategory.RESERVED); + KEYWORDS.put("GROUPING", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("GROUPING_ID", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("GROUPS", KeywordCategory.UNRESERVED); + KEYWORDS.put("HANDLER", KeywordCategory.UNRESERVED); + KEYWORDS.put("HAVING", KeywordCategory.RESERVED); + KEYWORDS.put("HEADER", KeywordCategory.UNRESERVED); + KEYWORDS.put("HOLD", KeywordCategory.UNRESERVED); + KEYWORDS.put("HOUR", KeywordCategory.UNRESERVED); + KEYWORDS.put("HOURS", KeywordCategory.UNRESERVED); + KEYWORDS.put("IDENTITY", KeywordCategory.UNRESERVED); + KEYWORDS.put("IF", KeywordCategory.UNRESERVED); + KEYWORDS.put("IGNORE", KeywordCategory.UNRESERVED); + KEYWORDS.put("ILIKE", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("IMMEDIATE", KeywordCategory.UNRESERVED); + KEYWORDS.put("IMMUTABLE", KeywordCategory.UNRESERVED); + KEYWORDS.put("IMPLICIT", KeywordCategory.UNRESERVED); + KEYWORDS.put("IMPORT", KeywordCategory.UNRESERVED); + KEYWORDS.put("IN", KeywordCategory.RESERVED); + KEYWORDS.put("INCLUDE", KeywordCategory.UNRESERVED); + KEYWORDS.put("INCLUDING", KeywordCategory.UNRESERVED); + KEYWORDS.put("INCREMENT", KeywordCategory.UNRESERVED); + KEYWORDS.put("INDEX", KeywordCategory.UNRESERVED); + KEYWORDS.put("INDEXES", KeywordCategory.UNRESERVED); + KEYWORDS.put("INHERIT", KeywordCategory.UNRESERVED); + KEYWORDS.put("INHERITS", KeywordCategory.UNRESERVED); + KEYWORDS.put("INITIALLY", KeywordCategory.RESERVED); + KEYWORDS.put("INLINE", KeywordCategory.UNRESERVED); + KEYWORDS.put("INNER", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("INOUT", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("INPUT", KeywordCategory.UNRESERVED); + KEYWORDS.put("INSENSITIVE", KeywordCategory.UNRESERVED); + KEYWORDS.put("INSERT", KeywordCategory.UNRESERVED); + KEYWORDS.put("INSTALL", KeywordCategory.UNRESERVED); + KEYWORDS.put("INSTEAD", KeywordCategory.UNRESERVED); + KEYWORDS.put("INT", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("INTEGER", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("INTERSECT", KeywordCategory.RESERVED); + KEYWORDS.put("INTERVAL", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("INTO", KeywordCategory.RESERVED); + KEYWORDS.put("INVOKER", KeywordCategory.UNRESERVED); + KEYWORDS.put("IS", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("ISNULL", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("ISOLATION", KeywordCategory.UNRESERVED); + KEYWORDS.put("JOIN", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("JSON", KeywordCategory.UNRESERVED); + KEYWORDS.put("KEY", KeywordCategory.UNRESERVED); + KEYWORDS.put("LABEL", KeywordCategory.UNRESERVED); + KEYWORDS.put("LAMBDA", KeywordCategory.RESERVED); + KEYWORDS.put("LANGUAGE", KeywordCategory.UNRESERVED); + KEYWORDS.put("LARGE", KeywordCategory.UNRESERVED); + KEYWORDS.put("LAST", KeywordCategory.UNRESERVED); + KEYWORDS.put("LATERAL", KeywordCategory.RESERVED); + KEYWORDS.put("LEADING", KeywordCategory.RESERVED); + KEYWORDS.put("LEAKPROOF", KeywordCategory.UNRESERVED); + KEYWORDS.put("LEFT", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("LEVEL", KeywordCategory.UNRESERVED); + KEYWORDS.put("LIKE", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("LIMIT", KeywordCategory.RESERVED); + KEYWORDS.put("LISTEN", KeywordCategory.UNRESERVED); + KEYWORDS.put("LOAD", KeywordCategory.UNRESERVED); + KEYWORDS.put("LOCAL", KeywordCategory.UNRESERVED); + KEYWORDS.put("LOCATION", KeywordCategory.UNRESERVED); + KEYWORDS.put("LOCK", KeywordCategory.UNRESERVED); + KEYWORDS.put("LOCKED", KeywordCategory.UNRESERVED); + KEYWORDS.put("LOGGED", KeywordCategory.UNRESERVED); + KEYWORDS.put("MACRO", KeywordCategory.UNRESERVED); + KEYWORDS.put("MAP", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("MAPPING", KeywordCategory.UNRESERVED); + KEYWORDS.put("MATCH", KeywordCategory.UNRESERVED); + KEYWORDS.put("MATCHED", KeywordCategory.UNRESERVED); + KEYWORDS.put("MATERIALIZED", KeywordCategory.UNRESERVED); + KEYWORDS.put("MAXVALUE", KeywordCategory.UNRESERVED); + KEYWORDS.put("MERGE", KeywordCategory.UNRESERVED); + KEYWORDS.put("METHOD", KeywordCategory.UNRESERVED); + KEYWORDS.put("MICROSECOND", KeywordCategory.UNRESERVED); + KEYWORDS.put("MICROSECONDS", KeywordCategory.UNRESERVED); + KEYWORDS.put("MILLENNIA", KeywordCategory.UNRESERVED); + KEYWORDS.put("MILLENNIUM", KeywordCategory.UNRESERVED); + KEYWORDS.put("MILLISECOND", KeywordCategory.UNRESERVED); + KEYWORDS.put("MILLISECONDS", KeywordCategory.UNRESERVED); + KEYWORDS.put("MINUTE", KeywordCategory.UNRESERVED); + KEYWORDS.put("MINUTES", KeywordCategory.UNRESERVED); + KEYWORDS.put("MINVALUE", KeywordCategory.UNRESERVED); + KEYWORDS.put("MODE", KeywordCategory.UNRESERVED); + KEYWORDS.put("MONTH", KeywordCategory.UNRESERVED); + KEYWORDS.put("MONTHS", KeywordCategory.UNRESERVED); + KEYWORDS.put("MOVE", KeywordCategory.UNRESERVED); + KEYWORDS.put("NAME", KeywordCategory.UNRESERVED); + KEYWORDS.put("NAMES", KeywordCategory.UNRESERVED); + KEYWORDS.put("NATIONAL", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("NATURAL", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("NCHAR", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("NEW", KeywordCategory.UNRESERVED); + KEYWORDS.put("NEXT", KeywordCategory.UNRESERVED); + KEYWORDS.put("NO", KeywordCategory.UNRESERVED); + KEYWORDS.put("NONE", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("NOT", KeywordCategory.RESERVED); + KEYWORDS.put("NOTHING", KeywordCategory.UNRESERVED); + KEYWORDS.put("NOTIFY", KeywordCategory.UNRESERVED); + KEYWORDS.put("NOTNULL", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("NOWAIT", KeywordCategory.UNRESERVED); + KEYWORDS.put("NULL", KeywordCategory.RESERVED); + KEYWORDS.put("NULLIF", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("NULLS", KeywordCategory.UNRESERVED); + KEYWORDS.put("NUMERIC", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("OBJECT", KeywordCategory.UNRESERVED); + KEYWORDS.put("OF", KeywordCategory.UNRESERVED); + KEYWORDS.put("OFF", KeywordCategory.UNRESERVED); + KEYWORDS.put("OFFSET", KeywordCategory.RESERVED); + KEYWORDS.put("OIDS", KeywordCategory.UNRESERVED); + KEYWORDS.put("OLD", KeywordCategory.UNRESERVED); + KEYWORDS.put("ON", KeywordCategory.RESERVED); + KEYWORDS.put("ONLY", KeywordCategory.RESERVED); + KEYWORDS.put("OPERATOR", KeywordCategory.UNRESERVED); + KEYWORDS.put("OPTION", KeywordCategory.UNRESERVED); + KEYWORDS.put("OPTIONS", KeywordCategory.UNRESERVED); + KEYWORDS.put("OR", KeywordCategory.RESERVED); + KEYWORDS.put("ORDER", KeywordCategory.RESERVED); + KEYWORDS.put("ORDINALITY", KeywordCategory.UNRESERVED); + KEYWORDS.put("OTHERS", KeywordCategory.UNRESERVED); + KEYWORDS.put("OUT", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("OUTER", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("OVER", KeywordCategory.UNRESERVED); + KEYWORDS.put("OVERLAPS", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("OVERLAY", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("OVERRIDING", KeywordCategory.UNRESERVED); + KEYWORDS.put("OWNED", KeywordCategory.UNRESERVED); + KEYWORDS.put("OWNER", KeywordCategory.UNRESERVED); + KEYWORDS.put("PARALLEL", KeywordCategory.UNRESERVED); + KEYWORDS.put("PARSER", KeywordCategory.UNRESERVED); + KEYWORDS.put("PARTIAL", KeywordCategory.UNRESERVED); + KEYWORDS.put("PARTITION", KeywordCategory.UNRESERVED); + KEYWORDS.put("PARTITIONED", KeywordCategory.UNRESERVED); + KEYWORDS.put("PASSING", KeywordCategory.UNRESERVED); + KEYWORDS.put("PASSWORD", KeywordCategory.UNRESERVED); + KEYWORDS.put("PERCENT", KeywordCategory.UNRESERVED); + KEYWORDS.put("PERSISTENT", KeywordCategory.UNRESERVED); + KEYWORDS.put("PIVOT", KeywordCategory.RESERVED); + KEYWORDS.put("PIVOT_LONGER", KeywordCategory.RESERVED); + KEYWORDS.put("PIVOT_WIDER", KeywordCategory.RESERVED); + KEYWORDS.put("PLACING", KeywordCategory.RESERVED); + KEYWORDS.put("PLANS", KeywordCategory.UNRESERVED); + KEYWORDS.put("POLICY", KeywordCategory.UNRESERVED); + KEYWORDS.put("POSITION", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("POSITIONAL", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("PRAGMA", KeywordCategory.UNRESERVED); + KEYWORDS.put("PRECEDING", KeywordCategory.UNRESERVED); + KEYWORDS.put("PRECISION", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("PREPARE", KeywordCategory.UNRESERVED); + KEYWORDS.put("PREPARED", KeywordCategory.UNRESERVED); + KEYWORDS.put("PRESERVE", KeywordCategory.UNRESERVED); + KEYWORDS.put("PRIMARY", KeywordCategory.RESERVED); + KEYWORDS.put("PRIOR", KeywordCategory.UNRESERVED); + KEYWORDS.put("PRIVILEGES", KeywordCategory.UNRESERVED); + KEYWORDS.put("PROCEDURAL", KeywordCategory.UNRESERVED); + KEYWORDS.put("PROCEDURE", KeywordCategory.UNRESERVED); + KEYWORDS.put("PROGRAM", KeywordCategory.UNRESERVED); + KEYWORDS.put("PUBLICATION", KeywordCategory.UNRESERVED); + KEYWORDS.put("QUALIFY", KeywordCategory.RESERVED); + KEYWORDS.put("QUARTER", KeywordCategory.UNRESERVED); + KEYWORDS.put("QUARTERS", KeywordCategory.UNRESERVED); + KEYWORDS.put("QUOTE", KeywordCategory.UNRESERVED); + KEYWORDS.put("RANGE", KeywordCategory.UNRESERVED); + KEYWORDS.put("READ", KeywordCategory.UNRESERVED); + KEYWORDS.put("REAL", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("REASSIGN", KeywordCategory.UNRESERVED); + KEYWORDS.put("RECHECK", KeywordCategory.UNRESERVED); + KEYWORDS.put("RECURSIVE", KeywordCategory.UNRESERVED); + KEYWORDS.put("REF", KeywordCategory.UNRESERVED); + KEYWORDS.put("REFERENCES", KeywordCategory.RESERVED); + KEYWORDS.put("REFERENCING", KeywordCategory.UNRESERVED); + KEYWORDS.put("REFRESH", KeywordCategory.UNRESERVED); + KEYWORDS.put("REINDEX", KeywordCategory.UNRESERVED); + KEYWORDS.put("RELATIVE", KeywordCategory.UNRESERVED); + KEYWORDS.put("RELEASE", KeywordCategory.UNRESERVED); + KEYWORDS.put("RENAME", KeywordCategory.UNRESERVED); + KEYWORDS.put("REPEATABLE", KeywordCategory.UNRESERVED); + KEYWORDS.put("REPLACE", KeywordCategory.UNRESERVED); + KEYWORDS.put("REPLICA", KeywordCategory.UNRESERVED); + KEYWORDS.put("RESET", KeywordCategory.UNRESERVED); + KEYWORDS.put("RESPECT", KeywordCategory.UNRESERVED); + KEYWORDS.put("RESTART", KeywordCategory.UNRESERVED); + KEYWORDS.put("RESTRICT", KeywordCategory.UNRESERVED); + KEYWORDS.put("RETURNING", KeywordCategory.RESERVED); + KEYWORDS.put("RETURNS", KeywordCategory.UNRESERVED); + KEYWORDS.put("REVOKE", KeywordCategory.UNRESERVED); + KEYWORDS.put("RIGHT", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("ROLE", KeywordCategory.UNRESERVED); + KEYWORDS.put("ROLLBACK", KeywordCategory.UNRESERVED); + KEYWORDS.put("ROLLUP", KeywordCategory.UNRESERVED); + KEYWORDS.put("ROW", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("ROWS", KeywordCategory.UNRESERVED); + KEYWORDS.put("RULE", KeywordCategory.UNRESERVED); + KEYWORDS.put("SAMPLE", KeywordCategory.UNRESERVED); + KEYWORDS.put("SAVEPOINT", KeywordCategory.UNRESERVED); + KEYWORDS.put("SCHEMA", KeywordCategory.UNRESERVED); + KEYWORDS.put("SCHEMAS", KeywordCategory.UNRESERVED); + KEYWORDS.put("SCOPE", KeywordCategory.UNRESERVED); + KEYWORDS.put("SCROLL", KeywordCategory.UNRESERVED); + KEYWORDS.put("SEARCH", KeywordCategory.UNRESERVED); + KEYWORDS.put("SECOND", KeywordCategory.UNRESERVED); + KEYWORDS.put("SECONDS", KeywordCategory.UNRESERVED); + KEYWORDS.put("SECRET", KeywordCategory.UNRESERVED); + KEYWORDS.put("SECURITY", KeywordCategory.UNRESERVED); + KEYWORDS.put("SELECT", KeywordCategory.RESERVED); + KEYWORDS.put("SEMI", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("SEQUENCE", KeywordCategory.UNRESERVED); + KEYWORDS.put("SEQUENCES", KeywordCategory.UNRESERVED); + KEYWORDS.put("SERIALIZABLE", KeywordCategory.UNRESERVED); + KEYWORDS.put("SERVER", KeywordCategory.UNRESERVED); + KEYWORDS.put("SESSION", KeywordCategory.UNRESERVED); + KEYWORDS.put("SET", KeywordCategory.UNRESERVED); + KEYWORDS.put("SETOF", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("SETS", KeywordCategory.UNRESERVED); + KEYWORDS.put("SHARE", KeywordCategory.UNRESERVED); + KEYWORDS.put("SHOW", KeywordCategory.RESERVED); + KEYWORDS.put("SIMILAR", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("SIMPLE", KeywordCategory.UNRESERVED); + KEYWORDS.put("SKIP", KeywordCategory.UNRESERVED); + KEYWORDS.put("SMALLINT", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("SNAPSHOT", KeywordCategory.UNRESERVED); + KEYWORDS.put("SOME", KeywordCategory.RESERVED); + KEYWORDS.put("SORTED", KeywordCategory.UNRESERVED); + KEYWORDS.put("SOURCE", KeywordCategory.UNRESERVED); + KEYWORDS.put("SQL", KeywordCategory.UNRESERVED); + KEYWORDS.put("STABLE", KeywordCategory.UNRESERVED); + KEYWORDS.put("STANDALONE", KeywordCategory.UNRESERVED); + KEYWORDS.put("START", KeywordCategory.UNRESERVED); + KEYWORDS.put("STATEMENT", KeywordCategory.UNRESERVED); + KEYWORDS.put("STATISTICS", KeywordCategory.UNRESERVED); + KEYWORDS.put("STDIN", KeywordCategory.UNRESERVED); + KEYWORDS.put("STDOUT", KeywordCategory.UNRESERVED); + KEYWORDS.put("STORAGE", KeywordCategory.UNRESERVED); + KEYWORDS.put("STORED", KeywordCategory.UNRESERVED); + KEYWORDS.put("STRICT", KeywordCategory.UNRESERVED); + KEYWORDS.put("STRIP", KeywordCategory.UNRESERVED); + KEYWORDS.put("STRUCT", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("SUBSCRIPTION", KeywordCategory.UNRESERVED); + KEYWORDS.put("SUBSTRING", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("SUMMARIZE", KeywordCategory.RESERVED); + KEYWORDS.put("SYMMETRIC", KeywordCategory.RESERVED); + KEYWORDS.put("SYSID", KeywordCategory.UNRESERVED); + KEYWORDS.put("SYSTEM", KeywordCategory.UNRESERVED); + KEYWORDS.put("TABLE", KeywordCategory.RESERVED); + KEYWORDS.put("TABLES", KeywordCategory.UNRESERVED); + KEYWORDS.put("TABLESAMPLE", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("TABLESPACE", KeywordCategory.UNRESERVED); + KEYWORDS.put("TARGET", KeywordCategory.UNRESERVED); + KEYWORDS.put("TEMP", KeywordCategory.UNRESERVED); + KEYWORDS.put("TEMPLATE", KeywordCategory.UNRESERVED); + KEYWORDS.put("TEMPORARY", KeywordCategory.UNRESERVED); + KEYWORDS.put("TEXT", KeywordCategory.UNRESERVED); + KEYWORDS.put("THEN", KeywordCategory.RESERVED); + KEYWORDS.put("TIES", KeywordCategory.UNRESERVED); + KEYWORDS.put("TIME", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("TIMESTAMP", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("TO", KeywordCategory.RESERVED); + KEYWORDS.put("TRAILING", KeywordCategory.RESERVED); + KEYWORDS.put("TRANSACTION", KeywordCategory.UNRESERVED); + KEYWORDS.put("TRANSFORM", KeywordCategory.UNRESERVED); + KEYWORDS.put("TREAT", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("TRIGGER", KeywordCategory.UNRESERVED); + KEYWORDS.put("TRIM", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("TRUE", KeywordCategory.RESERVED); + KEYWORDS.put("TRUNCATE", KeywordCategory.UNRESERVED); + KEYWORDS.put("TRUSTED", KeywordCategory.UNRESERVED); + KEYWORDS.put("TRY_CAST", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("TYPE", KeywordCategory.UNRESERVED); + KEYWORDS.put("TYPES", KeywordCategory.UNRESERVED); + KEYWORDS.put("UNBOUNDED", KeywordCategory.UNRESERVED); + KEYWORDS.put("UNCOMMITTED", KeywordCategory.UNRESERVED); + KEYWORDS.put("UNENCRYPTED", KeywordCategory.UNRESERVED); + KEYWORDS.put("UNION", KeywordCategory.RESERVED); + KEYWORDS.put("UNIQUE", KeywordCategory.RESERVED); + KEYWORDS.put("UNKNOWN", KeywordCategory.UNRESERVED); + KEYWORDS.put("UNLISTEN", KeywordCategory.UNRESERVED); + KEYWORDS.put("UNLOGGED", KeywordCategory.UNRESERVED); + KEYWORDS.put("UNPACK", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("UNPIVOT", KeywordCategory.RESERVED); + KEYWORDS.put("UNTIL", KeywordCategory.UNRESERVED); + KEYWORDS.put("UPDATE", KeywordCategory.UNRESERVED); + KEYWORDS.put("USE", KeywordCategory.UNRESERVED); + KEYWORDS.put("USER", KeywordCategory.UNRESERVED); + KEYWORDS.put("USING", KeywordCategory.RESERVED); + KEYWORDS.put("VACUUM", KeywordCategory.UNRESERVED); + KEYWORDS.put("VALID", KeywordCategory.UNRESERVED); + KEYWORDS.put("VALIDATE", KeywordCategory.UNRESERVED); + KEYWORDS.put("VALIDATOR", KeywordCategory.UNRESERVED); + KEYWORDS.put("VALUE", KeywordCategory.UNRESERVED); + KEYWORDS.put("VALUES", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("VARCHAR", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("VARIABLE", KeywordCategory.UNRESERVED); + KEYWORDS.put("VARIADIC", KeywordCategory.RESERVED); + KEYWORDS.put("VARYING", KeywordCategory.UNRESERVED); + KEYWORDS.put("VERBOSE", KeywordCategory.TYPE_FUNCTION); + KEYWORDS.put("VERSION", KeywordCategory.UNRESERVED); + KEYWORDS.put("VIEW", KeywordCategory.UNRESERVED); + KEYWORDS.put("VIEWS", KeywordCategory.UNRESERVED); + KEYWORDS.put("VIRTUAL", KeywordCategory.UNRESERVED); + KEYWORDS.put("VOLATILE", KeywordCategory.UNRESERVED); + KEYWORDS.put("WEEK", KeywordCategory.UNRESERVED); + KEYWORDS.put("WEEKS", KeywordCategory.UNRESERVED); + KEYWORDS.put("WHEN", KeywordCategory.RESERVED); + KEYWORDS.put("WHERE", KeywordCategory.RESERVED); + KEYWORDS.put("WHITESPACE", KeywordCategory.UNRESERVED); + KEYWORDS.put("WINDOW", KeywordCategory.RESERVED); + KEYWORDS.put("WITH", KeywordCategory.RESERVED); + KEYWORDS.put("WITHIN", KeywordCategory.UNRESERVED); + KEYWORDS.put("WITHOUT", KeywordCategory.UNRESERVED); + KEYWORDS.put("WORK", KeywordCategory.UNRESERVED); + KEYWORDS.put("WRAPPER", KeywordCategory.UNRESERVED); + KEYWORDS.put("WRITE", KeywordCategory.UNRESERVED); + KEYWORDS.put("XML", KeywordCategory.UNRESERVED); + KEYWORDS.put("XMLATTRIBUTES", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("XMLCONCAT", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("XMLELEMENT", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("XMLEXISTS", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("XMLFOREST", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("XMLNAMESPACES", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("XMLPARSE", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("XMLPI", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("XMLROOT", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("XMLSERIALIZE", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("XMLTABLE", KeywordCategory.COLUMN_NAME); + KEYWORDS.put("YEAR", KeywordCategory.UNRESERVED); + KEYWORDS.put("YEARS", KeywordCategory.UNRESERVED); + KEYWORDS.put("YES", KeywordCategory.UNRESERVED); + KEYWORDS.put("ZONE", KeywordCategory.UNRESERVED); + } + + private DuckDbKeywordPolicy() {} + + /** DuckDB's keywords and their categories, in upper case. */ + public Map getKeywords() { + return Collections.unmodifiableMap(KEYWORDS); + } + + @Override + public boolean isName(String word, NamePosition position) { + KeywordCategory category = KEYWORDS.get(word.toUpperCase(Locale.ROOT)); + if (category == null) { + return true; + } + switch (position) { + case LABEL: + return true; + case BARE_ALIAS: + return false; + case FUNCTION: + // DuckDB's grammar special-cases functions named by column-name keywords + // (COALESCE, NULLIF, IF, GREATEST, ...), so only reserved keywords are rejected + return category != KeywordCategory.RESERVED; + case NAME: + default: + return category == KeywordCategory.UNRESERVED + || category == KeywordCategory.COLUMN_NAME; + } + } +} diff --git a/src/main/java/net/sf/jsqlparser/parser/dialect/KeywordCategory.java b/src/main/java/net/sf/jsqlparser/parser/dialect/KeywordCategory.java new file mode 100644 index 000000000..3f81abc96 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/parser/dialect/KeywordCategory.java @@ -0,0 +1,22 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser.dialect; + +/** PostgreSQL's keyword categories, also used by DuckDB. */ +public enum KeywordCategory { + /** Usable as any name. */ + UNRESERVED, + /** Usable as a column, table or alias name, not as a function or type name. */ + COLUMN_NAME, + /** Usable as a function or type name, not as a column, table or alias name. */ + TYPE_FUNCTION, + /** Usable as a name only after {@code AS} or {@code .}. */ + RESERVED +} diff --git a/src/main/java/net/sf/jsqlparser/parser/dialect/KeywordPolicy.java b/src/main/java/net/sf/jsqlparser/parser/dialect/KeywordPolicy.java new file mode 100644 index 000000000..3d8d0d2fc --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/parser/dialect/KeywordPolicy.java @@ -0,0 +1,31 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser.dialect; + +/** + * A dialect's own reserved-keyword rules, replacing the default ones: which words it accepts as + * unquoted names in each position. + */ +public interface KeywordPolicy { + /** Whether {@code word}, a keyword or not, is a valid unquoted name at {@code position}. */ + boolean isName(String word, NamePosition position); + + /** Where a name appears, since dialects reserve keywords differently in each. */ + enum NamePosition { + /** A column, table, schema, CTE or other object name. */ + NAME, + /** A function name, directly followed by {@code (}. */ + FUNCTION, + /** A name after {@code AS} or {@code .}. */ + LABEL, + /** An alias without {@code AS}. */ + BARE_ALIAS + } +} diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index eb904885a..0766618b2 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -40,6 +40,7 @@ import net.sf.jsqlparser.statement.oracle.OracleNullStatement; import java.lang.reflect.Field; import java.lang.Integer; +import net.sf.jsqlparser.parser.dialect.KeywordPolicy; import net.sf.jsqlparser.parser.feature.*; import net.sf.jsqlparser.expression.*; import net.sf.jsqlparser.expression.operators.arithmetic.*; @@ -429,6 +430,7 @@ public class CCJSqlParser extends AbstractJSqlParser { return true; default: return (t.kind >= MIN_NON_RESERVED_WORD && t.kind <= MAX_NON_RESERVED_WORD) + || isDialectUnreservedKeyword(t) || ")".equals(t.image) || "]".equals(t.image) || "?".equals(t.image); } @@ -619,7 +621,7 @@ public class CCJSqlParser extends AbstractJSqlParser { boolean ternary = "?".equals(t.image) && isTernaryAhead(); int prec; if (op == K_AND || op == OP_DOUBLEAND) prec = 4; - else if (op == K_XOR || op == K_OR) prec = 2; + else if (op == K_XOR && !isDialectUnreservedKeyword(t) || op == K_OR) prec = 2; else if (ternary) prec = 2; else break; if (prec < minPrec) break; @@ -797,7 +799,7 @@ public class CCJSqlParser extends AbstractJSqlParser { * alternatives accepted there (base identifiers, DATA_TYPE, non-reserved keywords * and the reserved-keyword-as-identifier list). */ - private static boolean isIdentifierishNameToken(Token t) { + private boolean isIdentifierishNameToken(Token t) { switch (t.kind) { case S_IDENTIFIER: case S_QUOTED_IDENTIFIER: case DATA_TYPE: case K_DATETIMELITERAL: case K_DATE_LITERAL: @@ -810,7 +812,8 @@ public class CCJSqlParser extends AbstractJSqlParser { case K_TOP: case K_VALUE: case K_VALUES: return true; default: - return t.kind >= MIN_NON_RESERVED_WORD && t.kind <= MAX_NON_RESERVED_WORD; + return t.kind >= MIN_NON_RESERVED_WORD && t.kind <= MAX_NON_RESERVED_WORD + || isDialectUnreservedKeyword(t); } } @@ -1175,7 +1178,8 @@ public class CCJSqlParser extends AbstractJSqlParser { private boolean isOptionHintWordAhead() { int kind = getToken(1).kind; if (kind == S_IDENTIFIER - || (kind >= MIN_NON_RESERVED_WORD && kind <= MAX_NON_RESERVED_WORD)) { + || (kind >= MIN_NON_RESERVED_WORD && kind <= MAX_NON_RESERVED_WORD) + || isDialectUnreservedKeyword(getToken(1))) { return true; } switch (kind) { @@ -1505,7 +1509,8 @@ public class CCJSqlParser extends AbstractJSqlParser { || next.kind == OPENING_CURLY_BRACKET && "-".equals(getToken(2).image) || next.kind == S_IDENTIFIER || next.kind == S_QUOTED_IDENTIFIER || next.kind == DATA_TYPE - || next.kind >= MIN_NON_RESERVED_WORD && next.kind <= MAX_NON_RESERVED_WORD; + || next.kind >= MIN_NON_RESERVED_WORD && next.kind <= MAX_NON_RESERVED_WORD + || isDialectUnreservedKeyword(next); } private boolean isAliasAhead() { @@ -1532,6 +1537,10 @@ public class CCJSqlParser extends AbstractJSqlParser { return false; } + // The dialect's keyword policy decides for keywords it rejects or accepts as names + if (isDialectRejectedName(t, KeywordPolicy.NamePosition.NAME)) return false; + if (isDialectUnreservedKeyword(t)) return true; + // 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 @@ -1611,6 +1620,176 @@ public class CCJSqlParser extends AbstractJSqlParser { } } + /** The configured dialect's own reserved-keyword rules, or null for the default ones. */ + private KeywordPolicy keywordPolicy() { + String dialect = getAsString(Feature.dialect); + return dialect == null ? null : Dialect.valueOf(dialect).getKeywordPolicy(); + } + + /** An unquoted word: an identifier or a keyword, not a quoted identifier, literal or symbol. */ + private static boolean isWord(Token token) { + String image = token.image; + if (image == null || image.isEmpty() || token.kind == S_QUOTED_IDENTIFIER + || !(Character.isLetter(image.charAt(0)) || image.charAt(0) == '_')) { + return false; + } + for (int i = 1; i < image.length(); i++) { + char c = image.charAt(i); + if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') { + return false; + } + } + return true; + } + + /** Whether the dialect's keyword policy rejects {@code token} as a name at {@code position}. */ + private boolean isDialectRejectedName(Token token, KeywordPolicy.NamePosition position) { + KeywordPolicy policy = keywordPolicy(); + return policy != null && isWord(token) && !policy.isName(token.image, position); + } + + /** The position of the name ahead: after AS or "." a label, before "(" a function name. */ + private KeywordPolicy.NamePosition nameAheadPosition() { + Token previous = getToken(0); + if (previous != null && (previous.kind == K_AS || ".".equals(previous.image))) { + return KeywordPolicy.NamePosition.LABEL; + } + return "(".equals(getToken(2).image) ? KeywordPolicy.NamePosition.FUNCTION + : KeywordPolicy.NamePosition.NAME; + } + + private ParseException reservedInDialect(Token token) { + return new ParseException("\"" + token.image + "\" is a reserved keyword in the " + + getAsString(Feature.dialect) + " dialect, at line " + token.beginLine + + ", column " + token.beginColumn + "."); + } + + /** The keywords DialectLabel() accepts: every single-word keyword token. */ + private static final Set DIALECT_LABEL_KINDS = new HashSet(Arrays.asList( + K_ABSENT, K_ACTION, K_ACTIVE, K_ADD, K_ADVANCE, K_ADVISE, + K_AGAINST, K_AGGREGATE, K_ALGORITHM, K_ALIGN, K_ALL, K_ALTER, + K_ALWAYS, K_ANALYSE, K_ANALYZE, K_AND, K_ANTI, K_ANY, + K_APPEND_ONLY, K_APPLY, K_APPROX, K_APPROXIMATE, K_ARCHIVE, K_ARRAY_LITERAL, + K_AS, K_ASC, K_ASSERT, K_ASYMMETRIC, K_AT, K_ATTACH, + K_AUTHORIZATION, K_AUTO, K_AUTO_INCREMENT, K_AZURE, K_BASE64, K_BEFORE, + K_BEGIN, K_BERNOULLI, K_BETWEEN, K_BINARY, K_BIT, K_BLOBSTORAGE, + K_BLOCK, K_BOOLEAN, K_BOTH, K_BRANCH, K_BREADTH, K_BROWSE, + K_BUCKETS, K_BUFFERS, K_BY, K_BYTE, K_BYTES, K_CACHE, + K_CALL, K_CASCADE, K_CASE, K_CASEWHEN, K_CAST, K_CERTIFICATE, + K_CHANGE, K_CHANGES, K_CHAR, K_CHARACTER, K_CHECK, K_CHECKPOINT, + K_CLONE, K_CLOSE, K_CLOUD, K_COALESCE, K_COLLATE, K_COLUMN, + K_COLUMNS, K_COMMENT, K_COMMENTS, K_COMMIT, K_COMPLETION, K_CONCURRENTLY, + K_CONFLICT, K_CONNECT, K_CONNECT_BY_ROOT, K_CONSTRAINT, K_CONSTRAINTS, K_CONVERT, + K_COPY, K_CORRESPONDING, K_COSTS, K_COUNT, K_CREATE, K_CREATED, + K_CROSS, K_CSV, K_CURRENT, K_CYCLE, K_DATA, K_DATABASE, + K_DBA_RECYCLEBIN, K_DDL, K_DEALLOCATE, K_DECLARE, K_DEFAULT, K_DEFAULTS, + K_DEFERRABLE, K_DELAYED, K_DELETE, K_DELIMIT, K_DELIMITER, K_DEPTH, + K_DESC, K_DESCRIBE, K_DETACH, K_DIRECTORY, K_DISABLE, K_DISCARD, + K_DISCONNECT, K_DISTINCT, K_DISTINCTROW, K_DIV, K_DML, K_DO, + K_DOMAIN, K_DRIVER, K_DROP, K_DUMP, K_DUMPFILE, K_DUPLICATE, + K_ELEMENTS, K_ELSE, K_EMIT, K_EMPTY, K_ENABLE, K_ENCLOSED, + K_ENCODING, K_ENCRYPTION, K_END, K_ENDS, K_ENFORCED, K_ENGINE, + K_ERROR, K_ERRORS, K_ESCAPE, K_ESCAPED, K_EVENT, K_EVERY, + K_EXA, K_EXCEPT, K_EXCHANGE, K_EXCLUDE, K_EXCLUDES, K_EXCLUDING, + K_EXCLUSIVE, K_EXEC, K_EXECUTE, K_EXISTS, K_EXPLAIN, K_EXPLICIT, + K_EXPORT, K_EXTEND, K_EXTENDED, K_EXTRACT, K_FALSE, K_FBV, + K_FETCH, K_FIELDS, K_FILE, K_FILL, K_FILTER, K_FINAL, + K_FINALIZE, K_FIRST, K_FLUSH, K_FN, K_FOLLOWING, K_FOR, + K_FORCE, K_FOREIGN, K_FORMAT, K_FROM, K_FULL, K_FULLTEXT, + K_FUNCTION, K_GLOB, K_GLOBAL, K_GRANT, K_GROUP, K_GROUPING, + K_GROUPS, K_GROUP_CONCAT, K_GUARD, K_HASH, K_HAVING, K_HIGH, + K_HIGH_PRIORITY, K_HISTOGRAM, K_HISTORY, K_HOPPING, K_IDENTIFIED, K_IDENTITY, + K_IF, K_IGNORE, K_IIF, K_ILIKE, K_IMPORT, K_IN, + K_INCLUDE, K_INCLUDES, K_INCLUDE_NULL_VALUES, K_INCLUDING, K_INCREMENT, K_INDEX, + K_INFORMATION, K_INNER, K_INSERT, K_INSTALL, K_INTERLEAVE, K_INTERPOLATE, + K_INTERPRET, K_INTERSECT, K_INTERVAL, K_INTO, K_INVALIDATE, K_INVERSE, + K_INVISIBLE, K_IS, K_ISNULL, K_JDBC, K_JOIN, K_JSON, + K_JSON_ARRAY, K_JSON_ARRAYAGG, K_JSON_OBJECT, K_JSON_OBJECTAGG, K_KEEP, K_KEY, + K_KEYS, K_KEY_BLOCK_SIZE, K_KILL, K_LAST, K_LATERAL, K_LEADING, + K_LEFT, K_LESS, K_LEVEL, K_LIKE, K_LIMIT, K_LINEAR, + K_LINES, K_LINK, K_LIST, K_LOAD, K_LOCAL, K_LOCK, + K_LOCKED, K_LOG, K_LOOP, K_LOW, K_LOW_PRIORITY, K_LTRIM, + K_MACRO, K_MATCH, K_MATCHED, K_MATCH_ALL, K_MATCH_ANY, K_MATCH_PHRASE, + K_MATCH_PHRASE_PREFIX, K_MATCH_REGEXP, K_MATERIALIZED, K_MAX, K_MAXVALUE, K_MAX_ROWS, + K_MEMBER, K_MERGE, K_MIN, K_MINUS, K_MINVALUE, K_MIN_ROWS, + K_MODE, K_MODIFY, K_MODULUS, K_MOVEMENT, K_NAME, K_NAMES, + K_NATURAL, K_NEAREST, K_NEVER, K_NEXT, K_NO, K_NOCACHE, + K_NOCYCLE, K_NOKEEP, K_NOLOCK, K_NOMAXVALUE, K_NOMINVALUE, K_NONE, + K_NOORDER, K_NOT, K_NOTHING, K_NOTIFY, K_NOTNULL, K_NOVALIDATE, + K_NOWAIT, K_NO_WRITE_TO_BINLOG, K_NULL, K_NULLS, K_OF, K_OFF, + K_OFFSET, K_ON, K_ONLY, K_OPEN, K_OPTIMIZE, K_OPTION, + K_OPTIONALLY, K_OR, K_ORA, K_ORDER, K_ORDINALITY, K_OUTER, + K_OUTFILE, K_OUTPUT, K_OVER, K_OVERFLOW, K_OVERLAPS, K_OVERRIDING, + K_OVERWRITE, K_PADDING, K_PARALLEL, K_PARENT, K_PARSER, K_PARTITION, + K_PARTITIONING, K_PARTITIONS, K_PASSWORD, K_PATH, K_PERCENT, K_PIVOT, + K_PLACING, K_PLAN, K_PLUS, K_POLICY, K_PRAGMA, K_PRECEDING, + K_PREFERRING, K_PREPARE, K_PREWHERE, K_PRIMARY, K_PRIOR, K_PROCEDURE, + K_PUBLIC, K_PURGE, K_QUALIFY, K_QUERY, K_QUICK, K_QUIESCE, + K_RANDOM, K_RANGE, K_RAW, K_READ, K_REBUILD, K_RECURSIVE, + K_RECYCLEBIN, K_REFERENCES, K_REFRESH, K_REGEXP, K_REGEXP_LIKE, K_REGISTER, + K_REJECT, K_RELEASE, K_REMAINDER, K_REMOTE, K_REMOVE, K_RENAME, + K_REORGANIZE, K_REPAIR, K_REPEATABLE, K_REPLACE, K_RESET, K_RESPECT, + K_RESTART, K_RESTRICT, K_RESTRICTED, K_RESUMABLE, K_RESUME, K_RETURN, + K_RETURNING, K_RETURNS, K_REVOKE, K_RIGHT, K_RLIKE, K_ROLLBACK, + K_ROLLUP, K_ROOT, K_ROW, K_ROWS, K_RTRIM, K_SAFE_CAST, + K_SAFE_CONVERT, K_SAMPLE, K_SAVEPOINT, K_SCHEDULE, K_SCHEMA, K_SCRIPT, + K_SEARCH, K_SECURE, K_SECURITY, K_SEED, K_SEMI, K_SEPARATOR, + K_SEQUENCE, K_SESSION, K_SET, K_SETS, K_SETTINGS, K_SHARE, + K_SHOW, K_SHUTDOWN, K_SIBLINGS, K_SIGNED, K_SIMILAR, K_SIMILARITY, + K_SIZE, K_SKIP, K_SLAVE, K_SOME, K_SPATIAL, K_SQL_CACHE, + K_SQL_CALC_FOUND_ROWS, K_SQL_NO_CACHE, K_STALENESS, K_START, K_STARTING, K_STARTS, + K_STATEMENT, K_STEP, K_STORAGE, K_STORED, K_STRAIGHT, K_STREAM, + K_STRICT, K_STRING, K_STRUCT, K_SUBPARTITION, K_SUBPARTITIONS, K_SUMMARIZE, + K_SUSPEND, K_SWITCH, K_SYMMETRIC, K_SYNONYM, K_SYSTEM, K_SYSTEM_TIME, + K_SYSTEM_TIMESTAMP, K_SYSTEM_VERSION, K_TABLE, K_TABLES, K_TABLESAMPLE, K_TABLESPACE, + K_TEMP, K_TEMPORARY, K_TERMINATED, K_THAN, K_THEN, K_TIMEOUT, + K_TO, K_TOP, K_TRAILING, K_TRIGGER, K_TRIM, K_TRUE, + K_TRUNCATE, K_TRY_CAST, K_TRY_CONVERT, K_TUMBLING, K_TYPE, K_UNBOUNDED, + K_UNION, K_UNIQUE, K_UNKNOWN, K_UNLIMITED, K_UNLOGGED, K_UNPIVOT, + K_UNQIESCE, K_UNSIGNED, K_UPDATE, K_UPSERT, K_USE, K_USER, + K_USING, K_VALIDATE, K_VALIDATION, K_VALUE, K_VALUES, K_VERBOSE, + K_VERIFY, K_VERSION, K_VIEW, K_VISIBLE, K_VOLATILE, K_WAIT, + K_WHEN, K_WHERE, K_WINDOW, K_WITH, K_WITHIN, K_WITHOUT, + K_WITHOUT_ARRAY_WRAPPER, K_WORK, K_XML, K_XMLAGG, K_XMLDATA, K_XMLSCHEMA, + K_XMLSERIALIZE, K_XMLTEXT, K_XOR, K_XSINIL, K_YAML, K_YES, + K_ZEROFILL, K_ZONE)); + + /** A keyword that the dialect's keyword policy accepts as a name after AS or ".". */ + private boolean isDialectLabelAhead() { + return isDialectLabelAt(1); + } + + private boolean isDialectLabelAt(int offset) { + KeywordPolicy policy = keywordPolicy(); + Token token = getToken(offset); + return policy != null && DIALECT_LABEL_KINDS.contains(token.kind) + && policy.isName(token.image, KeywordPolicy.NamePosition.LABEL); + } + + /** The keywords reserved by default that DialectUnreservedWord() accepts. */ + private static final Set DIALECT_UNRESERVED_WORD_KINDS = new HashSet( + Arrays.asList(K_ABSENT, K_BETWEEN, K_CASEWHEN, K_CONNECT, K_CONNECT_BY_ROOT, K_CSV, K_CURRENT, K_EXCLUDES, K_EXISTS, K_EXTEND, K_FBV, K_FILE, K_FINAL, K_FORCE, K_IIF, K_IMPORT, K_INCLUDES, K_MINUS, K_NOCYCLE, K_OPTIMIZE, K_OUTPUT, K_PREFERRING, K_PREWHERE, K_PRIOR, K_PUBLIC, K_RETURNS, K_SAMPLE, K_SCRIPT, K_SETTINGS, K_TOP, K_UNBOUNDED, K_UNKNOWN, K_USE, K_VERIFY, K_XMLSERIALIZE, K_XOR)); + + /** + * Whether {@code token} is a keyword reserved by default that the configured dialect accepts + * as a name, see {@link KeywordPolicy}. + */ + private boolean isDialectUnreservedKeyword(Token token) { + KeywordPolicy policy = keywordPolicy(); + return policy != null && DIALECT_UNRESERVED_WORD_KINDS.contains(token.kind) + && policy.isName(token.image, KeywordPolicy.NamePosition.NAME); + } + + /** An alias of a select item: with a dialect keyword policy, a bare one cannot be a keyword. */ + private boolean isSelectItemAliasAhead() { + Token token = getToken(1); + if (token.kind != K_AS + && isDialectRejectedName(token, KeywordPolicy.NamePosition.BARE_ALIAS)) { + return false; + } + return isAliasAhead(); + } + private boolean isKeywordAhead(String keyword) { Token token = getToken(1); return token.kind != EOF && token.image != null && keyword.equalsIgnoreCase(token.image); @@ -2185,6 +2364,7 @@ public class CCJSqlParser extends AbstractJSqlParser { // PostgreSQL JSON operator, it is consumed by prattExpressionRest instead if ("?".equals(t.image) && isTernaryAhead()) return false; if (isComparisonOperator(t)) return true; + if (isDialectUnreservedKeyword(t)) return false; switch (t.kind) { // Each suffix's start token: case K_OVERLAPS: // OVERLAPS @@ -6825,6 +7005,12 @@ Column Column() #Column : String RelObjectName() : { Token tk = null; String result = null; } { + { + // A dialect's keyword policy rejects some names the default rules below accept + if (isDialectRejectedName(getToken(1), nameAheadPosition())) { + throw reservedInDialect(getToken(1)); + } + } ( /* ── Base identifier tokens ─────────────────────────────────────── */ tk= @@ -6838,6 +7024,10 @@ String RelObjectName() : && getToken(1).kind <= MAX_NON_RESERVED_WORD }) result = NonReservedWord() + /* ── Reserved keywords the dialect treats as plain names ────────── */ + | LOOKAHEAD({ isDialectUnreservedKeyword(getToken(1)) }) + tk = DialectUnreservedWord() + /* ── Reserved keywords usable as identifiers (context-guarded) ─── */ | LOOKAHEAD({ isReservedKeywordAsIdentifier() }) ( tk= | tk= | tk= | tk= @@ -6850,7 +7040,7 @@ String RelObjectName() : | tk= | tk= | tk= | tk= | tk= | tk= | tk= ) ) - { return result != null ? result : tk.image; } + { if (true) return result != null ? result : tk.image; } } /* @@ -6865,10 +7055,149 @@ String RelObjectName() : * was called: dotted-name continuations, index/constraint names, type names, * named-parameter identifier chains, and join table references. */ +/** + * The keywords reserved by default that some dialect accepts as plain names, see + * Dialect#getUnreservedKeywords(). Only reached behind isDialectUnreservedKeyword(). + */ +Token DialectUnreservedWord() : +{ Token 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= + | tk= | tk= | tk= | tk= | tk= + | tk= | tk= | tk= | tk= | tk= + | tk= + ) + { return tk; } +} + +/** + * Any single-word keyword, as a name after AS or "." where a dialect's keyword policy accepts it. + * Only reached behind isDialectLabelAhead(). + */ +Token DialectLabel() : +{ Token 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= + | 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= | 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= | 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= | 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= | 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= + | 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= | 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= | 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= | 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= | 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= + | 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= | 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= | 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= | 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= | 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= + | 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= | 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= | 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= | 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= + ) + { return tk; } +} + String RelObjectNameExt() : { Token tk = null; String result = null; } { - ( result = RelObjectName() | tk= | tk= | tk= ) + ( + LOOKAHEAD({ isDialectLabelAhead() }) tk = DialectLabel() + | result = RelObjectName() | tk= | tk= | tk= + ) { return tk != null ? tk.image : result; } } @@ -8350,7 +8679,7 @@ SelectItem SelectItem() #SelectItem: } { expression = SelectItemExpression() - [ LOOKAHEAD({ isAliasAhead() }) alias=Alias() ] + [ LOOKAHEAD({ isSelectItemAliasAhead() }) alias=Alias() ] { SelectItem selectItem = new SelectItem(expression, alias); linkAST(selectItem,jjtThis); @@ -8588,8 +8917,13 @@ Alias Alias(): // SELECT fun(x) T // SELECT fun(x) T(a,b,c) - [ { useAs = true; } ] - ( name=RelObjectName() | token= { name=token.image; } ) + ( + LOOKAHEAD({ getToken(1).kind == K_AS && isDialectLabelAt(2) }) + { useAs = true; } token=DialectLabel() { name=token.image; } + | + [ { useAs = true; } ] + ( name=RelObjectName() | token= { name=token.image; } ) + ) { alias = new Alias(name,useAs); } [ LOOKAHEAD(2) aliasColumns = AliasColumns() { alias.setAliasColumns(aliasColumns); } ] @@ -9693,14 +10027,14 @@ OracleHierarchicalExpression OracleHierarchicalQueryClause(): ( ( expr=XorExpression() {result.setStartExpression(expr);} - [ { result.setNoCycle(true); } ] expr=XorExpression() + [ LOOKAHEAD({ getToken(1).kind == K_NOCYCLE }) { result.setNoCycle(true); } ] expr=XorExpression() { result.setConnectExpression(expr); } ) | ( - [ { result.setNoCycle(true); } ] expr=XorExpression() + [ LOOKAHEAD({ getToken(1).kind == K_NOCYCLE }) { result.setNoCycle(true); } ] expr=XorExpression() { result.setConnectExpression(expr); result.setConnectFirst(true); @@ -10394,7 +10728,7 @@ Expression XorExpression(): { result=AndChain() ( - LOOKAHEAD(2) + LOOKAHEAD(2, { getToken(1).kind != K_XOR || !isDialectUnreservedKeyword(getToken(1)) }) ( right=AndChain() { result = new OrExpression(result, right); } | @@ -10443,7 +10777,7 @@ Expression Condition(): ( { exclamationMarks.addFirst(Boolean.FALSE); } | "!" { exclamationMarks.addFirst(Boolean.TRUE); } ) )* ( - result=ExistsExpression() + LOOKAHEAD({ getToken(1).kind == K_EXISTS && (getToken(2).kind == OPENING_BRACKET || !isDialectUnreservedKeyword(getToken(1))) }) result=ExistsExpression() | [ LOOKAHEAD(2, { getToken(1).kind == K_PRIOR }) { oraclePrior = EqualsTo.ORACLE_PRIOR_START; } ] left=SimpleExpression() { result = left; } @@ -10587,7 +10921,7 @@ Expression SQLCondition(): } { ( - result=ExistsExpression() + LOOKAHEAD({ getToken(1).kind == K_EXISTS && (getToken(2).kind == OPENING_BRACKET || !isDialectUnreservedKeyword(getToken(1))) }) result=ExistsExpression() | left = SimpleExpression() { result = left; } [ LOOKAHEAD(2) ( @@ -11380,7 +11714,7 @@ Expression PrimaryExpression() #PrimaryExpression: | LOOKAHEAD(4 , {!interrupted}) retval=ExtractExpression() - | retval=XMLSerializeExpr() + | LOOKAHEAD({ getToken(1).kind == K_XMLSERIALIZE && getToken(2).kind == OPENING_BRACKET }) retval=XMLSerializeExpr() | LOOKAHEAD(3, { !interrupted}) retval = JsonFunction() @@ -11428,9 +11762,10 @@ Expression PrimaryExpression() #PrimaryExpression: | LOOKAHEAD(2, {!interrupted}) retval = NextValExpression() - | retval=ConnectByRootOperator() + | LOOKAHEAD({ getToken(1).kind == K_CONNECT_BY_ROOT && !isDialectUnreservedKeyword(getToken(1)) }) + retval=ConnectByRootOperator() - | retval=ConnectByPriorOperator() + | LOOKAHEAD({ getToken(1).kind == K_PRIOR && !isDialectUnreservedKeyword(getToken(1)) }) retval=ConnectByPriorOperator() | LOOKAHEAD(2, { !interrupted && getToken(1).kind == K_KEY && getToken(2).kind != OPENING_BRACKET }) retval=KeyExpression() @@ -12923,7 +13258,7 @@ WindowElement WindowElement(): ( { windowElement.setType(WindowElement.Type.ROWS); } | { windowElement.setType(WindowElement.Type.RANGE); } | { windowElement.setType(WindowElement.Type.GROUPS); }) - ( ( + ( LOOKAHEAD({ getToken(1).kind == K_BETWEEN }) ( { windowElement.setRange(range); } offset = WindowOffset() { range.setStart(offset); } offset = WindowOffset() { range.setEnd(offset); } @@ -12976,6 +13311,7 @@ WindowOffset WindowOffset(): } { ( + LOOKAHEAD({ getToken(1).kind == K_UNBOUNDED && (getToken(2).kind == K_PRECEDING || getToken(2).kind == K_FOLLOWING) }) ( ( { offset.setType(WindowOffset.Type.PRECEDING); } | { offset.setType(WindowOffset.Type.FOLLOWING); } ) diff --git a/src/test/java/net/sf/jsqlparser/expression/TaggedDollarStringTest.java b/src/test/java/net/sf/jsqlparser/expression/TaggedDollarStringTest.java index 4dd3fa939..5f3c5b875 100644 --- a/src/test/java/net/sf/jsqlparser/expression/TaggedDollarStringTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/TaggedDollarStringTest.java @@ -22,6 +22,7 @@ import net.sf.jsqlparser.parser.CCJSqlParser; import net.sf.jsqlparser.parser.CCJSqlParserConstants; import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.parser.StreamProvider; import net.sf.jsqlparser.parser.Token; import net.sf.jsqlparser.schema.Column; @@ -94,7 +95,9 @@ static Stream> identifierConfigurations() { }, parser -> parser.withDialect(Dialect.POSTGRESQL) .withDollarQuotedStringTags(false)), - Stream.of(Dialect.values()).filter(dialect -> dialect != Dialect.POSTGRESQL) + Stream.of(Dialect.values()) + .filter(dialect -> !dialect.getLexerFeatures() + .contains(Feature.allowDollarQuotedStringTags)) .map(dialect -> parser -> parser.withDialect(dialect))); } diff --git a/src/test/java/net/sf/jsqlparser/parser/dialect/DuckDbKeywordPolicyTest.java b/src/test/java/net/sf/jsqlparser/parser/dialect/DuckDbKeywordPolicyTest.java new file mode 100644 index 000000000..7041562b0 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/parser/dialect/DuckDbKeywordPolicyTest.java @@ -0,0 +1,161 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser.dialect; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParser; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * With {@link Dialect#DUCKDB}, every DuckDB keyword parses in each name position exactly when + * DuckDB 1.5.5 itself parses it ({@code duckdb-keyword-names.csv}), except the exemptions below. + * Function names are not compared: DuckDB's grammar has dedicated syntax for many keyword-named + * functions (COALESCE, NULLIF, IF, ...), so whether a call parses depends on the function and its + * arguments rather than on keyword reservation. + */ +public class DuckDbKeywordPolicyTest { + private static final Consumer DUCKDB = + parser -> parser.withDialect(Dialect.DUCKDB); + + private static final Map POSITIONS = new LinkedHashMap<>(); + static { + POSITIONS.put("column", "SELECT %s FROM t"); + POSITIONS.put("qualified_column", "SELECT t.%s FROM t"); + POSITIONS.put("as_alias", "SELECT 1 AS %s"); + POSITIONS.put("bare_alias", "SELECT 1 %s"); + POSITIONS.put("table", "SELECT * FROM %s"); + POSITIONS.put("table_alias", "SELECT * FROM t %s"); + POSITIONS.put("cte", "WITH %1$s AS (SELECT 1) SELECT * FROM %1$s"); + } + + /** + * Differences that do not come from keyword reservation: JSqlParser parses these words as + * dedicated syntax at that position, or as a date unit. + */ + private static final Map> EXEMPTIONS = new HashMap<>(); + static { + // constructors, casts and expressions: MAP {...}, STRUCT(...), TRY_CAST(...), COLUMNS(...) + for (String keyword : Arrays.asList("map", "struct", "try_cast", "columns", "generated")) { + EXEMPTIONS.put(keyword, Arrays.asList("column", "table", "table_alias", "cte")); + } + EXEMPTIONS.put("default", Arrays.asList("column")); + EXEMPTIONS.put("prior", Arrays.asList("column")); + // WITH RECURSIVE, FETCH ... WITH TIES, UPDATE ... SET, a new SELECT after AS + EXEMPTIONS.put("recursive", Arrays.asList("cte")); + EXEMPTIONS.put("ties", Arrays.asList("cte")); + EXEMPTIONS.put("set", Arrays.asList("table_alias")); + EXEMPTIONS.put("select", Arrays.asList("as_alias")); + // 1 DAY is a date unit, not an alias + for (String keyword : Arrays.asList("day", "hour", "minute", "month", "second", "year")) { + EXEMPTIONS.put(keyword, Arrays.asList("bare_alias")); + } + } + + static Stream duckDbKeywordNames() throws IOException { + List arguments = new ArrayList<>(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader( + DuckDbKeywordPolicyTest.class.getResourceAsStream("duckdb-keyword-names.csv"), + StandardCharsets.UTF_8))) { + List header = null; + for (String line = reader.readLine(); line != null; line = reader.readLine()) { + if (line.startsWith("#")) { + continue; + } + List cells = Arrays.asList(line.split(",")); + if (header == null) { + header = cells; + continue; + } + String keyword = cells.get(0); + for (int i = 1; i < cells.size(); i++) { + String position = header.get(i); + if (!EXEMPTIONS.getOrDefault(keyword, List.of()).contains(position)) { + arguments.add(Arguments.of(keyword, position, "1".equals(cells.get(i)))); + } + } + } + } + return arguments.stream(); + } + + @ParameterizedTest(name = "{0} as {1}: {2}") + @MethodSource("duckDbKeywordNames") + void parsesLikeDuckDb(String keyword, String position, boolean duckDbParses) { + String sql = String.format(POSITIONS.get(position), keyword); + boolean parses; + try { + CCJSqlParserUtil.parse(sql, DUCKDB); + parses = true; + } catch (JSQLParserException e) { + parses = false; + } + assertEquals(duckDbParses, parses, sql); + } + + @Test + void keepsDefaultReservationWithoutTheDialect() throws JSQLParserException { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("SELECT settings, file FROM script")); + assertSqlCanBeParsedAndDeparsed("SELECT * FROM t PREWHERE a = 1 WHERE b = 2", true); + assertSqlCanBeParsedAndDeparsed("SELECT 1 FROM t MINUS SELECT 2 FROM u", true); + assertSqlCanBeParsedAndDeparsed("SELECT a desc FROM t", true); + } + + @Test + void parsesDuckDbSyntaxUsingItsKeywords() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT settings, file FROM script s WHERE s.top > 1", true, + DUCKDB); + assertSqlCanBeParsedAndDeparsed("UPDATE t SET a = 1", true, DUCKDB); + assertSqlCanBeParsedAndDeparsed("SELECT a FROM t USING SAMPLE 10%", true, DUCKDB); + assertSqlCanBeParsedAndDeparsed("COPY t TO 'out.csv' (FORMAT csv)", true, DUCKDB); + assertSqlCanBeParsedAndDeparsed("SELECT a AS from, t.where FROM t ORDER BY a DESC", true, + DUCKDB); + } + + @Test + void rejectsOnlyDuckDbReservedKeywordsAsFunctionNames() throws JSQLParserException { + for (String sql : Arrays.asList("SELECT order(1)", "SELECT where(1)", "SELECT table(1)")) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql, DUCKDB), sql); + } + assertSqlCanBeParsedAndDeparsed( + "SELECT coalesce(a, b), nullif(a, b), if(a > 1, 1, 2), greatest(a, b) FROM t", true, + DUCKDB); + } + + @Test + void rejectsDuckDbReservedKeywordsAsNames() { + for (String sql : Arrays.asList("SELECT desc FROM t", "SELECT 1 name", + "SELECT * FROM table")) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql, DUCKDB), sql); + } + } +} diff --git a/src/test/resources/net/sf/jsqlparser/parser/dialect/duckdb-keyword-names.csv b/src/test/resources/net/sf/jsqlparser/parser/dialect/duckdb-keyword-names.csv new file mode 100644 index 000000000..74e822a7d --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/parser/dialect/duckdb-keyword-names.csv @@ -0,0 +1,492 @@ +# Whether DuckDB 1.5.5 parses each of its keywords (duckdb_keywords()) in each name position, +# 1 or 0, with json_serialize_sql(). See DuckDbKeywordPolicyTest for the SQL of each position. +keyword,column,qualified_column,as_alias,bare_alias,table,table_alias,cte +abort,1,1,1,0,1,1,1 +absolute,1,1,1,0,1,1,1 +access,1,1,1,0,1,1,1 +action,1,1,1,0,1,1,1 +add,1,1,1,0,1,1,1 +admin,1,1,1,0,1,1,1 +after,1,1,1,0,1,1,1 +aggregate,1,1,1,0,1,1,1 +all,0,1,1,0,0,0,0 +also,1,1,1,0,1,1,1 +alter,1,1,1,0,1,1,1 +always,1,1,1,0,1,1,1 +analyse,0,1,1,0,0,0,0 +analyze,0,1,1,0,0,0,0 +and,0,1,1,0,0,0,0 +anti,0,1,1,0,0,0,0 +any,0,1,1,0,0,0,0 +array,0,1,1,0,0,0,0 +as,0,1,1,0,0,0,0 +asc,0,1,1,0,0,0,0 +asof,0,1,1,0,0,0,0 +assertion,1,1,1,0,1,1,1 +assignment,1,1,1,0,1,1,1 +asymmetric,0,1,1,0,0,0,0 +at,0,1,1,0,0,0,0 +attach,1,1,1,0,1,1,1 +attribute,1,1,1,0,1,1,1 +authorization,0,1,1,0,0,0,0 +backward,1,1,1,0,1,1,1 +before,1,1,1,0,1,1,1 +begin,1,1,1,0,1,1,1 +between,1,1,1,0,1,1,1 +bigint,1,1,1,0,1,1,1 +binary,0,1,1,0,0,0,0 +bit,1,1,1,0,1,1,1 +boolean,1,1,1,0,1,1,1 +both,0,1,1,0,0,0,0 +by,0,1,1,0,0,0,0 +cache,1,1,1,0,1,1,1 +call,1,1,1,0,1,1,1 +called,1,1,1,0,1,1,1 +cascade,1,1,1,0,1,1,1 +cascaded,1,1,1,0,1,1,1 +case,0,1,1,0,0,0,0 +cast,0,1,1,0,0,0,0 +catalog,1,1,1,0,1,1,1 +centuries,1,1,1,0,1,1,1 +century,1,1,1,0,1,1,1 +chain,1,1,1,0,1,1,1 +char,1,1,1,0,1,1,1 +character,1,1,1,0,1,1,1 +characteristics,1,1,1,0,1,1,1 +check,0,1,1,0,0,0,0 +checkpoint,1,1,1,0,1,1,1 +class,1,1,1,0,1,1,1 +close,1,1,1,0,1,1,1 +cluster,1,1,1,0,1,1,1 +coalesce,1,1,1,0,1,1,1 +collate,0,1,1,0,0,0,0 +collation,0,1,1,0,0,0,0 +column,0,1,1,0,0,0,0 +columns,1,1,1,0,1,1,1 +comment,1,1,1,0,1,1,1 +comments,1,1,1,0,1,1,1 +commit,1,1,1,0,1,1,1 +committed,1,1,1,0,1,1,1 +compression,1,1,1,0,1,1,1 +concurrently,0,1,1,0,0,0,0 +configuration,1,1,1,0,1,1,1 +conflict,1,1,1,0,1,1,1 +connection,1,1,1,0,1,1,1 +constraint,0,1,1,0,0,0,0 +constraints,1,1,1,0,1,1,1 +content,1,1,1,0,1,1,1 +continue,1,1,1,0,1,1,1 +conversion,1,1,1,0,1,1,1 +copy,1,1,1,0,1,1,1 +cost,1,1,1,0,1,1,1 +create,0,1,1,0,0,0,0 +cross,0,1,1,0,0,0,0 +csv,1,1,1,0,1,1,1 +cube,1,1,1,0,1,1,1 +current,1,1,1,0,1,1,1 +cursor,1,1,1,0,1,1,1 +cycle,1,1,1,0,1,1,1 +data,1,1,1,0,1,1,1 +database,1,1,1,0,1,1,1 +day,1,1,1,0,1,1,1 +days,1,1,1,0,1,1,1 +deallocate,1,1,1,0,1,1,1 +dec,1,1,1,0,1,1,1 +decade,1,1,1,0,1,1,1 +decades,1,1,1,0,1,1,1 +decimal,1,1,1,0,1,1,1 +declare,1,1,1,0,1,1,1 +default,1,1,1,0,0,0,0 +defaults,1,1,1,0,1,1,1 +deferrable,0,1,1,0,0,0,0 +deferred,1,1,1,0,1,1,1 +definer,1,1,1,0,1,1,1 +delete,1,1,1,0,1,1,1 +delimiter,1,1,1,0,1,1,1 +delimiters,1,1,1,0,1,1,1 +depends,1,1,1,0,1,1,1 +desc,0,1,1,0,0,0,0 +describe,0,1,1,0,0,0,0 +detach,1,1,1,0,1,1,1 +dictionary,1,1,1,0,1,1,1 +disable,1,1,1,0,1,1,1 +discard,1,1,1,0,1,1,1 +distinct,0,1,1,0,0,0,0 +do,0,1,1,0,0,0,0 +document,1,1,1,0,1,1,1 +domain,1,1,1,0,1,1,1 +double,1,1,1,0,1,1,1 +drop,1,1,1,0,1,1,1 +each,1,1,1,0,1,1,1 +else,0,1,1,0,0,0,0 +enable,1,1,1,0,1,1,1 +encoding,1,1,1,0,1,1,1 +encrypted,1,1,1,0,1,1,1 +end,0,1,1,0,0,0,0 +enum,1,1,1,0,1,1,1 +error,1,1,1,0,1,1,1 +escape,1,1,1,0,1,1,1 +event,1,1,1,0,1,1,1 +except,0,1,1,0,0,0,0 +exclude,1,1,1,0,1,1,1 +excluding,1,1,1,0,1,1,1 +exclusive,1,1,1,0,1,1,1 +execute,1,1,1,0,1,1,1 +exists,1,1,1,0,1,1,1 +explain,1,1,1,0,1,1,1 +export,1,1,1,0,1,1,1 +export_state,1,1,1,0,1,1,1 +extension,1,1,1,0,1,1,1 +extensions,1,1,1,0,1,1,1 +external,1,1,1,0,1,1,1 +extract,1,1,1,0,1,1,1 +false,1,1,1,0,0,0,0 +family,1,1,1,0,1,1,1 +fetch,0,1,1,0,0,0,0 +filter,1,1,1,0,1,1,1 +first,1,1,1,0,1,1,1 +float,1,1,1,0,1,1,1 +following,1,1,1,0,1,1,1 +for,0,1,1,0,0,0,0 +force,1,1,1,0,1,1,1 +foreign,0,1,1,0,0,0,0 +forward,1,1,1,0,1,1,1 +freeze,0,1,1,0,0,0,0 +from,0,1,1,0,0,0,0 +full,0,1,1,0,0,0,0 +function,1,1,1,0,1,1,1 +functions,1,1,1,0,1,1,1 +generated,1,1,1,0,1,1,1 +glob,0,1,1,0,0,0,0 +global,1,1,1,0,1,1,1 +grant,1,1,1,0,1,1,1 +granted,1,1,1,0,1,1,1 +group,0,1,1,0,0,0,0 +grouping,1,1,1,0,1,1,1 +grouping_id,1,1,1,0,1,1,1 +groups,1,1,1,0,1,1,1 +handler,1,1,1,0,1,1,1 +having,0,1,1,0,0,0,0 +header,1,1,1,0,1,1,1 +hold,1,1,1,0,1,1,1 +hour,1,1,1,0,1,1,1 +hours,1,1,1,0,1,1,1 +identity,1,1,1,0,1,1,1 +if,1,1,1,0,1,1,1 +ignore,1,1,1,0,1,1,1 +ilike,0,1,1,0,0,0,0 +immediate,1,1,1,0,1,1,1 +immutable,1,1,1,0,1,1,1 +implicit,1,1,1,0,1,1,1 +import,1,1,1,0,1,1,1 +in,0,1,1,0,0,0,0 +include,1,1,1,0,1,1,1 +including,1,1,1,0,1,1,1 +increment,1,1,1,0,1,1,1 +index,1,1,1,0,1,1,1 +indexes,1,1,1,0,1,1,1 +inherit,1,1,1,0,1,1,1 +inherits,1,1,1,0,1,1,1 +initially,0,1,1,0,0,0,0 +inline,1,1,1,0,1,1,1 +inner,0,1,1,0,0,0,0 +inout,1,1,1,0,1,1,1 +input,1,1,1,0,1,1,1 +insensitive,1,1,1,0,1,1,1 +insert,1,1,1,0,1,1,1 +install,1,1,1,0,1,1,1 +instead,1,1,1,0,1,1,1 +int,1,1,1,0,1,1,1 +integer,1,1,1,0,1,1,1 +intersect,0,1,1,0,0,0,0 +interval,1,1,1,0,1,1,1 +into,0,1,1,0,0,0,0 +invoker,1,1,1,0,1,1,1 +is,0,1,1,0,0,0,0 +isnull,0,1,1,1,0,0,0 +isolation,1,1,1,0,1,1,1 +join,0,1,1,0,0,0,0 +json,1,1,1,0,1,1,1 +key,1,1,1,0,1,1,1 +label,1,1,1,0,1,1,1 +lambda,0,1,1,0,0,0,0 +language,1,1,1,0,1,1,1 +large,1,1,1,0,1,1,1 +last,1,1,1,0,1,1,1 +lateral,0,1,1,0,0,0,0 +leading,0,1,1,0,0,0,0 +leakproof,1,1,1,0,1,1,1 +left,0,1,1,0,0,0,0 +level,1,1,1,0,1,1,1 +like,0,1,1,0,0,0,0 +limit,0,1,1,0,0,0,0 +listen,1,1,1,0,1,1,1 +load,1,1,1,0,1,1,1 +local,1,1,1,0,1,1,1 +location,1,1,1,0,1,1,1 +lock,1,1,1,0,1,1,1 +locked,1,1,1,0,1,1,1 +logged,1,1,1,0,1,1,1 +macro,1,1,1,0,1,1,1 +map,1,1,1,0,1,1,1 +mapping,1,1,1,0,1,1,1 +match,1,1,1,0,1,1,1 +matched,1,1,1,0,1,1,1 +materialized,1,1,1,0,1,1,1 +maxvalue,1,1,1,0,1,1,1 +merge,1,1,1,0,1,1,1 +method,1,1,1,0,1,1,1 +microsecond,1,1,1,0,1,1,1 +microseconds,1,1,1,0,1,1,1 +millennia,1,1,1,0,1,1,1 +millennium,1,1,1,0,1,1,1 +millisecond,1,1,1,0,1,1,1 +milliseconds,1,1,1,0,1,1,1 +minute,1,1,1,0,1,1,1 +minutes,1,1,1,0,1,1,1 +minvalue,1,1,1,0,1,1,1 +mode,1,1,1,0,1,1,1 +month,1,1,1,0,1,1,1 +months,1,1,1,0,1,1,1 +move,1,1,1,0,1,1,1 +name,1,1,1,0,1,1,1 +names,1,1,1,0,1,1,1 +national,1,1,1,0,1,1,1 +natural,0,1,1,0,0,0,0 +nchar,1,1,1,0,1,1,1 +new,1,1,1,0,1,1,1 +next,1,1,1,0,1,1,1 +no,1,1,1,0,1,1,1 +none,1,1,1,0,1,1,1 +not,0,1,1,0,0,0,0 +nothing,1,1,1,0,1,1,1 +notify,1,1,1,0,1,1,1 +notnull,0,1,1,1,0,0,0 +nowait,1,1,1,0,1,1,1 +null,1,1,1,0,0,0,0 +nullif,1,1,1,0,1,1,1 +nulls,1,1,1,0,1,1,1 +numeric,1,1,1,0,1,1,1 +object,1,1,1,0,1,1,1 +of,1,1,1,0,1,1,1 +off,1,1,1,0,1,1,1 +offset,0,1,1,0,0,0,0 +oids,1,1,1,0,1,1,1 +old,1,1,1,0,1,1,1 +on,0,1,1,0,0,0,0 +only,0,1,1,0,0,0,0 +operator,1,1,1,0,1,1,1 +option,1,1,1,0,1,1,1 +options,1,1,1,0,1,1,1 +or,0,1,1,0,0,0,0 +order,0,1,1,0,0,0,0 +ordinality,1,1,1,0,1,1,1 +others,1,1,1,0,1,1,1 +out,1,1,1,0,1,1,1 +outer,0,1,1,0,0,0,0 +over,1,1,1,0,1,1,1 +overlaps,0,1,1,0,0,0,0 +overlay,1,1,1,0,1,1,1 +overriding,1,1,1,0,1,1,1 +owned,1,1,1,0,1,1,1 +owner,1,1,1,0,1,1,1 +parallel,1,1,1,0,1,1,1 +parser,1,1,1,0,1,1,1 +partial,1,1,1,0,1,1,1 +partition,1,1,1,0,1,1,1 +partitioned,1,1,1,0,1,1,1 +passing,1,1,1,0,1,1,1 +password,1,1,1,0,1,1,1 +percent,1,1,1,0,1,1,1 +persistent,1,1,1,0,1,1,1 +pivot,0,1,1,0,0,0,0 +pivot_longer,0,1,1,0,0,0,0 +pivot_wider,0,1,1,0,0,0,0 +placing,0,1,1,0,0,0,0 +plans,1,1,1,0,1,1,1 +policy,1,1,1,0,1,1,1 +position,1,1,1,0,1,1,1 +positional,0,1,1,0,0,0,0 +pragma,1,1,1,0,1,1,1 +preceding,1,1,1,0,1,1,1 +precision,1,1,1,0,1,1,1 +prepare,1,1,1,0,1,1,1 +prepared,1,1,1,0,1,1,1 +preserve,1,1,1,0,1,1,1 +primary,0,1,1,0,0,0,0 +prior,1,1,1,0,1,1,1 +privileges,1,1,1,0,1,1,1 +procedural,1,1,1,0,1,1,1 +procedure,1,1,1,0,1,1,1 +program,1,1,1,0,1,1,1 +publication,1,1,1,0,1,1,1 +qualify,0,1,1,0,0,0,0 +quarter,1,1,1,0,1,1,1 +quarters,1,1,1,0,1,1,1 +quote,1,1,1,0,1,1,1 +range,1,1,1,0,1,1,1 +read,1,1,1,0,1,1,1 +real,1,1,1,0,1,1,1 +reassign,1,1,1,0,1,1,1 +recheck,1,1,1,0,1,1,1 +recursive,1,1,1,0,1,1,1 +ref,1,1,1,0,1,1,1 +references,0,1,1,0,0,0,0 +referencing,1,1,1,0,1,1,1 +refresh,1,1,1,0,1,1,1 +reindex,1,1,1,0,1,1,1 +relative,1,1,1,0,1,1,1 +release,1,1,1,0,1,1,1 +rename,1,1,1,0,1,1,1 +repeatable,1,1,1,0,1,1,1 +replace,1,1,1,0,1,1,1 +replica,1,1,1,0,1,1,1 +reset,1,1,1,0,1,1,1 +respect,1,1,1,0,1,1,1 +restart,1,1,1,0,1,1,1 +restrict,1,1,1,0,1,1,1 +returning,0,1,1,0,0,0,0 +returns,1,1,1,0,1,1,1 +revoke,1,1,1,0,1,1,1 +right,0,1,1,0,0,0,0 +role,1,1,1,0,1,1,1 +rollback,1,1,1,0,1,1,1 +rollup,1,1,1,0,1,1,1 +row,1,1,1,0,1,1,1 +rows,1,1,1,0,1,1,1 +rule,1,1,1,0,1,1,1 +sample,1,1,1,0,1,1,1 +savepoint,1,1,1,0,1,1,1 +schema,1,1,1,0,1,1,1 +schemas,1,1,1,0,1,1,1 +scope,1,1,1,0,1,1,1 +scroll,1,1,1,0,1,1,1 +search,1,1,1,0,1,1,1 +second,1,1,1,0,1,1,1 +seconds,1,1,1,0,1,1,1 +secret,1,1,1,0,1,1,1 +security,1,1,1,0,1,1,1 +select,0,1,1,0,0,0,0 +semi,0,1,1,0,0,0,0 +sequence,1,1,1,0,1,1,1 +sequences,1,1,1,0,1,1,1 +serializable,1,1,1,0,1,1,1 +server,1,1,1,0,1,1,1 +session,1,1,1,0,1,1,1 +set,1,1,1,0,1,1,1 +setof,1,1,1,0,1,1,1 +sets,1,1,1,0,1,1,1 +share,1,1,1,0,1,1,1 +show,0,1,1,0,0,0,0 +similar,0,1,1,0,0,0,0 +simple,1,1,1,0,1,1,1 +skip,1,1,1,0,1,1,1 +smallint,1,1,1,0,1,1,1 +snapshot,1,1,1,0,1,1,1 +some,0,1,1,0,0,0,0 +sorted,1,1,1,0,1,1,1 +source,1,1,1,0,1,1,1 +sql,1,1,1,0,1,1,1 +stable,1,1,1,0,1,1,1 +standalone,1,1,1,0,1,1,1 +start,1,1,1,0,1,1,1 +statement,1,1,1,0,1,1,1 +statistics,1,1,1,0,1,1,1 +stdin,1,1,1,0,1,1,1 +stdout,1,1,1,0,1,1,1 +storage,1,1,1,0,1,1,1 +stored,1,1,1,0,1,1,1 +strict,1,1,1,0,1,1,1 +strip,1,1,1,0,1,1,1 +struct,1,1,1,0,1,1,1 +subscription,1,1,1,0,1,1,1 +substring,1,1,1,0,1,1,1 +summarize,0,1,1,0,0,0,0 +symmetric,0,1,1,0,0,0,0 +sysid,1,1,1,0,1,1,1 +system,1,1,1,0,1,1,1 +table,0,1,1,0,0,0,0 +tables,1,1,1,0,1,1,1 +tablesample,0,1,1,0,0,0,0 +tablespace,1,1,1,0,1,1,1 +target,1,1,1,0,1,1,1 +temp,1,1,1,0,1,1,1 +template,1,1,1,0,1,1,1 +temporary,1,1,1,0,1,1,1 +text,1,1,1,0,1,1,1 +then,0,1,1,0,0,0,0 +ties,1,1,1,0,1,1,1 +time,1,1,1,0,1,1,1 +timestamp,1,1,1,0,1,1,1 +to,0,1,1,0,0,0,0 +trailing,0,1,1,0,0,0,0 +transaction,1,1,1,0,1,1,1 +transform,1,1,1,0,1,1,1 +treat,1,1,1,0,1,1,1 +trigger,1,1,1,0,1,1,1 +trim,1,1,1,0,1,1,1 +true,1,1,1,0,0,0,0 +truncate,1,1,1,0,1,1,1 +trusted,1,1,1,0,1,1,1 +try_cast,1,1,1,0,1,1,1 +type,1,1,1,0,1,1,1 +types,1,1,1,0,1,1,1 +unbounded,1,1,1,0,1,1,1 +uncommitted,1,1,1,0,1,1,1 +unencrypted,1,1,1,0,1,1,1 +union,0,1,1,0,0,0,0 +unique,0,1,1,0,0,0,0 +unknown,1,1,1,0,1,1,1 +unlisten,1,1,1,0,1,1,1 +unlogged,1,1,1,0,1,1,1 +unpack,0,1,1,0,0,0,0 +unpivot,0,1,1,0,0,0,0 +until,1,1,1,0,1,1,1 +update,1,1,1,0,1,1,1 +use,1,1,1,0,1,1,1 +user,1,1,1,0,1,1,1 +using,0,1,1,0,0,0,0 +vacuum,1,1,1,0,1,1,1 +valid,1,1,1,0,1,1,1 +validate,1,1,1,0,1,1,1 +validator,1,1,1,0,1,1,1 +value,1,1,1,0,1,1,1 +values,1,1,1,0,1,1,1 +varchar,1,1,1,0,1,1,1 +variable,1,1,1,0,1,1,1 +variadic,0,1,1,0,0,0,0 +varying,1,1,1,0,1,1,1 +verbose,0,1,1,0,0,0,0 +version,1,1,1,0,1,1,1 +view,1,1,1,0,1,1,1 +views,1,1,1,0,1,1,1 +virtual,1,1,1,0,1,1,1 +volatile,1,1,1,0,1,1,1 +week,1,1,1,0,1,1,1 +weeks,1,1,1,0,1,1,1 +when,0,1,1,0,0,0,0 +where,0,1,1,0,0,0,0 +whitespace,1,1,1,0,1,1,1 +window,0,1,1,0,0,0,0 +with,0,1,1,0,0,0,0 +within,1,1,1,0,1,1,1 +without,1,1,1,0,1,1,1 +work,1,1,1,0,1,1,1 +wrapper,1,1,1,0,1,1,1 +write,1,1,1,0,1,1,1 +xml,1,1,1,0,1,1,1 +xmlattributes,1,1,1,0,1,1,1 +xmlconcat,1,1,1,0,1,1,1 +xmlelement,1,1,1,0,1,1,1 +xmlexists,1,1,1,0,1,1,1 +xmlforest,1,1,1,0,1,1,1 +xmlnamespaces,1,1,1,0,1,1,1 +xmlparse,1,1,1,0,1,1,1 +xmlpi,1,1,1,0,1,1,1 +xmlroot,1,1,1,0,1,1,1 +xmlserialize,1,1,1,0,1,1,1 +xmltable,1,1,1,0,1,1,1 +year,1,1,1,0,1,1,1 +years,1,1,1,0,1,1,1 +yes,1,1,1,0,1,1,1 +zone,1,1,1,0,1,1,1