parser grammar ClickHouseParser; options { tokenVocab = ClickHouseLexer; } // Top-level statements queryStmt: query (INTO OUTFILE STRING_LITERAL)? (FORMAT identifierOrNull)? (SEMICOLON)? | insertStmt; query : alterStmt // DDL | attachStmt // DDL | checkStmt | createStmt // DDL | describeStmt | dropStmt // DDL | existsStmt | explainStmt | killStmt // DDL | optimizeStmt // DDL | renameStmt // DDL | selectUnionStmt | setStmt | showStmt | systemStmt | truncateStmt // DDL | useStmt | watchStmt ; // ALTER statement alterStmt : ALTER TABLE tableIdentifier clusterClause? alterTableClause (COMMA alterTableClause)* # AlterTableStmt ; alterTableClause : ADD COLUMN (IF NOT EXISTS)? tableColumnDfnt (AFTER nestedIdentifier)? # AlterTableClauseAddColumn | ADD INDEX (IF NOT EXISTS)? tableIndexDfnt (AFTER nestedIdentifier)? # AlterTableClauseAddIndex | ADD PROJECTION (IF NOT EXISTS)? tableProjectionDfnt (AFTER nestedIdentifier)? # AlterTableClauseAddProjection | ATTACH partitionClause (FROM tableIdentifier)? # AlterTableClauseAttach | CLEAR COLUMN (IF EXISTS)? nestedIdentifier (IN partitionClause)? # AlterTableClauseClearColumn | CLEAR INDEX (IF EXISTS)? nestedIdentifier (IN partitionClause)? # AlterTableClauseClearIndex | CLEAR PROJECTION (IF EXISTS)? nestedIdentifier (IN partitionClause)? # AlterTableClauseClearProjection | COMMENT COLUMN (IF EXISTS)? nestedIdentifier STRING_LITERAL # AlterTableClauseComment | DELETE WHERE columnExpr # AlterTableClauseDelete | DETACH partitionClause # AlterTableClauseDetach | DROP COLUMN (IF EXISTS)? nestedIdentifier # AlterTableClauseDropColumn | DROP INDEX (IF EXISTS)? nestedIdentifier # AlterTableClauseDropIndex | DROP PROJECTION (IF EXISTS)? nestedIdentifier # AlterTableClauseDropProjection | DROP partitionClause # AlterTableClauseDropPartition | FREEZE partitionClause? # AlterTableClauseFreezePartition | MATERIALIZE INDEX (IF EXISTS)? nestedIdentifier (IN partitionClause)? # AlterTableClauseMaterializeIndex | MATERIALIZE PROJECTION (IF EXISTS)? nestedIdentifier (IN partitionClause)? # AlterTableClauseMaterializeProjection | MODIFY COLUMN (IF EXISTS)? nestedIdentifier codecExpr # AlterTableClauseModifyCodec | MODIFY COLUMN (IF EXISTS)? nestedIdentifier COMMENT STRING_LITERAL # AlterTableClauseModifyComment | MODIFY COLUMN (IF EXISTS)? nestedIdentifier REMOVE tableColumnPropertyType # AlterTableClauseModifyRemove | MODIFY COLUMN (IF EXISTS)? tableColumnDfnt # AlterTableClauseModify | MODIFY ORDER BY columnExpr # AlterTableClauseModifyOrderBy | MODIFY ttlClause # AlterTableClauseModifyTTL | MOVE partitionClause ( TO DISK STRING_LITERAL | TO VOLUME STRING_LITERAL | TO TABLE tableIdentifier ) # AlterTableClauseMovePartition | REMOVE TTL # AlterTableClauseRemoveTTL | RENAME COLUMN (IF EXISTS)? nestedIdentifier TO nestedIdentifier # AlterTableClauseRename | REPLACE partitionClause FROM tableIdentifier # AlterTableClauseReplace | UPDATE assignmentExprList whereClause # AlterTableClauseUpdate ; assignmentExprList: assignmentExpr (COMMA assignmentExpr)*; assignmentExpr: nestedIdentifier EQ_SINGLE columnExpr; tableColumnPropertyType: ALIAS | CODEC | COMMENT | DEFAULT | MATERIALIZED | TTL; partitionClause : PARTITION columnExpr // actually we expect here any form of tuple of literals | PARTITION ID STRING_LITERAL ; // ATTACH statement attachStmt : ATTACH DICTIONARY tableIdentifier clusterClause? # AttachDictionaryStmt ; // CHECK statement checkStmt: CHECK TABLE tableIdentifier partitionClause?; // CREATE statement createStmt : (ATTACH | CREATE) DATABASE (IF NOT EXISTS)? databaseIdentifier clusterClause? engineExpr? # CreateDatabaseStmt | (ATTACH | CREATE (OR REPLACE)? | REPLACE) DICTIONARY (IF NOT EXISTS)? tableIdentifier uuidClause? clusterClause? dictionarySchemaClause dictionaryEngineClause # CreateDictionaryStmt | (ATTACH | CREATE) LIVE VIEW (IF NOT EXISTS)? tableIdentifier uuidClause? clusterClause? (WITH TIMEOUT DECIMAL_LITERAL?)? destinationClause? tableSchemaClause? subqueryClause # CreateLiveViewStmt | (ATTACH | CREATE) MATERIALIZED VIEW (IF NOT EXISTS)? tableIdentifier uuidClause? clusterClause? tableSchemaClause? (destinationClause | engineClause POPULATE?) subqueryClause # CreateMaterializedViewStmt | (ATTACH | CREATE (OR REPLACE)? | REPLACE) TEMPORARY? TABLE (IF NOT EXISTS)? tableIdentifier uuidClause? clusterClause? tableSchemaClause? engineClause? subqueryClause? # CreateTableStmt | (ATTACH | CREATE) (OR REPLACE)? VIEW (IF NOT EXISTS)? tableIdentifier uuidClause? clusterClause? tableSchemaClause? subqueryClause # CreateViewStmt ; dictionarySchemaClause: LPAREN dictionaryAttrDfnt (COMMA dictionaryAttrDfnt)* RPAREN; dictionaryAttrDfnt locals [std::set attrs]: identifier columnTypeExpr ( {!$attrs.count("default")}? DEFAULT literal {$attrs.insert("default");} | {!$attrs.count("expression")}? EXPRESSION columnExpr {$attrs.insert("expression");} | {!$attrs.count("hierarchical")}? HIERARCHICAL {$attrs.insert("hierarchical");} | {!$attrs.count("injective")}? INJECTIVE {$attrs.insert("injective");} | {!$attrs.count("is_object_id")}? IS_OBJECT_ID {$attrs.insert("is_object_id");} )* ; dictionaryEngineClause locals [std::set clauses]: dictionaryPrimaryKeyClause? ( {!$clauses.count("source")}? sourceClause {$clauses.insert("source");} | {!$clauses.count("lifetime")}? lifetimeClause {$clauses.insert("lifetime");} | {!$clauses.count("layout")}? layoutClause {$clauses.insert("layout");} | {!$clauses.count("range")}? rangeClause {$clauses.insert("range");} | {!$clauses.count("settings")}? dictionarySettingsClause {$clauses.insert("settings");} )* ; dictionaryPrimaryKeyClause: PRIMARY KEY columnExprList; dictionaryArgExpr: identifier (identifier (LPAREN RPAREN)? | literal); sourceClause: SOURCE LPAREN identifier LPAREN dictionaryArgExpr* RPAREN RPAREN; lifetimeClause: LIFETIME LPAREN ( DECIMAL_LITERAL | MIN DECIMAL_LITERAL MAX DECIMAL_LITERAL | MAX DECIMAL_LITERAL MIN DECIMAL_LITERAL ) RPAREN; layoutClause: LAYOUT LPAREN identifier LPAREN dictionaryArgExpr* RPAREN RPAREN; rangeClause: RANGE LPAREN (MIN identifier MAX identifier | MAX identifier MIN identifier) RPAREN; dictionarySettingsClause: SETTINGS LPAREN settingExprList RPAREN; clusterClause: ON CLUSTER (identifier | STRING_LITERAL); uuidClause: UUID STRING_LITERAL; destinationClause: TO tableIdentifier; subqueryClause: AS selectUnionStmt; tableSchemaClause : LPAREN tableElementExpr (COMMA tableElementExpr)* RPAREN # SchemaDescriptionClause | AS tableIdentifier # SchemaAsTableClause | AS tableFunctionExpr # SchemaAsFunctionClause ; engineClause locals [std::set clauses]: engineExpr ( {!$clauses.count("orderByClause")}? orderByClause {$clauses.insert("orderByClause");} | {!$clauses.count("partitionByClause")}? partitionByClause {$clauses.insert("partitionByClause");} | {!$clauses.count("primaryKeyClause")}? primaryKeyClause {$clauses.insert("primaryKeyClause");} | {!$clauses.count("sampleByClause")}? sampleByClause {$clauses.insert("sampleByClause");} | {!$clauses.count("ttlClause")}? ttlClause {$clauses.insert("ttlClause");} | {!$clauses.count("settingsClause")}? settingsClause {$clauses.insert("settingsClause");} )* ; partitionByClause: PARTITION BY columnExpr; primaryKeyClause: PRIMARY KEY columnExpr; sampleByClause: SAMPLE BY columnExpr; ttlClause: TTL ttlExpr (COMMA ttlExpr)*; engineExpr: ENGINE EQ_SINGLE? identifierOrNull (LPAREN columnExprList? RPAREN)?; tableElementExpr : tableColumnDfnt # TableElementExprColumn | CONSTRAINT identifier CHECK columnExpr # TableElementExprConstraint | INDEX tableIndexDfnt # TableElementExprIndex | PROJECTION tableProjectionDfnt # TableElementExprProjection ; tableColumnDfnt : nestedIdentifier columnTypeExpr tableColumnPropertyExpr? (COMMENT STRING_LITERAL)? codecExpr? (TTL columnExpr)? | nestedIdentifier columnTypeExpr? tableColumnPropertyExpr (COMMENT STRING_LITERAL)? codecExpr? (TTL columnExpr)? ; tableColumnPropertyExpr: (DEFAULT | MATERIALIZED | ALIAS) columnExpr; tableIndexDfnt: nestedIdentifier columnExpr TYPE columnTypeExpr GRANULARITY DECIMAL_LITERAL; tableProjectionDfnt: nestedIdentifier projectionSelectStmt; codecExpr: CODEC LPAREN codecArgExpr (COMMA codecArgExpr)* RPAREN; codecArgExpr: identifier (LPAREN columnExprList? RPAREN)?; ttlExpr: columnExpr (DELETE | TO DISK STRING_LITERAL | TO VOLUME STRING_LITERAL)?; // DESCRIBE statement describeStmt: (DESCRIBE | DESC) TABLE? tableExpr; // DROP statement dropStmt : (DETACH | DROP) DATABASE (IF EXISTS)? databaseIdentifier clusterClause? # DropDatabaseStmt | (DETACH | DROP) (DICTIONARY | TEMPORARY? TABLE | VIEW) (IF EXISTS)? tableIdentifier clusterClause? (NO DELAY)? # DropTableStmt ; // EXISTS statement existsStmt : EXISTS DATABASE databaseIdentifier # ExistsDatabaseStmt | EXISTS (DICTIONARY | TEMPORARY? TABLE | VIEW)? tableIdentifier # ExistsTableStmt ; // EXPLAIN statement explainStmt : EXPLAIN AST query # ExplainASTStmt | EXPLAIN SYNTAX query # ExplainSyntaxStmt ; // INSERT statement insertStmt: INSERT INTO TABLE? (tableIdentifier | FUNCTION tableFunctionExpr) columnsClause? dataClause; columnsClause: LPAREN nestedIdentifier (COMMA nestedIdentifier)* RPAREN; dataClause : FORMAT identifier # DataClauseFormat | VALUES # DataClauseValues | selectUnionStmt SEMICOLON? EOF # DataClauseSelect ; // KILL statement killStmt : KILL MUTATION clusterClause? whereClause (SYNC | ASYNC | TEST)? # KillMutationStmt ; // OPTIMIZE statement optimizeStmt: OPTIMIZE TABLE tableIdentifier clusterClause? partitionClause? FINAL? DEDUPLICATE?; // RENAME statement renameStmt: RENAME TABLE tableIdentifier TO tableIdentifier (COMMA tableIdentifier TO tableIdentifier)* clusterClause?; // PROJECTION SELECT statement projectionSelectStmt: LPAREN withClause? SELECT columnExprList groupByClause? projectionOrderByClause? RPAREN ; // SELECT statement selectUnionStmt: selectStmtWithParens (UNION ALL selectStmtWithParens)*; selectStmtWithParens: selectStmt | LPAREN selectUnionStmt RPAREN; selectStmt: withClause? SELECT DISTINCT? topClause? columnExprList fromClause? arrayJoinClause? prewhereClause? whereClause? groupByClause? (WITH (CUBE | ROLLUP))? (WITH TOTALS)? havingClause? orderByClause? limitByClause? limitClause? settingsClause? ; withClause: WITH columnExprList; topClause: TOP DECIMAL_LITERAL (WITH TIES)?; fromClause: FROM joinExpr; arrayJoinClause: (LEFT | INNER)? ARRAY JOIN columnExprList; prewhereClause: PREWHERE columnExpr; whereClause: WHERE columnExpr; groupByClause: GROUP BY ((CUBE | ROLLUP) LPAREN columnExprList RPAREN | columnExprList); havingClause: HAVING columnExpr; orderByClause: ORDER BY orderExprList; projectionOrderByClause: ORDER BY columnExprList; limitByClause: LIMIT limitExpr BY columnExprList; limitClause: LIMIT limitExpr (WITH TIES)?; settingsClause: SETTINGS settingExprList; joinExpr : joinExpr (GLOBAL | LOCAL)? joinOp? JOIN joinExpr joinConstraintClause # JoinExprOp | joinExpr joinOpCross joinExpr # JoinExprCrossOp | tableExpr FINAL? sampleClause? # JoinExprTable | LPAREN joinExpr RPAREN # JoinExprParens ; joinOp : ((ALL | ANY | ASOF)? INNER | INNER (ALL | ANY | ASOF)? | (ALL | ANY | ASOF)) # JoinOpInner | ( (SEMI | ALL | ANTI | ANY | ASOF)? (LEFT | RIGHT) OUTER? | (LEFT | RIGHT) OUTER? (SEMI | ALL | ANTI | ANY | ASOF)? ) # JoinOpLeftRight | ((ALL | ANY)? FULL OUTER? | FULL OUTER? (ALL | ANY)?) # JoinOpFull ; joinOpCross : (GLOBAL|LOCAL)? CROSS JOIN | COMMA ; joinConstraintClause : ON columnExprList | USING LPAREN columnExprList RPAREN | USING columnExprList ; sampleClause: SAMPLE ratioExpr (OFFSET ratioExpr)?; limitExpr: columnExpr ((COMMA | OFFSET) columnExpr)?; orderExprList: orderExpr (COMMA orderExpr)*; orderExpr: columnExpr (ASCENDING | DESCENDING | DESC)? (NULLS (FIRST | LAST))? (COLLATE STRING_LITERAL)?; ratioExpr: numberLiteral (SLASH numberLiteral)?; settingExprList: settingExpr (COMMA settingExpr)*; settingExpr: identifier EQ_SINGLE literal; // SET statement setStmt: SET settingExprList; // SHOW statements showStmt : SHOW CREATE DATABASE databaseIdentifier # showCreateDatabaseStmt | SHOW CREATE DICTIONARY tableIdentifier # showCreateDictionaryStmt | SHOW CREATE TEMPORARY? TABLE? tableIdentifier # showCreateTableStmt | SHOW DATABASES # showDatabasesStmt | SHOW DICTIONARIES (FROM databaseIdentifier)? # showDictionariesStmt | SHOW TEMPORARY? TABLES ((FROM | IN) databaseIdentifier)? (LIKE STRING_LITERAL | whereClause)? limitClause? # showTablesStmt ; // SYSTEM statements systemStmt : SYSTEM FLUSH DISTRIBUTED tableIdentifier | SYSTEM FLUSH LOGS | SYSTEM RELOAD DICTIONARIES | SYSTEM RELOAD DICTIONARY tableIdentifier | SYSTEM (START | STOP) (DISTRIBUTED SENDS | FETCHES | TTL? MERGES) tableIdentifier | SYSTEM (START | STOP) REPLICATED SENDS | SYSTEM SYNC REPLICA tableIdentifier ; // TRUNCATE statements truncateStmt: TRUNCATE TEMPORARY? TABLE? (IF EXISTS)? tableIdentifier clusterClause?; // USE statement useStmt: USE databaseIdentifier; // WATCH statement watchStmt: WATCH tableIdentifier EVENTS? (LIMIT DECIMAL_LITERAL)?; // Columns columnTypeExpr : identifier # ColumnTypeExprSimple // UInt64 | identifier LPAREN identifier columnTypeExpr (COMMA identifier columnTypeExpr)* RPAREN # ColumnTypeExprNested // Nested | identifier LPAREN enumValue (COMMA enumValue)* RPAREN # ColumnTypeExprEnum // Enum | identifier LPAREN columnTypeExpr (COMMA columnTypeExpr)* RPAREN # ColumnTypeExprComplex // Array, Tuple | identifier LPAREN columnExprList? RPAREN # ColumnTypeExprParam // FixedString(N) ; columnExprList: columnsExpr (COMMA columnsExpr)*; columnsExpr : (tableIdentifier DOT)? ASTERISK # ColumnsExprAsterisk | LPAREN selectUnionStmt RPAREN # ColumnsExprSubquery // NOTE: asterisk and subquery goes before |columnExpr| so that we can mark them as multi-column expressions. | columnExpr # ColumnsExprColumn ; columnExpr : CASE columnExpr? (WHEN columnExpr THEN columnExpr)+ (ELSE columnExpr)? END # ColumnExprCase | CAST LPAREN columnExpr AS columnTypeExpr RPAREN # ColumnExprCast | DATE STRING_LITERAL # ColumnExprDate | EXTRACT LPAREN interval FROM columnExpr RPAREN # ColumnExprExtract | INTERVAL columnExpr interval # ColumnExprInterval | SUBSTRING LPAREN columnExpr FROM columnExpr (FOR columnExpr)? RPAREN # ColumnExprSubstring | TIMESTAMP STRING_LITERAL # ColumnExprTimestamp | TRIM LPAREN (BOTH | LEADING | TRAILING) STRING_LITERAL FROM columnExpr RPAREN # ColumnExprTrim | identifier (LPAREN columnExprList? RPAREN)? LPAREN DISTINCT? columnArgList? RPAREN # ColumnExprFunction | literal # ColumnExprLiteral // FIXME(ilezhankin): this part looks very ugly, maybe there is another way to express it | columnExpr LBRACKET columnExpr RBRACKET # ColumnExprArrayAccess | columnExpr DOT DECIMAL_LITERAL # ColumnExprTupleAccess | DASH columnExpr # ColumnExprNegate | columnExpr ( ASTERISK // multiply | SLASH // divide | PERCENT // modulo ) columnExpr # ColumnExprPrecedence1 | columnExpr ( PLUS // plus | DASH // minus | CONCAT // concat ) columnExpr # ColumnExprPrecedence2 | columnExpr ( EQ_DOUBLE // equals | EQ_SINGLE // equals | NOT_EQ // notEquals | LE // lessOrEquals | GE // greaterOrEquals | LT // less | GT // greater | GLOBAL? NOT? IN // in, notIn, globalIn, globalNotIn | NOT? (LIKE | ILIKE) // like, notLike, ilike, notILike ) columnExpr # ColumnExprPrecedence3 | columnExpr IS NOT? NULL_SQL # ColumnExprIsNull | NOT columnExpr # ColumnExprNot | columnExpr AND columnExpr # ColumnExprAnd | columnExpr OR columnExpr # ColumnExprOr // TODO(ilezhankin): `BETWEEN a AND b AND c` is parsed in a wrong way: `BETWEEN (a AND b) AND c` | columnExpr NOT? BETWEEN columnExpr AND columnExpr # ColumnExprBetween | columnExpr QUERY columnExpr COLON columnExpr # ColumnExprTernaryOp | columnExpr (alias | AS identifier) # ColumnExprAlias | (tableIdentifier DOT)? ASTERISK # ColumnExprAsterisk // single-column only | LPAREN selectUnionStmt RPAREN # ColumnExprSubquery // single-column only | LPAREN columnExpr RPAREN # ColumnExprParens // single-column only | LPAREN columnExprList RPAREN # ColumnExprTuple | LBRACKET columnExprList? RBRACKET # ColumnExprArray | columnIdentifier # ColumnExprIdentifier ; columnArgList: columnArgExpr (COMMA columnArgExpr)*; columnArgExpr: columnLambdaExpr | columnExpr; columnLambdaExpr: ( LPAREN identifier (COMMA identifier)* RPAREN | identifier (COMMA identifier)* ) ARROW columnExpr ; columnIdentifier: (tableIdentifier DOT)? nestedIdentifier; nestedIdentifier: identifier (DOT identifier)?; // Tables tableExpr : tableIdentifier # TableExprIdentifier | tableFunctionExpr # TableExprFunction | LPAREN selectUnionStmt RPAREN # TableExprSubquery | tableExpr (alias | AS identifier) # TableExprAlias ; tableFunctionExpr: identifier LPAREN tableArgList? RPAREN; tableIdentifier: (databaseIdentifier DOT)? identifier; tableArgList: tableArgExpr (COMMA tableArgExpr)*; tableArgExpr : nestedIdentifier | tableFunctionExpr | literal ; // Databases databaseIdentifier: identifier; // Basics floatingLiteral : FLOATING_LITERAL | DOT (DECIMAL_LITERAL | OCTAL_LITERAL) | DECIMAL_LITERAL DOT (DECIMAL_LITERAL | OCTAL_LITERAL)? // can't move this to the lexer or it will break nested tuple access: t.1.2 ; numberLiteral: (PLUS | DASH)? (floatingLiteral | OCTAL_LITERAL | DECIMAL_LITERAL | HEXADECIMAL_LITERAL | INF | NAN_SQL); literal : numberLiteral | STRING_LITERAL | NULL_SQL ; interval: SECOND | MINUTE | HOUR | DAY | WEEK | MONTH | QUARTER | YEAR; keyword // except NULL_SQL, INF, NAN_SQL : AFTER | ALIAS | ALL | ALTER | AND | ANTI | ANY | ARRAY | AS | ASCENDING | ASOF | AST | ASYNC | ATTACH | BETWEEN | BOTH | BY | CASE | CAST | CHECK | CLEAR | CLUSTER | CODEC | COLLATE | COLUMN | COMMENT | CONSTRAINT | CREATE | CROSS | CUBE | DATABASE | DATABASES | DATE | DEDUPLICATE | DEFAULT | DELAY | DELETE | DESCRIBE | DESC | DESCENDING | DETACH | DICTIONARIES | DICTIONARY | DISK | DISTINCT | DISTRIBUTED | DROP | ELSE | END | ENGINE | EVENTS | EXISTS | EXPLAIN | EXPRESSION | EXTRACT | FETCHES | FINAL | FIRST | FLUSH | FOR | FORMAT | FREEZE | FROM | FULL | FUNCTION | GLOBAL | GRANULARITY | GROUP | HAVING | HIERARCHICAL | ID | IF | ILIKE | IN | INDEX | INJECTIVE | INNER | INSERT | INTERVAL | INTO | IS | IS_OBJECT_ID | JOIN | JSON_FALSE | JSON_TRUE | KEY | KILL | LAST | LAYOUT | LEADING | LEFT | LIFETIME | LIKE | LIMIT | LIVE | LOCAL | LOGS | MATERIALIZE | MATERIALIZED | MAX | MERGES | MIN | MODIFY | MOVE | MUTATION | NO | NOT | NULLS | OFFSET | ON | OPTIMIZE | OR | ORDER | OUTER | OUTFILE | PARTITION | POPULATE | PREWHERE | PRIMARY | RANGE | RELOAD | REMOVE | RENAME | REPLACE | REPLICA | REPLICATED | RIGHT | ROLLUP | SAMPLE | SELECT | SEMI | SENDS | SET | SETTINGS | SHOW | SOURCE | START | STOP | SUBSTRING | SYNC | SYNTAX | SYSTEM | TABLE | TABLES | TEMPORARY | TEST | THEN | TIES | TIMEOUT | TIMESTAMP | TOTALS | TRAILING | TRIM | TRUNCATE | TO | TOP | TTL | TYPE | UNION | UPDATE | USE | USING | UUID | VALUES | VIEW | VOLUME | WATCH | WHEN | WHERE | WITH ; keywordForAlias : DATE | FIRST | ID | KEY ; alias: IDENTIFIER | keywordForAlias; // |interval| can't be an alias, otherwise 'INTERVAL 1 SOMETHING' becomes ambiguous. identifier: IDENTIFIER | interval | keyword; identifierOrNull: identifier | NULL_SQL; // NULL_SQL can be only 'Null' here. enumValue: STRING_LITERAL EQ_SINGLE numberLiteral;