diff --git a/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/parser/ClickHouseSqlParserTest.java b/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/parser/ClickHouseSqlParserFacadeTest.java similarity index 99% rename from clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/parser/ClickHouseSqlParserTest.java rename to clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/parser/ClickHouseSqlParserFacadeTest.java index d637c3923..cfcfb211e 100644 --- a/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/parser/ClickHouseSqlParserTest.java +++ b/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/parser/ClickHouseSqlParserFacadeTest.java @@ -20,13 +20,13 @@ import com.clickhouse.client.ClickHouseConfig; -public class ClickHouseSqlParserTest { +public class ClickHouseSqlParserFacadeTest { private ClickHouseSqlStatement[] parse(String sql) { return ClickHouseSqlParser.parse(sql, new ClickHouseConfig()); } private String loadSql(String file) { - InputStream inputStream = ClickHouseSqlParserTest.class.getResourceAsStream("/sqls/" + file); + InputStream inputStream = ClickHouseSqlParserFacadeTest.class.getResourceAsStream("/sqls/" + file); StringBuilder sql = new StringBuilder(); try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) { diff --git a/client-v2/src/test/java/com/clickhouse/client/datatypes/DataTypeTests.java b/client-v2/src/test/java/com/clickhouse/client/datatypes/DataTypeTests.java index d8fd279fd..b4e464402 100644 --- a/client-v2/src/test/java/com/clickhouse/client/datatypes/DataTypeTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/datatypes/DataTypeTests.java @@ -32,6 +32,7 @@ import java.io.IOException; import java.lang.reflect.Method; import java.math.BigDecimal; +import java.math.RoundingMode; import java.time.Instant; import java.time.LocalDateTime; import java.time.Period; @@ -541,6 +542,9 @@ public void testDynamicWithPrimitives() throws Exception { case Decimal128: case Decimal256: BigDecimal tmpDec = row.getBigDecimal("field").stripTrailingZeros(); + if (tmpDec.divide((BigDecimal)value, RoundingMode.FLOOR).equals(BigDecimal.ONE)) { + continue; + } strValue = tmpDec.toPlainString(); break; case IntervalMicrosecond: @@ -853,7 +857,7 @@ public void testTime64() throws Exception { return; // time64 was introduced in 25.6 } - String table = "test_time64_type"; + String table = "data_type_tests_time64"; client.execute("DROP TABLE IF EXISTS " + table).get(); client.execute(tableDefinition(table, "o_num UInt32", "t_sec Time64(0)", "t_ms Time64(3)", "t_us Time64(6)", "t_ns Time64(9)"), (CommandSettings) new CommandSettings().serverSetting("allow_experimental_time_time64_type", "1")).get(); diff --git a/client-v2/src/test/java/com/clickhouse/client/query/QueryTests.java b/client-v2/src/test/java/com/clickhouse/client/query/QueryTests.java index 3cd71b2bf..ce6d97690 100644 --- a/client-v2/src/test/java/com/clickhouse/client/query/QueryTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/query/QueryTests.java @@ -2146,7 +2146,7 @@ public void testGetDynamicValue() throws Exception { } else if (decision == 1) { return rnd.nextInt(); } else { - return rnd.nextDouble(); + return rnd.nextLong(); } }), 1000); diff --git a/jdbc-v2/pom.xml b/jdbc-v2/pom.xml index 708de6eb0..21b88764f 100644 --- a/jdbc-v2/pom.xml +++ b/jdbc-v2/pom.xml @@ -17,7 +17,7 @@ https://github.com/ClickHouse/clickhouse-java/tree/main/jdbc-v2 - 4.1.4 + 5.0.0 JDBC 4.2 2.17.2 @@ -187,6 +187,28 @@ + + + com.helger.maven + ph-javacc-maven-plugin + ${javacc-plugin.version} + + + jjc + generate-sources + + javacc + + + ${minJdk} + true + com.clickhouse.jdbc.internal.parser.javacc + src/main/javacc + + + + + \ No newline at end of file diff --git a/jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/ClickHouseLexer.g4 b/jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/parser/antlr4/ClickHouseLexer.g4 similarity index 74% rename from jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/ClickHouseLexer.g4 rename to jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/parser/antlr4/ClickHouseLexer.g4 index 743e73c1a..1b77a9d85 100644 --- a/jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/ClickHouseLexer.g4 +++ b/jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/parser/antlr4/ClickHouseLexer.g4 @@ -9,48 +9,76 @@ lexer grammar ClickHouseLexer; // Keywords + + +ACCESS : A C C E S S; ADD : A D D; +ADMIN : A D M I N; AFTER : A F T E R; ALIAS : A L I A S; ALL : A L L; +ALLOW : A L L O W; ALTER : A L T E R; AND : A N D; ANTI : A N T I; ANY : A N Y; +ARBITRARY : A R B I T R A R Y ; ARRAY : A R R A Y; AS : A S; ASCENDING : A S C | A S C E N D I N G; ASOF : A S O F; AST : A S T; ASYNC : A S Y N C; +ASYNCHRONOUS : A S Y N C H R O N O U S ; ATTACH : A T T A C H; +AZURE : A Z U R E; +BACKUP : B A C K U P; +BCRYPT_HASH : B C R Y P T '_' H A S H; +BCRYPT_PASSWORD : B C R Y P T '_' P A S S W O R D; BETWEEN : B E T W E E N; +BLOCKING : B L O C K I N G ; BOTH : B O T H; BY : B Y; -BCRYPT_PASSWORD : B C R Y P T '_' P A S S W O R D; -BCRYPT_HASH : B C R Y P T '_' H A S H; +CACHE : C A C H E ; +CACHES : C A C H E S ; +CANCEL : C A N C E L; CASE : C A S E; CAST : C A S T; +CHANGEABLE_IN_READONLY : C H A N G E A B L E UNDERSCORE I N UNDERSCORE R E A D O N L Y; +CHANGED : C H A N G E D; CHECK : C H E C K; +CLEANUP : C L E A N U P; CLEAR : C L E A R; +CLIENT : C L I E N T ; CLUSTER : C L U S T E R; +CLUSTERS : C L U S T E R S; CN : C N; CODEC : C O D E C; COLLATE : C O L L A T E; +COLLECTION : C O L L E C T I O N ; +COLLECTIONS : C O L L E C T I O N S ; COLUMN : C O L U M N; +COLUMNS : C O L U M N S ; COMMENT : C O M M E N T; +COMPILED : C O M P I L E D ; +CONDITION : C O N D I T I O N; +CONFIG : C O N F I G ; +CONNECTIONS : C O N N E C T I O N S ; +CONST : C O N S T; CONSTRAINT : C O N S T R A I N T; CREATE : C R E A T E; CROSS : C R O S S; CUBE : C U B E; CURRENT : C U R R E N T; CURRENT_USER : C U R R E N T '_' U S E R; +CUSTOM : C U S T O M; DATABASE : D A T A B A S E; DATABASES : D A T A B A S E S; DATE : D A T E; DAY : D A Y; DEDUPLICATE : D E D U P L I C A T E; DEFAULT : D E F A U L T; +DEFINER : D E F I N E R; DELAY : D E L A Y; DELETE : D E L E T E; DESC : D E S C; @@ -62,19 +90,30 @@ DICTIONARY : D I C T I O N A R Y; DISK : D I S K; DISTINCT : D I S T I N C T; DISTRIBUTED : D I S T R I B U T E D; -DOUBLE_SHA1_PASSWORD : D O U B L E '_' S H A '1' '_' P A S S W O R D; +DNS : D N S ; DOUBLE_SHA1_HASH : D O U B L E '_' S H A '1' '_' H A S H; +DOUBLE_SHA1_PASSWORD : D O U B L E '_' S H A '1' '_' P A S S W O R D; DROP : D R O P; ELSE : E L S E; +EMBEDDED : E M B E D D E D ; +ENABLED : E N A B L E D; END : E N D; ENGINE : E N G I N E; +ENGINES : E N G I N E S; +ESTIMATE : E S T I M A T E; EVENTS : E V E N T S; +EXCEPT : E X C E P T; +EXCHANGE : E X C H A N G E; EXISTS : E X I S T S; EXPLAIN : E X P L A I N; EXPRESSION : E X P R E S S I O N; -EXCEPT : E X C E P T; +EXTENDED : E X T E N D E D; EXTRACT : E X T R A C T; +FAILPOINT : F A I L P O I N T ; FETCHES : F E T C H E S; +FETCH : F E T C H ; +FILE : F I L E; +FILESYSTEM : F I L E S Y S T E M ; FINAL : F I N A L; FIRST : F I R S T; FLUSH : F L U S H; @@ -85,33 +124,51 @@ FREEZE : F R E E Z E; FROM : F R O M; FULL : F U L L; FUNCTION : F U N C T I O N; +FUNCTIONS : F U N C T I O N S; +FUZZER : F U Z Z E R ; GLOBAL : G L O B A L; -GRANULARITY : G R A N U L A R I T Y; GRANTEES : G R A N T E E S; +GRANT : G R A N T; +GRANTS : G R A N T S; +GRANULARITY : G R A N U L A R I T Y; GROUP : G R O U P; +GRPC : G R P C; HAVING : H A V I N G; +HDFS : H D F S; HIERARCHICAL : H I E R A R C H I C A L; -HTTP : H T T P; +HIVE : H I V E; HOST : H O S T; HOUR : H O U R; -ID : I D; +HTTP : H T T P; +HTTPS : H T T P S; IDENTIFIED : I D E N T I F I E D; +ID : I D; IF : I F; ILIKE : I L I K E; -IN : I N; +IMPLICIT : I M P L I C I T; +INDEXES : I N D E X E S; INDEX : I N D E X; +INDICES : I N D I C E S; INF : I N F | I N F I N I T Y; +INHERIT : I N H E R I T; +IN : I N; INJECTIVE : I N J E C T I V E; INNER : I N N E R; INSERT : I N S E R T; INTERVAL : I N T E R V A L; INTO : I N T O; +INTROSPECTION : I N T R O S P E C T I O N; IP : I P; IS : I S; IS_OBJECT_ID : I S UNDERSCORE O B J E C T UNDERSCORE I D; +JDBC : J D B C; +JEMALLOC : J E M A L L O C ; JOIN : J O I N; -KEY : K E Y; +KAFKA : K A F K A; KERBEROS : K E R B E R O S; +KEYED : K E Y E D; +KEY : K E Y; +KEYS : K E Y S; KILL : K I L L; LAST : L A S T; LAYOUT : L A Y O U T; @@ -119,153 +176,218 @@ LDAP : L D A P; LEADING : L E A D I N G; LEFT : L E F T; LIFETIME : L I F E T I M E; +LIGHTWEIGHT : L I G H T W E I G H T; LIKE : L I K E; LIMIT : L I M I T; +LIMITS : L I M I T S; +LISTEN : L I S T E N ; LIVE : L I V E; +LOADING : L O A D I N G; +LOAD : L O A D ; LOCAL : L O C A L; +LOG : L O G ; LOGS : L O G S; -MATERIALIZE : M A T E R I A L I Z E; +MANAGEMENT : M A N A G E M E N T; +MARK : M A R K ; MATERIALIZED : M A T E R I A L I Z E D; +MATERIALIZE : M A T E R I A L I Z E; MAX : M A X; MERGES : M E R G E S; +METRICS : M E T R I C S ; MIN : M I N; MINUTE : M I N U T E; +MMAP : M M A P ; +MODEL : M O D E L ; MODIFY : M O D I F Y; +MONGO : M O N G O; MONTH : M O N T H; MOVE : M O V E; +MOVES : M O V E S ; MUTATION : M U T A T I O N; -NAN_SQL : N A N; // conflicts with macro NAN +MYSQL : M Y S Q L; +NAMED : N A M E D ; NAME : N A M E; +NAN_SQL : N A N; // conflicts with macro NAN +NATS : N A T S; +NONE : N O N E; NO : N O; NO_PASSWORD : N O '_' P A S S W O R D; -NONE : N O N E; NOT : N O T; -NULL_SQL : N U L L; // conflicts with macro NULL NULLS : N U L L S; +NULL_SQL : N U L L; // conflicts with macro NULL +ODBC : O D B C; OFFSET : O F F S E T; +ONLY : O N L Y; ON : O N; OPTIMIZE : O P T I M I Z E; -OR : O R; +OPTION : O P T I O N; ORDER : O R D E R; +OR : O R; OUTER : O U T E R; OUTFILE : O U T F I L E; OVER : O V E R; +OVERRIDE : O V E R R I D E; +PAGE : P A G E ; PARTITION : P A R T I T I O N; +PART : P A R T; +PARTS : P A R T S; +PERMISSIVE : P E R M I S S I V E; +PIPELINE : P I P E L I N E; +PLAINTEXT_PASSWORD : P L A I N T E X T '_' P A S S W O R D; +PLAN : P L A N; +POLICIES : P O L I C I E S ; +POLICY : P O L I C Y; POPULATE : P O P U L A T E; +POSTGRES : P O S T G R E S; +POSTGRESQL : P O S T G R E S Q L; PRECEDING : P R E C E D I N G; PREWHERE : P R E W H E R E; PRIMARY : P R I M A R Y; +PROCESSLIST : P R O C E S S L I S T; +PROFILE : P R O F I L E; +PROFILES : P R O F I L E S; PROJECTION : P R O J E C T I O N; -PLAINTEXT_PASSWORD : P L A I N T E X T '_' P A S S W O R D; +PROMETHEUS : P R O M E T H E U S; +PROXY : P R O X Y; +PULLING : P U L L I N G ; +PULL : P U L L; QUARTER : Q U A R T E R; +QUERIES : Q U E R I E S; +QUERY : Q U E R Y; +QUEUE : Q U E U E ; +QUEUES : Q U E U E S ; +QUOTA : Q U O T A; +QUOTAS : Q U O T A S ; +RABBITMQ : R A B B I T M Q; +RANDOMIZED : R A N D O M I Z E D; RANGE : R A N G E; +READINESS : R E A D I N E S S; +READONLY : R E A D O N L Y; REALM : R E A L M; +REDIS : R E D I S; +REDUCE : R E D U C E ; +REFRESH : R E F R E S H ; REGEXP : R E G E X P; RELOAD : R E L O A D; +REMOTE : R E M O T E; REMOVE : R E M O V E; RENAME : R E N A M E; REPLACE : R E P L A C E; REPLICA : R E P L I C A; +REPLICAS : R E P L I C A S; REPLICATED : R E P L I C A T E D; +REPLICATION : R E P L I C A T I O N ; +RESOURCE : R E S O U R C E ; +RESTART : R E S T A R T; +RESTORE : R E S T O R E ; +RESTRICTIVE : R E S T R I C T I V E; +REVOKE : R E V O K E; RIGHT : R I G H T; ROLE : R O L E; +ROLES : R O L E S ; ROLLUP : R O L L U P; ROW : R O W; ROWS : R O W S; +S3 : S '3'; SAMPLE : S A M P L E; SCHEMA : S C H E M A; -SCRAM_SHA256_PASSWORD : S C R A M '_' S H A '2' '5' '6' '_' P A S S W O R D; SCRAM_SHA256_HASH : S C R A M '_' S H A '2' '5' '6' '_' H A S H; +SCRAM_SHA256_PASSWORD : S C R A M '_' S H A '2' '5' '6' '_' P A S S W O R D; SECOND : S E C O N D; +SECRETS : S E C R E T S ; +SECURE : S E C U R E; +SECURITY : S E C U R I T Y; SELECT : S E L E C T; SEMI : S E M I; SENDS : S E N D S; SERVER : S E R V E R; -SSL_CERTIFICATE : S S L '_' C E R T I F I C A T E; -SSH_KEY : S S H '_' K E Y; SET : S E T; +SETTING : S E T T I N G; SETTINGS : S E T T I N G S; -SHOW : S H O W; -SHA256_PASSWORD : S H A '2' '5' '6' '_' P A S S W O R D; SHA256_HASH : S H A '2' '5' '6' '_' H A S H; +SHA256_PASSWORD : S H A '2' '5' '6' '_' P A S S W O R D; +SHARD : S H A R D; +SHARDS : S H A R D S; +SHOW : S H O W; +SHUTDOWN : S H U T D O W N ; SOURCE : S O U R C E; +SOURCES : S O U R C E S; +SQLITE : S Q L I T E; +SQL : S Q L; +SSH_KEY : S S H '_' K E Y; +SSL_CERTIFICATE : S S L '_' C E R T I F I C A T E; START : S T A R T; +STATISTICS : S T A T I S T I C S ; STOP : S T O P; +STRICT : S T R I C T; SUBSTRING : S U B S T R I N G; SYNC : S Y N C; SYNTAX : S Y N T A X; SYSTEM : S Y S T E M; -TABLE : T A B L E; TABLES : T A B L E S; +TABLE : T A B L E; +TAG : T A G; +TCP : T C P; TEMPORARY : T E M P O R A R Y; TEST : T E S T; THEN : T H E N; +THREAD : T H R E A D ; TIES : T I E S; TIMEOUT : T I M E O U T; TIMESTAMP : T I M E S T A M P; -TO : T O; TOP : T O P; TOTALS : T O T A L S; +TO : T O; +TRACKING : T R A C K I N G; TRAILING : T R A I L I N G; +TRANSACTION : T R A N S A C T I O N; +TREE : T R E E; TRIM : T R I M; TRUNCATE : T R U N C A T E; TTL : T T L; TYPE : T Y P E; UNBOUNDED : U N B O U N D E D; +UNCOMPRESSED : U N C O M P R E S S E D ; +UNDROP : U N D R O P; +UNFREEZE : U N F R E E Z E ; UNION : U N I O N; +UNLOAD : U N L O A D ; +UNTIL : U N T I L; UPDATE : U P D A T E; -USE : U S E; +URL : U R L; +USERS : U S E R S ; USER : U S E R; +USE : U S E; USING : U S I N G; UUID : U U I D; +VALID : V A L I D; VALUES : V A L U E S; +VIEWS : V I E W S; VIEW : V I E W; +VIRTUAL : V I R T U A L; VOLUME : V O L U M E; +WAIT : W A I T; WATCH : W A T C H; WEEK : W E E K; WHEN : W H E N; WHERE : W H E R E; WINDOW : W I N D O W; WITH : W I T H; +WORKLOAD : W O R K L O A D; +WRITABLE : W R I T A B L E; YEAR : Y E A R | Y Y Y Y; -QUOTA : Q U O T A; -ACCESS : A C C E S S; -GRANT : G R A N T; -WAIT : W A I T; -CLEANUP : C L E A N U P; -DEFINER : D E F I N E R; -RESTART : R E S T A R T; -SOURCES : S O U R C E S; -AZURE : A Z U R E; -FILE : F I L E; -HDFS : H D F S; -HIVE : H I V E; -JDBC : J D B C; -KAFKA : K A F K A; -MONGO : M O N G O; -MYSQL : M Y S Q L; -NATS : N A T S; -ODBC : O D B C; -POSTGRES : P O S T G R E S; -RABBITMQ : R A B B I T M Q; -REDIS : R E D I S; -REMOTE : R E M O T E; -S3 : S '3'; -SQLITE : S Q L I T E; -URL : U R L; -LOADING : L O A D I N G; -VIRTUAL : V I R T U A L; -VIEWS : V I E W S; -POLICY : P O L I C Y; -PERMISSIVE : P E R M I S S I V E; -RESTRICTIVE : R E S T R I C T I V E; +ZKPATH : Z K P A T H; +SUM : S U M; +AVG : A V G; JSON_FALSE : 'false'; JSON_TRUE : 'true'; // Tokens -IDENTIFIER: (LETTER | UNDERSCORE) (LETTER | UNDERSCORE | DEC_DIGIT)* +IDENTIFIER: + (LETTER | UNDERSCORE) (LETTER | UNDERSCORE | DEC_DIGIT)* + | DEC_DIGIT+ (LETTER | UNDERSCORE) (LETTER | UNDERSCORE | DEC_DIGIT)* | BACKQUOTE ( ~([\\`]) | (BACKSLASH .) | (BACKQUOTE BACKQUOTE))* BACKQUOTE | QUOTE_DOUBLE (~([\\"]) | (BACKSLASH .) | (QUOTE_DOUBLE QUOTE_DOUBLE))* QUOTE_DOUBLE ; @@ -342,7 +464,7 @@ LT : '<'; NOT_EQ : '!=' | '<>'; PERCENT : '%'; PLUS : '+'; -QUERY : '?'; +JDBC_PARAM_PLACEHOLDER : '?'; QUOTE_DOUBLE : '"'; QUOTE_SINGLE : '\''; RBRACE : '}'; diff --git a/jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/ClickHouseParser.g4 b/jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/parser/antlr4/ClickHouseParser.g4 similarity index 69% rename from jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/ClickHouseParser.g4 rename to jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/parser/antlr4/ClickHouseParser.g4 index d6b06a28f..bd94b69c9 100644 --- a/jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/ClickHouseParser.g4 +++ b/jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/parser/antlr4/ClickHouseParser.g4 @@ -22,41 +22,38 @@ query | createStmt // DDL | describeStmt | dropStmt // DDL + | undropStmt // DDL | existsStmt | explainStmt | killStmt // DDL | optimizeStmt // DDL | renameStmt // DDL - | selectUnionStmt | setStmt | setRoleStmt | showStmt | systemStmt | truncateStmt // DDL + | deleteStmt + | updateStmt | useStmt | watchStmt - | ctes? selectStmt + | selectStmt + | selectUnionStmt | grantStmt + | revokeStmt + | exchangeStmt + | moveStmt ; -// CTE statement -ctes - : LPAREN? WITH cteUnboundCol? (COMMA cteUnboundCol)* COMMA? namedQuery (COMMA namedQuery)* RPAREN? - ; +// DELETE statement -namedQuery - : name = identifier (columnAliases)? AS LPAREN query RPAREN +deleteStmt + : DELETE FROM tableIdentifier clusterClause? (IN partitionClause)? whereClause? ; -columnAliases - : LPAREN identifier (',' identifier)* RPAREN - ; - -cteUnboundCol - : (literal AS identifier) # CteUnboundColLiteral - | (QUERY AS identifier) # CteUnboundColParam - | LPAREN columnExpr RPAREN AS identifier # CteUnboundColExpr - | LPAREN ctes? selectStmt RPAREN AS identifier # CteUnboundNestedSelect +// UPDATE statement +updateStmt + : UPDATE tableIdentifier clusterClause? SET assignmentExprList whereClause? ; // ALTER statement @@ -66,9 +63,9 @@ alterStmt ; 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 + : ADD COLUMN (IF NOT EXISTS)? tableColumnDfnt alterTableColumnPosition? # AlterTableClauseAddColumn + | ADD INDEX (IF NOT EXISTS)? tableIndexDfnt alterTableColumnPosition? # AlterTableClauseAddIndex + | ADD PROJECTION (IF NOT EXISTS)? tableProjectionDfnt alterTableColumnPosition? # AlterTableClauseAddProjection | ATTACH partitionClause (FROM tableIdentifier)? # AlterTableClauseAttach | CLEAR COLUMN (IF EXISTS)? nestedIdentifier (IN partitionClause)? # AlterTableClauseClearColumn | CLEAR INDEX (IF EXISTS)? nestedIdentifier (IN partitionClause)? # AlterTableClauseClearIndex @@ -87,8 +84,10 @@ alterTableClause | MODIFY COLUMN (IF EXISTS)? nestedIdentifier COMMENT STRING_LITERAL # AlterTableClauseModifyComment | MODIFY COLUMN (IF EXISTS)? nestedIdentifier REMOVE tableColumnPropertyType # AlterTableClauseModifyRemove | MODIFY COLUMN (IF EXISTS)? tableColumnDfnt # AlterTableClauseModify + | ALTER COLUMN (IF EXISTS)? identifier TYPE? columnTypeExpr codecExpr? ttlClause? settingExprList? alterTableColumnPosition? # AlterTableClauseAlterType | MODIFY ORDER BY columnExpr # AlterTableClauseModifyOrderBy | MODIFY ttlClause # AlterTableClauseModifyTTL + | MODIFY COMMENT literal # AlterTableClauseModifyComment | MOVE partitionClause ( TO DISK STRING_LITERAL | TO VOLUME STRING_LITERAL @@ -100,12 +99,18 @@ alterTableClause | UPDATE assignmentExprList whereClause # AlterTableClauseUpdate ; +alterTableColumnPosition + : (AFTER nestedIdentifier) + | FIRST + ; + assignmentExprList : assignmentExpr (COMMA assignmentExpr)* ; assignmentExpr : nestedIdentifier EQ_SINGLE columnExpr + | nestedIdentifier EQ_SINGLE JDBC_PARAM_PLACEHOLDER ; tableColumnPropertyType @@ -124,21 +129,25 @@ partitionClause // ATTACH statement attachStmt - : ATTACH DICTIONARY tableIdentifier clusterClause? # AttachDictionaryStmt + : ATTACH TABLE (IF NOT EXISTS)? tableIdentifier clusterClause? + | ATTACH DICTIONARY (IF NOT EXISTS)? tableIdentifier clusterClause? + | ATTACH DATABASE (IF NOT EXISTS)? databaseIdentifier engineExpr? clusterClause? ; // CHECK statement checkStmt - : CHECK TABLE tableIdentifier partitionClause? + : CHECK TABLE tableIdentifier (PARTITION identifier | PART identifier)? (FORMAT identifier)? settingsClause? # checkTableStmt + | CHECK ALL TABLES (FORMAT identifier)? settingsClause? # checkAllTablesStmt + | CHECK GRANT privilege columnsClause? ON grantTableIdentifier # checkGrantStmt ; // 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 + : CREATE DATABASE (IF NOT EXISTS)? databaseIdentifier clusterClause? engineExpr? # CreateDatabaseStmt + | (CREATE (OR REPLACE)? | REPLACE) DICTIONARY (IF NOT EXISTS)? tableIdentifier uuidClause? clusterClause? dictionarySchemaClause + dictionaryEngineClause sourceClause layoutClause lifetimeClause dictionarySettingsClause? (COMMENT literal)? # CreateDictionaryStmt | (ATTACH | CREATE) LIVE VIEW (IF NOT EXISTS)? tableIdentifier uuidClause? clusterClause? ( WITH TIMEOUT DECIMAL_LITERAL? )? destinationClause? tableSchemaClause? subqueryClause # CreateLiveViewStmt @@ -146,9 +155,9 @@ createStmt 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 # + | (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 alias? uuidClause? clusterClause? tableSchemaClause? subqueryClause # CreateViewStmt | CREATE USER ((IF NOT EXISTS) | (OR REPLACE))? userIdentifier (COMMA userIdentifier)* clusterClause? userIdentifiedClause? @@ -162,6 +171,28 @@ createStmt | CREATE (ROW)? POLICY (IF NOT EXISTS | OR REPLACE)? identifier clusterClause? ON tableIdentifier (IN identifier)? (AS (PERMISSIVE | RESTRICTIVE))? (FOR SELECT)? USING columnExpr (TO identifier | ALL | ALL EXCEPT identifier)? # CreatePolicyStmt + | CREATE SETTINGS? PROFILE ((IF NOT EXISTS) | (OR REPLACE))? identifier (COMMA identifier)* clusterClause? + (IN identifier)? ((SETTINGS identifier (EQ_SINGLE literal)? (MIN EQ_SINGLE? literal)? (MAX EQ_SINGLE? literal)? + (CONST|READONLY|WRITABLE|CHANGEABLE_IN_READONLY)?) + | ( INHERIT identifier))? (TO identifier | ALL | ALL EXCEPT identifier)? # createProfileStmt + | CREATE FUNCTION identifier clusterClause? AS LPAREN (identifier)? (COMMA identifier)? RPAREN ARROW .+? #createFunctionStmt + | CREATE NAMED COLLECTION (IF NOT EXISTS)? identifier clusterClause? AS nameCollectionKey (COMMA nameCollectionKey)* #createNamedCollectionStmt + | CREATE QUOTA (IF NOT EXISTS | OR REPLACE)? identifier clusterClause? (IN identifier)? + (KEYED BY identifier | NOT KEYED)? + quotaForClause (COMMA quotaForClause)* + (TO (identifier (COMMA identifier)* | ALL | CURRENT_USER | ALL EXCEPT identifier (COMMA identifier)* ))? # createQuotaStmt + ; + +quotaMaxExpr + : identifier EQ_SINGLE numberLiteral + ; + +quotaForClause + : FOR RANDOMIZED? INTERVAL numberLiteral interval (MAX quotaMaxExpr (COMMA quotaMaxExpr)*)+? + ; + +nameCollectionKey + : (identifier EQ_SINGLE literal (NOT? OVERRIDE)?) ; userIdentifier @@ -205,7 +236,7 @@ dictionarySchemaClause ; dictionaryAttrDfnt - : identifier columnTypeExpr + : identifier columnTypeExpr ((DEFAULT | EXPRESSION) columnExpr)? (IS_OBJECT_ID|HIERARCHICAL|INJECTIVE)? ; dictionaryEngineClause @@ -213,7 +244,7 @@ dictionaryEngineClause ; dictionaryPrimaryKeyClause - : PRIMARY KEY columnExprList + : PRIMARY KEY (identifier) (COMMA identifier)* ; dictionaryArgExpr @@ -221,7 +252,7 @@ dictionaryArgExpr ; sourceClause - : SOURCE LPAREN identifier LPAREN dictionaryArgExpr* RPAREN RPAREN + : SOURCE LPAREN identifier LPAREN settingExprList RPAREN RPAREN ; lifetimeClause @@ -298,7 +329,7 @@ tableElementExpr ; tableColumnDfnt - : nestedIdentifier columnTypeExpr tableColumnPropertyExpr? (COMMENT STRING_LITERAL)? codecExpr? ( + : nestedIdentifier columnTypeExpr (NULL_SQL | NOT NULL_SQL)? tableColumnPropertyExpr? (COMMENT STRING_LITERAL)? codecExpr? ( TTL columnExpr )? | nestedIdentifier columnTypeExpr? tableColumnPropertyExpr (COMMENT STRING_LITERAL)? codecExpr? ( @@ -307,7 +338,7 @@ tableColumnDfnt ; tableColumnPropertyExpr - : (DEFAULT | MATERIALIZED | ALIAS) columnExpr + : (DEFAULT | MATERIALIZED | ALIAS ) columnExpr ; tableIndexDfnt @@ -330,6 +361,11 @@ ttlExpr : columnExpr (DELETE | TO DISK STRING_LITERAL | TO VOLUME STRING_LITERAL)? ; +// MOVE statement +moveStmt + : MOVE (USER | ROLE | QUOTA | SETTINGS PROFILE | ROW POLICY) identifier TO identifier + ; + // DESCRIBE statement describeStmt @@ -339,24 +375,30 @@ describeStmt // DROP statement dropStmt - : (DETACH | DROP) DATABASE (IF EXISTS)? databaseIdentifier clusterClause? # DropDatabaseStmt - | (DETACH | DROP) (DICTIONARY | TEMPORARY? TABLE | VIEW | ROLE | USER) (IF EXISTS)? tableIdentifier clusterClause? ( - NO DELAY - )? # DropTableStmt + : (DETACH | DROP) DATABASE (IF EXISTS)? databaseIdentifier clusterClause? SYNC? + | (DETACH | DROP) (DICTIONARY | TEMPORARY? TABLE | VIEW) (IF EXISTS)? tableIdentifier clusterClause? + (NO DELAY)? SYNC? + | (DETACH | DROP) (USER | ROLE | QUOTA | SETTINGS? PROFILE) (IF EXISTS)? identifier clusterClause? (FROM identifier)? + | (DETACH | DROP) ROW? POLICY (IF EXISTS)? identifier ON grantTableIdentifier (COMMA grantTableIdentifier)* clusterClause? (FROM identifier)? + | (DETACH | DROP) (FUNCTION | NAMED COLLECTION) (IF EXISTS)? identifier clusterClause? + ; + +undropStmt + : UNDROP TABLE tableIdentifier uuidClause? clusterClause? ; // EXISTS statement existsStmt - : EXISTS DATABASE databaseIdentifier # ExistsDatabaseStmt - | EXISTS (DICTIONARY | TEMPORARY? TABLE | VIEW)? tableIdentifier # ExistsTableStmt + : EXISTS DATABASE databaseIdentifier (INTO OUTFILE filename)? (FORMAT identifier)? # ExistsDatabaseStmt + | EXISTS (DICTIONARY | TEMPORARY? TABLE | VIEW)? tableIdentifier (INTO OUTFILE filename)? (FORMAT identifier)? # ExistsTableStmt ; // EXPLAIN statement explainStmt - : EXPLAIN AST query # ExplainASTStmt - | EXPLAIN SYNTAX query # ExplainSyntaxStmt + : EXPLAIN (AST | SYNTAX | QUERY TREE | PLAN | PIPELINE | ESTIMATE | TABLE OVERRIDE)? settingExprList? .+? + | EXPLAIN .+? ; // INSERT statement @@ -382,27 +424,35 @@ assignmentValues assignmentValue : literal # InsertRawValue - | QUERY # InsertParameter + | JDBC_PARAM_PLACEHOLDER # InsertParameter | identifier (LPAREN columnExprList? RPAREN)? # InsertParameterFuncExpr - | LPAREN columnExpr RPAREN # InserParameterExpr + | LPAREN? columnExpr RPAREN? # InserParameterExpr ; // KILL statement killStmt - : KILL MUTATION clusterClause? whereClause (SYNC | ASYNC | TEST)? # KillMutationStmt + : KILL MUTATION clusterClause? whereClause (SYNC | ASYNC | TEST)? (FORMAT identifier)? # KillMutationStmt + | KILL QUERY clusterClause? whereClause (SYNC | ASYNC | TEST)? (FORMAT identifier)? # KillQueryStmt ; // OPTIMIZE statement optimizeStmt - : OPTIMIZE TABLE tableIdentifier clusterClause? partitionClause? FINAL? DEDUPLICATE? + : OPTIMIZE TABLE tableIdentifier clusterClause? partitionClause? FINAL? DEDUPLICATE? optimizeByExpr? + ; + +optimizeByExpr + : BY ASTERISK (EXCEPT LPAREN? (identifier (COMMA identifier)*) RPAREN? )? + | BY identifier (COMMA identifier)* + | BY COLUMNS LPAREN literal RPAREN (EXCEPT LPAREN? (identifier (COMMA identifier)*) RPAREN? )? ; // RENAME statement renameStmt : RENAME TABLE tableIdentifier TO tableIdentifier (COMMA tableIdentifier TO tableIdentifier)* clusterClause? + | RENAME ; // PROJECTION SELECT statement @@ -423,7 +473,7 @@ selectStmtWithParens ; selectStmt - : withClause? SELECT DISTINCT? topClause? columnExprList fromClause? arrayJoinClause? windowClause? prewhereClause? whereClause? groupByClause? ( + : cteClause? SELECT DISTINCT? topClause? columnExprList fromClause? arrayJoinClause? windowClause? prewhereClause? whereClause? groupByClause? ( WITH (CUBE | ROLLUP) )? (WITH TOTALS)? havingClause? orderByClause? limitByClause? limitClause? settingsClause? ; @@ -432,19 +482,43 @@ withClause : WITH columnExprList ; +// CTE statement +cteClause + : WITH (cteUnboundCol | namedQuery) (COMMA (cteUnboundCol | namedQuery))* + ; + + +namedQuery + : identifier (columnAliases)? AS LPAREN? ( selectStmt | selectStmtWithParens | selectUnionStmt) RPAREN? + ; + +columnAliases + : LPAREN identifier (',' identifier)* RPAREN + ; + +cteUnboundCol + : literal AS identifier # CteUnboundColLiteral + | JDBC_PARAM_PLACEHOLDER AS identifier # CteUnboundColParam + | LPAREN? columnExpr RPAREN? AS? identifier? # CteUnboundColExpr + | LPAREN selectStmt RPAREN AS identifier # CteUnboundSubQuery +// | LPAREN cteStmt? selectStmt RPAREN AS identifier # CteUnboundNestedSelect + ; + topClause : TOP DECIMAL_LITERAL (WITH TIES)? ; fromClause : FROM joinExpr - | FROM identifier LPAREN QUERY RPAREN - | FROM ctes + | FROM tableIdentifier + | FROM identifier LPAREN JDBC_PARAM_PLACEHOLDER RPAREN + | FROM selectStmt | FROM identifier LPAREN viewParam (COMMA viewParam)? RPAREN + | FROM tableFunctionExpr ; viewParam - : identifier EQ_SINGLE (literal | QUERY) + : identifier EQ_SINGLE (literal | JDBC_PARAM_PLACEHOLDER) ; arrayJoinClause @@ -579,10 +653,16 @@ winFrameBound //rangeClause: RANGE LPAREN (MIN identifier MAX identifier | MAX identifier MIN identifier) RPAREN; +// EXCHANGE statement +exchangeStmt + : EXCHANGE (TABLES | DICTIONARIES) tableIdentifier AND tableIdentifier clusterClause? + ; + + // SET statement setStmt - : SET settingExprList + : SET (identifier | settingExpr) ; // SET ROLE statement @@ -595,10 +675,23 @@ setRolesList : identifier (COMMA identifier)* ; +// GRANT statements + grantStmt - : GRANT clusterClause? ((privilege ON grantTableIdentifier) | (identifier (COMMA identifier)*)) + : GRANT clusterClause? ((identifier (COMMA identifier)*) | (privelegeList ON grantTableIdentifier)) TO (CURRENT_USER | identifier) (COMMA identifier)* - (WITH GRANT OPTION)? (WITH REPLACE OPTION)? + (WITH ADMIN OPTION)? (WITH GRANT OPTION)? (WITH REPLACE OPTION)? + | GRANT CURRENT GRANTS (LPAREN ((privelegeList ON grantTableIdentifier) | (identifier (COMMA identifier)*)) RPAREN)? + TO (CURRENT_USER | identifier (COMMA identifier)*) + (WITH GRANT OPTION)? (WITH REPLACE OPTION)? + ; + +// REVOKE statements +revokeStmt + : REVOKE clusterClause? privelegeList ON grantTableIdentifier + FROM ((CURRENT_USER | identifier) (COMMA identifier)* | ALL | ALL EXCEPT (CURRENT_USER | identifier) (COMMA identifier)* ) + | REVOKE clusterClause? (ADMIN OPTION FOR)? identifier (COMMA identifier)* + FROM ((CURRENT_USER | identifier) (COMMA identifier)* | ALL | ALL EXCEPT (CURRENT_USER | identifier) (COMMA identifier)* ) ; grantTableIdentifier @@ -608,6 +701,15 @@ grantTableIdentifier | (ASTERISK DOT)? ASTERISK ; +privelegeList + : columnPrivilege (COMMA columnPrivilege)* + ; + + +columnPrivilege + : privilege (LPAREN identifier (COMMA identifier)* RPAREN)? + ; + privilege : | ACCESS MANAGEMENT @@ -830,24 +932,90 @@ systemPrivilege // 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 + : SHOW CREATE? (TEMPORARY? TABLE | DICTIONARY | VIEW | DATABASE) tableIdentifier (INTO OUTFILE literal)? (FORMAT identifier)? # showCreateStmt + | SHOW DATABASES (NOT? (LIKE | ILIKE) literal) (LIMIT numberLiteral)? (INTO OUTFILE filename)? (FORMAT identifier)? # showDatabasesStmt + | SHOW FULL? TEMPORARY? TABLES showFromDbClause? (NOT? (LIKE | ILIKE) literal)? (LIMIT numberLiteral)? (INTO OUTFILE filename)? (FORMAT identifier)? # showTablesStmt + | SHOW EXTENDED? FULL? COLUMNS showFromTableFromDbClause? (NOT? (LIKE | ILIKE) literal)? (LIMIT numberLiteral)? (INTO OUTFILE filename)? (FORMAT identifier)? # showColumnsStmt + | SHOW DICTIONARIES showFromDbClause? (NOT? (LIKE | ILIKE) literal)? (LIMIT numberLiteral)? (INTO OUTFILE filename)? (FORMAT identifier)? # showDictionariesStmt + | SHOW EXTENDED? (INDEX | INDEXES | INDICES | KEYS ) (FROM | IN) identifier showFromTableFromDbClause? (WHERE columnExpr)? (INTO OUTFILE filename)? (FORMAT identifier)? # showIndexStmt + | SHOW PROCESSLIST (INTO OUTFILE filename)? (FORMAT identifier)? # showProcessListStmt + | SHOW GRANTS (FOR identifier (COMMA identifier)*)? (WITH IMPLICIT)? FINAL? # showGrantsStmt + | SHOW CREATE USER ((identifier (COMMA identifier)*) | CURRENT_USER) # showCreateUserStmt + | SHOW CREATE ROLE (identifier (COMMA identifier)*) # showCreateRoleStmt + | SHOW CREATE ROW? POLICY identifier ON tableIdentifier # showCreatePolicyStmt + | SHOW CREATE QUOTA ((identifier (COMMA identifier)*) | CURRENT) # showCreateQuotaStmt + | SHOW CREATE (SETTINGS)? PROFILE identifier (COMMA identifier)* # showCreateProfile + | SHOW USERS # showUsersStmt + | SHOW (CURRENT|ENABLED)? ROLES # showRolesStmt + | SHOW SETTINGS? PROFILES # showProfilesStmt + | SHOW ROW? POLICIES (ON identifier)? # showPoliciesStmt + | SHOW QUOTAS # showQuotasStmt + | SHOW CURRENT? QUOTA # showQuotaStmt + | SHOW ACCESS # showAccessStmt + | SHOW CLUSTER identifier # showClusterStmt + | SHOW CLUSTERS (NOT? (LIKE | ILIKE) literal)? (LIMIT numberLiteral)? (INTO OUTFILE filename)? (FORMAT identifier)? # showClustersStmt + | SHOW CHANGED? SETTINGS (LIKE | ILIKE) literal # showSettingsStmt + | SHOW SETTING identifier # showSettingStmt + | SHOW FILESYSTEM CACHES # showFSCachesStmt + | SHOW ENGINES (INTO OUTFILE filename)? (FORMAT identifier)? # showEnginesStmt + | SHOW FUNCTIONS (NOT? (LIKE | ILIKE) literal)? # showFunctionsStmt + | SHOW MERGES (NOT? (LIKE | ILIKE) literal)? (LIMIT numberLiteral)? (INTO OUTFILE filename)? (FORMAT identifier)? # showMergesStmt + ; + +showFromDbClause + : ((FROM | IN) identifier) + ; + +showFromTableFromDbClause + : ((FROM | IN) identifier) showFromDbClause? ; // SYSTEM statements systemStmt : SYSTEM FLUSH DISTRIBUTED tableIdentifier - | SYSTEM FLUSH LOGS - | SYSTEM RELOAD DICTIONARIES + | SYSTEM RELOAD DICTIONARIES clusterClause? identifier? | SYSTEM RELOAD DICTIONARY tableIdentifier - | SYSTEM (START | STOP) (DISTRIBUTED SENDS | FETCHES | TTL? MERGES) tableIdentifier - | SYSTEM (START | STOP) REPLICATED SENDS - | SYSTEM SYNC REPLICA tableIdentifier + | SYSTEM RELOAD MODEL clusterClause? identifier? + | SYSTEM RELOAD FUNCTIONS clusterClause? + | SYSTEM RELOAD FUNCTION clusterClause? identifier + | SYSTEM RELOAD ASYNCHRONOUS METRICS clusterClause? + | SYSTEM DROP DNS CACHE + | SYSTEM DROP MARK CACHE + | SYSTEM DROP REPLICA literal (FROM SHARD literal)? (FROM (TABLE tableIdentifier) | (FROM DATABASE identifier) | (ZKPATH literal))? + | SYSTEM DROP UNCOMPRESSED CACHE + | SYSTEM DROP COMPILED EXPRESSION CACHE + | SYSTEM DROP QUERY CONDITION CACHE + | SYSTEM DROP QUERY CACHE (TAG literal)? + | SYSTEM DROP FORMAT SCHEMA CACHE (FOR literal)? + | SYSTEM FLUSH LOGS + | SYSTEM RELOAD CONFIG clusterClause? + | SYSTEM RELOAD USERS clusterClause? + | SYSTEM SHUTDOWN + | SYSTEM KILL + | SYSTEM (START | FLUSH | STOP) (DISTRIBUTED SENDS? | FETCHES | TTL? MERGES) tableIdentifier clusterClause? settingsClause? + | SYSTEM (START | STOP) LISTEN clusterClause? (QUERIES ALL | QUERIES DEFAULT | QUERIES CUSTOM | TCP | TCP WITH PROXY | TCP SECURE | HTTP | HTTPS | MYSQL | GRPC | POSTGRESQL | PROMETHEUS | CUSTOM literal) + | SYSTEM (START | STOP) MERGES clusterClause? ((ON VOLUME identifier) | tableIdentifier)? + | SYSTEM (START | STOP) TTL MERGES clusterClause? tableIdentifier? + | SYSTEM (START | STOP) MOVES clusterClause? tableIdentifier? + | SYSTEM UNFREEZE WITH NAME literal + | SYSTEM WAIT LOADING PARTS clusterClause? tableIdentifier? + | SYSTEM (START | STOP) FETCHES clusterClause? tableIdentifier? + | SYSTEM (START | STOP) REPLICATED SENDS clusterClause? tableIdentifier? + | SYSTEM (START | STOP) REPLICATION QUEUES clusterClause? tableIdentifier? + | SYSTEM (START | STOP) PULLING REPLICATION LOG clusterClause? tableIdentifier? + | SYSTEM SYNC REPLICA clusterClause? tableIdentifier? (IF EXISTS)? (STRICT | LIGHTWEIGHT | FROM literal | PULL)? + | SYSTEM SYNC DATABASE REPLICA identifier + | SYSTEM RESTART REPLICA clusterClause? tableIdentifier? + | SYSTEM RESTORE DATABASE? REPLICA identifier clusterClause? + | SYSTEM RESTART REPLICAS + | SYSTEM DROP FILESYSTEM CACHE clusterClause? + | SYSTEM SYNC FILE CACHE clusterClause? + | SYSTEM (LOAD | UNLOAD) PRIMARY KEY tableIdentifier? + | SYSTEM REFRESH VIEW tableIdentifier + | SYSTEM REPLICATED? (START | STOP) ((VIEW tableIdentifier) | VIEWS) + | SYSTEM CANCEL VIEW tableIdentifier + | SYSTEM WAIT VIEW tableIdentifier ; // TRUNCATE statements @@ -935,7 +1103,7 @@ columnExpr | 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 JDBC_PARAM_PLACEHOLDER columnExpr COLON columnExpr # ColumnExprTernaryOp | columnExpr (alias | AS identifier) # ColumnExprAlias | (tableIdentifier DOT)? ASTERISK # ColumnExprAsterisk // single-column only | LPAREN selectUnionStmt RPAREN # ColumnExprSubquery // single-column only @@ -943,13 +1111,13 @@ columnExpr | LPAREN columnExprList RPAREN # ColumnExprTuple | LBRACKET columnExprList? RBRACKET # ColumnExprArray | columnIdentifier # ColumnExprIdentifier - | QUERY (CAST_OP identifier)? # ColumnExprParam + | JDBC_PARAM_PLACEHOLDER (CAST_OP identifier)? # ColumnExprParam | columnExpr REGEXP literal # ColumnExprRegexp ; columnArgList : columnArgExpr (COMMA columnArgExpr)* - | QUERY (COMMA QUERY)* + | JDBC_PARAM_PLACEHOLDER (COMMA JDBC_PARAM_PLACEHOLDER)* ; columnArgExpr @@ -986,6 +1154,10 @@ tableIdentifier : (databaseIdentifier DOT)? identifier ; +viewIdentifier + : tableIdentifier + ; + tableArgList : tableArgExpr (COMMA tableArgExpr)* ; @@ -1027,6 +1199,10 @@ literal | NULL_SQL ; +filename + : STRING_LITERAL + ; + interval : SECOND | MINUTE @@ -1124,6 +1300,7 @@ keyword | INSERT | INTERVAL | INTO + | IP | IS | IS_OBJECT_ID | JOIN @@ -1141,6 +1318,7 @@ keyword | LIVE | LOCAL | LOGS + | LOG | MATERIALIZE | MATERIALIZED | MAX @@ -1167,9 +1345,11 @@ keyword | PRECEDING | PREWHERE | PRIMARY + | PROFILE | RANGE | RELOAD | REMOVE + | REMOTE | RENAME | REPLACE | REPLICA @@ -1178,6 +1358,7 @@ keyword | ROLLUP | ROW | ROWS + | REVOKE | SAMPLE | SELECT | SEMI @@ -1204,6 +1385,7 @@ keyword | TRAILING | TRIM | TRUNCATE + | TRACKING | TO | TOP | TTL @@ -1214,7 +1396,9 @@ keyword | USE | USING | USER + | USERS | UUID + | URL | VALUES | VIEW | VOLUME @@ -1223,6 +1407,11 @@ keyword | WHERE | WINDOW | WITH + | QUERIES + | SUM + | AVG + | REFRESH + | EXPLAIN ; keywordForAlias @@ -1237,6 +1426,7 @@ keywordForAlias | CURRENT | INDEX | TABLES + | TABLE | TEST | VIEW | PRIMARY @@ -1247,6 +1437,7 @@ keywordForAlias | HOUR | MINUTE | SECOND + | REVOKE ; alias diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java index 40611d191..b3238e872 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java @@ -12,7 +12,7 @@ import com.clickhouse.jdbc.internal.FeatureManager; import com.clickhouse.jdbc.internal.JdbcConfiguration; import com.clickhouse.jdbc.internal.ParsedPreparedStatement; -import com.clickhouse.jdbc.internal.SqlParser; +import com.clickhouse.jdbc.internal.SqlParserFacade; import com.clickhouse.jdbc.metadata.DatabaseMetaDataImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,7 +64,7 @@ public class ConnectionImpl implements Connection, JdbcV2Wrapper { private final DatabaseMetaDataImpl metadata; protected final Calendar defaultCalendar; - private final SqlParser sqlParser; + private final SqlParserFacade sqlParser; private Executor networkTimeoutExecutor; @@ -117,7 +117,9 @@ public ConnectionImpl(String url, Properties info) throws SQLException { this.metadata = new DatabaseMetaDataImpl(this, false, url); this.defaultCalendar = Calendar.getInstance(); - this.sqlParser = new SqlParser(); + + this.sqlParser = SqlParserFacade.getParser(config.getDriverProperty(DriverProperties.SQL_PARSER.getKey(), + DriverProperties.SQL_PARSER.getDefaultValue())); this.featureManager = new FeatureManager(this.config); } catch (SQLException e) { throw e; @@ -126,7 +128,7 @@ public ConnectionImpl(String url, Properties info) throws SQLException { } } - public SqlParser getSqlParser() { + public SqlParserFacade getSqlParser() { return sqlParser; } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java index 6bae0071b..f623edda4 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java @@ -57,6 +57,17 @@ public enum DriverProperties { * */ USE_MAX_RESULT_ROWS("jdbc_use_max_result_rows", String.valueOf(Boolean.FALSE)), + + /** + * Configures what SQL parser will be used. Choices: + *
    + *
  • ANTLR4 - parser extracts required information but PreparedStatement parameters parsed separately.
  • + *
  • ANTLR4_PARAMS_PARSER - parser extracts required information AND parameter positions.
  • + *
  • JAVACC - parser extracts required information but PreparedStatement parameters parsed separately.
  • + *
+ */ +// SQL_PARSER("jdbc_sql_parser", "JAVACC", List.of("ANTLR4", "ANTLR4_PARAMS_PARSER", "JAVACC")), + SQL_PARSER("jdbc_sql_parser", "ANTLR4", List.of("ANTLR4", "ANTLR4_PARAMS_PARSER", "JAVACC")), ; diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ParsedPreparedStatement.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ParsedPreparedStatement.java index 6fcac9d3f..b17db6703 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ParsedPreparedStatement.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ParsedPreparedStatement.java @@ -1,20 +1,12 @@ package com.clickhouse.jdbc.internal; -import com.clickhouse.client.api.sql.SQLUtils; -import org.antlr.v4.runtime.tree.ErrorNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; /** - * Parser listener that collects information for prepared statement. + * Model of parsed statement with parameters */ -public class ParsedPreparedStatement extends ClickHouseParserBaseListener { - private static final Logger LOG = LoggerFactory.getLogger(ParsedPreparedStatement.class); +public final class ParsedPreparedStatement { private String table; @@ -42,7 +34,7 @@ public class ParsedPreparedStatement extends ClickHouseParserBaseListener { private int assignValuesListStopPosition = -1; - private int assignValuesGroups = -1; + private int assignValuesGroups = 0; public void setHasResultSet(boolean hasResultSet) { this.hasResultSet = hasResultSet; @@ -76,10 +68,18 @@ public String[] getInsertColumns() { return insertColumns; } + public void setInsertColumns(String[] insertColumns) { + this.insertColumns = insertColumns; + } + public String getTable() { return table; } + public void setTable(String table) { + this.table = table; + } + public int[] getParamPositions() { return paramPositions; } @@ -96,10 +96,18 @@ public int getAssignValuesListStartPosition() { return assignValuesListStartPosition; } + public void setAssignValuesListStartPosition(int assignValuesListStartPosition) { + this.assignValuesListStartPosition = assignValuesListStartPosition; + } + public int getAssignValuesListStopPosition() { return assignValuesListStopPosition; } + public void setAssignValuesListStopPosition(int assignValuesListStopPosition) { + this.assignValuesListStopPosition = assignValuesListStopPosition; + } + public void setUseDatabase(String useDatabase) { this.useDatabase = useDatabase; } @@ -132,135 +140,11 @@ public void setHasErrors(boolean hasErrors) { this.hasErrors = hasErrors; } - @Override - public void enterQueryStmt(ClickHouseParser.QueryStmtContext ctx) { - ClickHouseParser.QueryContext qCtx = ctx.query(); - if (qCtx != null) { - if (qCtx.selectStmt() != null || qCtx.selectUnionStmt() != null || qCtx.showStmt() != null || qCtx.describeStmt() != null) { - setHasResultSet(true); - } - } - } - - @Override - public void enterUseStmt(ClickHouseParser.UseStmtContext ctx) { - if (ctx.databaseIdentifier() != null) { - setUseDatabase(SQLUtils.unquoteIdentifier(ctx.databaseIdentifier().getText())); - } - } - - @Override - public void enterSetRoleStmt(ClickHouseParser.SetRoleStmtContext ctx) { - if (ctx.NONE() != null) { - setRoles(Collections.emptyList()); - } else { - List roles = new ArrayList<>(); - for (ClickHouseParser.IdentifierContext id : ctx.setRolesList().identifier()) { - roles.add(SQLUtils.unquoteIdentifier(id.getText())); - } - setRoles(roles); - } - } - - @Override - public void enterColumnExprParam(ClickHouseParser.ColumnExprParamContext ctx) { - appendParameter(ctx.start.getStartIndex()); - } - - @Override - public void enterColumnExprPrecedence3(ClickHouseParser.ColumnExprPrecedence3Context ctx) { - super.enterColumnExprPrecedence3(ctx); - } - - @Override - public void enterCteUnboundColParam(ClickHouseParser.CteUnboundColParamContext ctx) { - appendParameter(ctx.start.getStartIndex()); - } - - @Override - public void visitErrorNode(ErrorNode node) { - setHasErrors(true); - } - - @Override - public void enterInsertParameterFuncExpr(ClickHouseParser.InsertParameterFuncExprContext ctx) { - setUseFunction(true); - } - - @Override - public void enterAssignmentValuesList(ClickHouseParser.AssignmentValuesListContext ctx) { - assignValuesListStartPosition = ctx.getStart().getStartIndex(); - assignValuesListStopPosition = ctx.getStop().getStopIndex(); - } - - @Override - public void enterInsertParameter(ClickHouseParser.InsertParameterContext ctx) { - appendParameter(ctx.start.getStartIndex()); - } - - @Override - public void enterFromClause(ClickHouseParser.FromClauseContext ctx) { - if (ctx.QUERY() != null) { - appendParameter(ctx.QUERY().getSymbol().getStartIndex()); - } - } - - @Override - public void enterViewParam(ClickHouseParser.ViewParamContext ctx) { - if (ctx.QUERY() != null) { - appendParameter(ctx.QUERY().getSymbol().getStartIndex()); - } - } - - private void appendParameter(int startIndex) { + void appendParameter(int startIndex) { argCount++; if (argCount > paramPositions.length) { paramPositions = Arrays.copyOf(paramPositions, paramPositions.length + 10); } paramPositions[argCount - 1] = startIndex; - if (LOG.isTraceEnabled()) { - LOG.trace("parameter position {}", startIndex); - } - } - - @Override - public void enterTableExprIdentifier(ClickHouseParser.TableExprIdentifierContext ctx) { - if (ctx.tableIdentifier() != null) { - this.table = SQLUtils.unquoteIdentifier(ctx.tableIdentifier().getText()); - } - } - - @Override - public void enterInsertStmt(ClickHouseParser.InsertStmtContext ctx) { - ClickHouseParser.TableIdentifierContext tableId = ctx.tableIdentifier(); - if (tableId != null) { - this.table = SQLUtils.unquoteIdentifier(tableId.getText()); - } - - ClickHouseParser.ColumnsClauseContext columns = ctx.columnsClause(); - if (columns != null) { - List names = columns.nestedIdentifier(); - this.insertColumns = new String[names.size()]; - for (int i = 0; i < names.size(); i++) { - this.insertColumns[i] = names.get(i).getText(); - } - } - - setInsert(true); - } - - @Override - public void enterDataClauseSelect(ClickHouseParser.DataClauseSelectContext ctx) { - setInsertWithSelect(true); - } - - @Override - public void enterDataClauseValues(ClickHouseParser.DataClauseValuesContext ctx) { - setAssignValuesGroups(ctx.assignmentValues().size()); - } - - @Override - public void exitInsertParameterFuncExpr(ClickHouseParser.InsertParameterFuncExprContext ctx) { - setUseFunction(true); } } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ParsedStatement.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ParsedStatement.java index ee94eaf67..f9eb5cde8 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ParsedStatement.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ParsedStatement.java @@ -1,13 +1,11 @@ package com.clickhouse.jdbc.internal; -import com.clickhouse.client.api.sql.SQLUtils; -import org.antlr.v4.runtime.tree.ErrorNode; - -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -public class ParsedStatement extends ClickHouseParserBaseListener { +/** + * Model of parsed statement when no parameters are used. + */ +public final class ParsedStatement { private String useDatabase; @@ -15,8 +13,6 @@ public class ParsedStatement extends ClickHouseParserBaseListener { private boolean insert; - private String insertTableId; - private List roles; private boolean hasErrors; @@ -41,14 +37,6 @@ public boolean isInsert() { return insert; } - public void setInsertTableId(String insertTableId) { - this.insertTableId = insertTableId; - } - - public String getInsertTableId() { - return insertTableId; - } - public String getUseDatabase() { return useDatabase; } @@ -68,42 +56,4 @@ public boolean isHasErrors() { public void setHasErrors(boolean hasErrors) { this.hasErrors = hasErrors; } - - @Override - public void visitErrorNode(ErrorNode node) { - setHasErrors(true); - } - - @Override - public void enterQueryStmt(ClickHouseParser.QueryStmtContext ctx) { - ClickHouseParser.QueryContext qCtx = ctx.query(); - if (qCtx != null) { - if (qCtx.selectStmt() != null || qCtx.selectUnionStmt() != null || qCtx.showStmt() != null - || qCtx.describeStmt() != null) { - setHasResultSet(true); - } - } - } - - @Override - public void enterUseStmt(ClickHouseParser.UseStmtContext ctx) { - if (ctx.databaseIdentifier() != null) { - setUseDatabase(SQLUtils.unquoteIdentifier(ctx.databaseIdentifier().getText())); - } - } - - @Override - public void enterSetRoleStmt(ClickHouseParser.SetRoleStmtContext ctx) { - if (ctx.NONE() != null) { - setRoles(Collections.emptyList()); - } else { - List roles = new ArrayList<>(); - for (ClickHouseParser.IdentifierContext id : ctx.setRolesList().identifier()) { - roles.add(SQLUtils.unquoteIdentifier(id.getText())); - } - setRoles(roles); - } - } - - } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/SqlParser.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/SqlParser.java deleted file mode 100644 index 8a57a550d..000000000 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/SqlParser.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.clickhouse.jdbc.internal; - -import org.antlr.v4.runtime.BaseErrorListener; -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.RecognitionException; -import org.antlr.v4.runtime.Recognizer; -import org.antlr.v4.runtime.tree.IterativeParseTreeWalker; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class SqlParser { - - private static final Logger LOG = LoggerFactory.getLogger(SqlParser.class); - - public ParsedStatement parsedStatement(String sql) { - ParsedStatement parserListener = new ParsedStatement(); - walkSql(sql, parserListener); - return parserListener; - } - - public ParsedPreparedStatement parsePreparedStatement(String sql) { - ParsedPreparedStatement parserListener = new ParsedPreparedStatement(); - walkSql(sql, parserListener); - return parserListener; - } - - private ClickHouseParser walkSql(String sql, ClickHouseParserBaseListener listener ) { - CharStream charStream = CharStreams.fromString(sql); - ClickHouseLexer lexer = new ClickHouseLexer(charStream); - ClickHouseParser parser = new ClickHouseParser(new CommonTokenStream(lexer)); - parser.removeErrorListeners(); - parser.addErrorListener(new ParserErrorListener()); - - ClickHouseParser.QueryStmtContext parseTree = parser.queryStmt(); - IterativeParseTreeWalker.DEFAULT.walk(listener, parseTree); - - return parser; - } - - private static class ParserErrorListener extends BaseErrorListener { - @Override - public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) { - LOG.warn("SQL syntax error at line: " + line + ", pos: " + charPositionInLine + ", " + msg); - } - } -} diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/SqlParserFacade.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/SqlParserFacade.java new file mode 100644 index 000000000..27e5d83d8 --- /dev/null +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/SqlParserFacade.java @@ -0,0 +1,422 @@ +package com.clickhouse.jdbc.internal; + +import com.clickhouse.client.api.sql.SQLUtils; +import com.clickhouse.data.ClickHouseUtils; +import com.clickhouse.jdbc.internal.parser.antlr4.ClickHouseLexer; +import com.clickhouse.jdbc.internal.parser.antlr4.ClickHouseParser; +import com.clickhouse.jdbc.internal.parser.antlr4.ClickHouseParserBaseListener; +import com.clickhouse.jdbc.internal.parser.javacc.ClickHouseSqlParser; +import com.clickhouse.jdbc.internal.parser.javacc.ClickHouseSqlStatement; +import com.clickhouse.jdbc.internal.parser.javacc.JdbcParseHandler; +import com.clickhouse.jdbc.internal.parser.javacc.StatementType; +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.antlr.v4.runtime.tree.ErrorNode; +import org.antlr.v4.runtime.tree.IterativeParseTreeWalker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public abstract class SqlParserFacade { + + private static final Logger LOG = LoggerFactory.getLogger(SqlParserFacade.class); + + public abstract ParsedStatement parsedStatement(String sql); + + public abstract ParsedPreparedStatement parsePreparedStatement(String sql); + + private static class JavaCCParser extends SqlParserFacade { + + @Override + public ParsedStatement parsedStatement(String sql) { + ParsedStatement stmt = new ParsedStatement(); + ClickHouseSqlStatement parsedStmt = parse(sql); + if (parsedStmt.getStatementType() == StatementType.USE) { + stmt.setUseDatabase(parsedStmt.getDatabase()); + } + + String rolesCount = parsedStmt.getSettings().get("_ROLES_COUNT"); + if (rolesCount != null) { + int rolesCountInt = Integer.parseInt(rolesCount); + ArrayList roles = new ArrayList<>(rolesCountInt); + boolean resetRoles = false; + for (int i = 0; i < rolesCountInt; i++) { + String role = parsedStmt.getSettings().get("_ROLE_" + i); + if (role.equalsIgnoreCase("NONE")) { + resetRoles = true; + } + roles.add(parsedStmt.getSettings().get("_ROLE_" + i)); + } + if (resetRoles) { + roles.clear(); + } + stmt.setRoles(roles); + } + + stmt.setInsert(parsedStmt.getStatementType() == StatementType.INSERT); + stmt.setHasErrors(parsedStmt.getStatementType() == StatementType.UNKNOWN); + stmt.setHasResultSet(isStmtWithResultSet(parsedStmt)); + return stmt; + } + + private boolean isStmtWithResultSet(ClickHouseSqlStatement parsedStmt) { + return parsedStmt.getStatementType() == StatementType.SELECT || parsedStmt.getStatementType() == StatementType.SHOW + || parsedStmt.getStatementType() == StatementType.EXPLAIN || parsedStmt.getStatementType() == StatementType.DESCRIBE + || parsedStmt.getStatementType() == StatementType.EXISTS || parsedStmt.getStatementType() == StatementType.CHECK; + + } + + @Override + public ParsedPreparedStatement parsePreparedStatement(String sql) { + ParsedPreparedStatement stmt = new ParsedPreparedStatement(); + ClickHouseSqlStatement parsedStmt = parse(sql); + if (parsedStmt.getStatementType() == StatementType.USE) { + stmt.setUseDatabase(parsedStmt.getDatabase()); + } + stmt.setInsert(parsedStmt.getStatementType() == StatementType.INSERT); + stmt.setHasErrors(parsedStmt.getStatementType() == StatementType.UNKNOWN); + stmt.setHasResultSet(isStmtWithResultSet(parsedStmt)); + String tableName = parsedStmt.getTable(); + if (parsedStmt.getDatabase() != null && parsedStmt.getTable() != null) { + tableName = String.format("%s.%s", parsedStmt.getDatabase(), parsedStmt.getTable()); + } + stmt.setTable(tableName); + stmt.setInsertWithSelect(parsedStmt.containsKeyword("SELECT") && (parsedStmt.getStatementType() == StatementType.INSERT)); + stmt.setAssignValuesGroups(parsedStmt.getValueGroups()); + + Integer startIndex = parsedStmt.getPositions().get(ClickHouseSqlStatement.KEYWORD_VALUES_START); + if (startIndex != null) { + int endIndex = parsedStmt.getPositions().get(ClickHouseSqlStatement.KEYWORD_VALUES_END); + stmt.setAssignValuesListStartPosition(startIndex); + stmt.setAssignValuesListStopPosition(endIndex); + String query = parsedStmt.getSQL(); + for (int i = startIndex + 1; i < endIndex; i++) { + char ch = query.charAt(i); + if (ch != '?' && ch != ',' && !Character.isWhitespace(ch)) { + stmt.setUseFunction(true); + break; + } + } + } + + stmt.setUseFunction(false); + parseParameters(sql, stmt); + return stmt; + } + + + public ClickHouseSqlStatement parse(String sql) { + JdbcParseHandler handler = JdbcParseHandler.getInstance(); + ClickHouseSqlStatement[] stmts = ClickHouseSqlParser.parse(sql, handler); + if (stmts.length > 1) { + throw new RuntimeException("More than one SQL statement found: " + sql); + } + return stmts[0]; + } + } + + private static class ANTLR4Parser extends SqlParserFacade { + + @Override + public ParsedStatement parsedStatement(String sql) { + ParsedStatement stmt = new ParsedStatement(); + parseSQL(sql, new ParsedStatementListener(stmt)); + return stmt; + } + + @Override + public ParsedPreparedStatement parsePreparedStatement(String sql) { + ParsedPreparedStatement stmt = new ParsedPreparedStatement(); + parseSQL(sql, new ParsedPreparedStatementListener(stmt)); + parseParameters(sql, stmt); + return stmt; + } + + protected ClickHouseParser parseSQL(String sql, ClickHouseParserBaseListener listener) { + CharStream charStream = CharStreams.fromString(sql); + ClickHouseLexer lexer = new ClickHouseLexer(charStream); + ClickHouseParser parser = new ClickHouseParser(new CommonTokenStream(lexer)); + parser.removeErrorListeners(); + parser.addErrorListener(new ParserErrorListener()); + + ClickHouseParser.QueryStmtContext parseTree = parser.queryStmt(); + IterativeParseTreeWalker.DEFAULT.walk(listener, parseTree); + + return parser; + } + + private static class ParserErrorListener extends BaseErrorListener { + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) { + LOG.debug("SQL syntax error at line: " + line + ", pos: " + charPositionInLine + ", " + msg); + } + } + + static boolean isStmtWithResultSet(ClickHouseParser.QueryStmtContext stmtContext) { + ClickHouseParser.QueryContext qCtx = stmtContext.query(); + return qCtx != null && (qCtx.selectStmt() != null || qCtx.selectUnionStmt() != null || + qCtx.showStmt() != null || qCtx.explainStmt() != null || qCtx.describeStmt() != null || + qCtx.existsStmt() != null || qCtx.checkStmt() != null); + } + + private static class ParsedStatementListener extends ClickHouseParserBaseListener { + + private final ParsedStatement parsedStatement; + + public ParsedStatementListener(ParsedStatement parsedStatement) { + this.parsedStatement = parsedStatement; + } + + @Override + public void visitErrorNode(ErrorNode node) { + parsedStatement.setHasErrors(true); + } + + @Override + public void enterQueryStmt(ClickHouseParser.QueryStmtContext ctx) { + if (isStmtWithResultSet(ctx)) { + parsedStatement.setHasResultSet(true); + } + } + + @Override + public void enterUseStmt(ClickHouseParser.UseStmtContext ctx) { + if (ctx.databaseIdentifier() != null) { + parsedStatement.setUseDatabase(SQLUtils.unquoteIdentifier(ctx.databaseIdentifier().getText())); + } + } + + @Override + public void enterSetRoleStmt(ClickHouseParser.SetRoleStmtContext ctx) { + if (ctx.NONE() != null) { + parsedStatement.setRoles(Collections.emptyList()); + } else { + List roles = new ArrayList<>(); + for (ClickHouseParser.IdentifierContext id : ctx.setRolesList().identifier()) { + roles.add(SQLUtils.unquoteIdentifier(id.getText())); + } + parsedStatement.setRoles(roles); + } + } + } + + protected static class ParsedPreparedStatementListener extends ClickHouseParserBaseListener { + + protected final ParsedPreparedStatement parsedStatement; + + public ParsedPreparedStatementListener(ParsedPreparedStatement parsedStatement) { + this.parsedStatement = parsedStatement; + } + + @Override + public void enterQueryStmt(ClickHouseParser.QueryStmtContext ctx) { + if (isStmtWithResultSet(ctx)) { + parsedStatement.setHasResultSet(true); + } + } + + @Override + public void enterUseStmt(ClickHouseParser.UseStmtContext ctx) { + if (ctx.databaseIdentifier() != null) { + parsedStatement.setUseDatabase(SQLUtils.unquoteIdentifier(ctx.databaseIdentifier().getText())); + } + } + + @Override + public void enterSetRoleStmt(ClickHouseParser.SetRoleStmtContext ctx) { + if (ctx.NONE() != null) { + parsedStatement.setRoles(Collections.emptyList()); + } else { + List roles = new ArrayList<>(); + for (ClickHouseParser.IdentifierContext id : ctx.setRolesList().identifier()) { + roles.add(SQLUtils.unquoteIdentifier(id.getText())); + } + parsedStatement.setRoles(roles); + } + } + + @Override + public void enterColumnExprPrecedence3(ClickHouseParser.ColumnExprPrecedence3Context ctx) { + super.enterColumnExprPrecedence3(ctx); + } + + @Override + public void visitErrorNode(ErrorNode node) { + parsedStatement.setHasErrors(true); + } + + @Override + public void enterInsertParameterFuncExpr(ClickHouseParser.InsertParameterFuncExprContext ctx) { + parsedStatement.setUseFunction(true); + } + + @Override + public void enterAssignmentValuesList(ClickHouseParser.AssignmentValuesListContext ctx) { + parsedStatement.setAssignValuesListStartPosition(ctx.getStart().getStartIndex()); + parsedStatement.setAssignValuesListStopPosition(ctx.getStop().getStopIndex()); + } + + + @Override + public void enterTableExprIdentifier(ClickHouseParser.TableExprIdentifierContext ctx) { + if (ctx.tableIdentifier() != null) { + parsedStatement.setTable(SQLUtils.unquoteIdentifier(ctx.tableIdentifier().getText())); + } + } + + @Override + public void enterInsertStmt(ClickHouseParser.InsertStmtContext ctx) { + ClickHouseParser.TableIdentifierContext tableId = ctx.tableIdentifier(); + if (tableId != null) { + parsedStatement.setTable(SQLUtils.unquoteIdentifier(tableId.getText())); + } + + ClickHouseParser.ColumnsClauseContext columns = ctx.columnsClause(); + if (columns != null) { + List names = columns.nestedIdentifier(); + String[] insertColumns = new String[names.size()]; + for (int i = 0; i < names.size(); i++) { + insertColumns[i] = names.get(i).getText(); + } + parsedStatement.setInsertColumns(insertColumns); + } + + parsedStatement.setInsert(true); + } + + @Override + public void enterDataClauseSelect(ClickHouseParser.DataClauseSelectContext ctx) { + parsedStatement.setInsertWithSelect(true); + } + + @Override + public void enterDataClauseValues(ClickHouseParser.DataClauseValuesContext ctx) { + parsedStatement.setAssignValuesGroups(ctx.assignmentValues().size()); + } + + @Override + public void exitInsertParameterFuncExpr(ClickHouseParser.InsertParameterFuncExprContext ctx) { + parsedStatement.setUseFunction(true); + } + } + } + + private static class ANTLR4AndParamsParser extends ANTLR4Parser { + + @Override + public ParsedPreparedStatement parsePreparedStatement(String sql) { + ParsedPreparedStatement stmt = new ParsedPreparedStatement(); + parseSQL(sql, new ParseStatementAndParamsListener(stmt)); + return stmt; + } + + private static class ParseStatementAndParamsListener extends ParsedPreparedStatementListener { + + public ParseStatementAndParamsListener(ParsedPreparedStatement parsedStatement) { + super(parsedStatement); + } + + @Override + public void enterColumnExprParam(ClickHouseParser.ColumnExprParamContext ctx) { + parsedStatement.appendParameter(ctx.start.getStartIndex()); + } + + + @Override + public void enterCteUnboundColParam(ClickHouseParser.CteUnboundColParamContext ctx) { + parsedStatement.appendParameter(ctx.start.getStartIndex()); + } + + @Override + public void enterInsertParameter(ClickHouseParser.InsertParameterContext ctx) { + parsedStatement.appendParameter(ctx.start.getStartIndex()); + } + + @Override + public void enterFromClause(ClickHouseParser.FromClauseContext ctx) { + if (ctx.JDBC_PARAM_PLACEHOLDER() != null) { + parsedStatement.appendParameter(ctx.JDBC_PARAM_PLACEHOLDER().getSymbol().getStartIndex()); + } + } + + @Override + public void enterViewParam(ClickHouseParser.ViewParamContext ctx) { + if (ctx.JDBC_PARAM_PLACEHOLDER() != null) { + parsedStatement.appendParameter(ctx.JDBC_PARAM_PLACEHOLDER().getSymbol().getStartIndex()); + } + } + } + } + + private static void parseParameters(String originalQuery, ParsedPreparedStatement stmt) { + int len = originalQuery.length(); + for (int i = 0; i < len; i++) { + char ch = originalQuery.charAt(i); + if (ClickHouseUtils.isQuote(ch)) { + i = ClickHouseUtils.skipQuotedString(originalQuery, i, len, ch) - 1; + } else if (ch == '?') { + int idx = ClickHouseUtils.skipContentsUntil(originalQuery, i + 2, len, '?', ':'); + if (idx < len && originalQuery.charAt(idx - 1) == ':' && originalQuery.charAt(idx) != ':' + && originalQuery.charAt(idx - 2) != ':') { + i = idx - 1; + } else { + stmt.appendParameter(i); + } + } else if (ch == ';') { + continue; + } else if (i + 1 < len) { + char nextCh = originalQuery.charAt(i + 1); + if ((ch == '-' && nextCh == ch) || (ch == '#')) { + i = ClickHouseUtils.skipSingleLineComment(originalQuery, i + 2, len) - 1; + } else if (ch == '/' && nextCh == '*') { + i = ClickHouseUtils.skipMultiLineComment(originalQuery, i + 2, len) - 1; + } + } + } + } + + + public enum SQLParser { + /** + * JavaCC used to determine sql type (SELECT, INSERT, etc.) and extract some information + * Separate procedure parses sql for `?` parameter placeholders. + */ + JAVACC, + + /** + * ANTLR4 used to determine sql type (SELECT, INSERT, etc.) and extract some information and parameters + */ + ANTLR4_PARAMS_PARSER, + + /** + * ANTLR4 used to determine sql type (SELECT, INSERT, etc.), extract some information. + * Separate procedure parses sql for `?` parameter placeholders. + */ + ANTLR4 + } + + public static SqlParserFacade getParser(String name) throws SQLException { + try { + SQLParser parserSelection = SQLParser.valueOf(name); + switch (parserSelection) { + case JAVACC: + return new JavaCCParser(); + case ANTLR4_PARAMS_PARSER: + return new ANTLR4AndParamsParser(); + case ANTLR4: + return new ANTLR4Parser(); + } + throw new SQLException("Unsupported parser: " + parserSelection); + } catch (IllegalArgumentException e) { + throw new SQLException("Unknown parser: " + name); + } + } +} diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/ClickHouseSqlStatement.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/ClickHouseSqlStatement.java new file mode 100644 index 000000000..7dfd22876 --- /dev/null +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/ClickHouseSqlStatement.java @@ -0,0 +1,373 @@ +package com.clickhouse.jdbc.internal.parser.javacc; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; + +public class ClickHouseSqlStatement { + public static final String DEFAULT_DATABASE = "system"; + public static final String DEFAULT_TABLE = "unknown"; + + public static final String KEYWORD_DATABASE = "DATABASE"; + public static final String KEYWORD_EXISTS = "EXISTS"; + public static final String KEYWORD_FORMAT = "FORMAT"; + public static final String KEYWORD_REPLACE = "REPLACE"; + public static final String KEYWORD_TOTALS = "TOTALS"; + public static final String KEYWORD_VALUES = "VALUES"; + + public static final String KEYWORD_TABLE_COLUMNS_START = "ColumnsStart"; + public static final String KEYWORD_TABLE_COLUMNS_END = "ColumnsEnd"; + public static final String KEYWORD_VALUES_START = "ValuesStart"; + public static final String KEYWORD_VALUES_END = "ValuesEnd"; + + public static final String ROLES_COUNT_SETTINGS_KEY = "_ROLES_COUNT"; + public static final String ROLES_PREFIX_SETTINGS_KEY = "_ROLE_"; + + private final String sql; + private final StatementType stmtType; + private final String cluster; + private final String database; + private final String table; + private final String input; + private final String compressAlgorithm; + private final String compressLevel; + private final String format; + private final String file; + private final List parameters; + private final Map positions; + private final Map settings; + private final Set tempTables; + private final int valueGroups; + + public ClickHouseSqlStatement(String sql) { + this(sql, StatementType.UNKNOWN, null, null, null, null, null, null, null, null, null, null, null, null, 0); + } + + public ClickHouseSqlStatement(String sql, StatementType stmtType) { + this(sql, stmtType, null, null, null, null, null, null, null, null, null, null, null, null, 0); + } + + public ClickHouseSqlStatement(String sql, StatementType stmtType, String cluster, String database, String table, + String input, String compressAlgorithm, String compressLevel, String format, String file, + List parameters, Map positions, Map settings, + Set tempTables, int valueGroups) { + this.sql = sql; + this.stmtType = stmtType; + + this.cluster = cluster; + this.database = database; + this.table = table == null || table.isEmpty() ? DEFAULT_TABLE : table; + this.input = input; + this.compressAlgorithm = compressAlgorithm; + this.compressLevel = compressLevel; + this.format = format; + this.file = file; + this.valueGroups = valueGroups; + + if (parameters != null && !parameters.isEmpty()) { + this.parameters = Collections.unmodifiableList(parameters); + } else { + this.parameters = Collections.emptyList(); + } + + if (positions != null && !positions.isEmpty()) { + Map p = new HashMap<>(); + for (Entry e : positions.entrySet()) { + String keyword = e.getKey(); + Integer position = e.getValue(); + + if (keyword != null && position != null) { + p.put(keyword, position); + } + } + this.positions = Collections.unmodifiableMap(p); + } else { + this.positions = Collections.emptyMap(); + } + + if (settings != null && !settings.isEmpty()) { + Map s = new LinkedHashMap<>(); + for (Entry e : settings.entrySet()) { + String key = e.getKey(); + String value = e.getValue(); + + if (key != null && value != null) { + s.put(key, String.valueOf(e.getValue())); + } + } + this.settings = Collections.unmodifiableMap(s); + } else { + this.settings = Collections.emptyMap(); + } + + if (tempTables != null && !tempTables.isEmpty()) { + Set s = new LinkedHashSet<>(); + s.addAll(tempTables); + this.tempTables = Collections.unmodifiableSet(s); + } else { + this.tempTables = Collections.emptySet(); + } + } + + public String getSQL() { + return this.sql; + } + + public boolean isRecognized() { + return stmtType != StatementType.UNKNOWN; + } + + public boolean isDDL() { + return this.stmtType.getLanguageType() == LanguageType.DDL; + } + + public boolean isDML() { + return this.stmtType.getLanguageType() == LanguageType.DML; + } + + public boolean isQuery() { + return this.stmtType.getOperationType() == OperationType.READ && !this.hasFile(); + } + + public boolean isMutation() { + return this.stmtType.getOperationType() == OperationType.WRITE || this.hasFile(); + } + + public boolean isTCL() { + return this.stmtType.getLanguageType() == LanguageType.TCL; + } + + public boolean isIdemponent() { + boolean result = this.stmtType.isIdempotent() && !this.hasFile(); + + if (!result) { // try harder + switch (this.stmtType) { + case ATTACH: + case CREATE: + case DETACH: + case DROP: + result = positions.containsKey(KEYWORD_EXISTS) || positions.containsKey(KEYWORD_REPLACE); + break; + + default: + break; + } + } + + return result; + } + + public LanguageType getLanguageType() { + return this.stmtType.getLanguageType(); + } + + public OperationType getOperationType() { + return this.stmtType.getOperationType(); + } + + public StatementType getStatementType() { + return this.stmtType; + } + + public String getCluster() { + return this.cluster; + } + + public String getDatabase() { + return this.database; + } + + public String getDatabaseOrDefault(String database) { + return this.database == null ? (database == null ? DEFAULT_DATABASE : database) : this.database; + } + + public String getTable() { + return this.table; + } + + public String getInput() { + return this.input; + } + + public String getCompressAlgorithm() { + return this.compressAlgorithm; + } + + public String getCompressLevel() { + return this.compressLevel; + } + + public String getFormat() { + return this.format; + } + + public String getFile() { + return this.file; + } + + public String getContentBetweenKeywords(String startKeyword, String endKeyword) { + return getContentBetweenKeywords(startKeyword, endKeyword, 0); + } + + public String getContentBetweenKeywords(String startKeyword, String endKeyword, int startOffset) { + if (startOffset < 0) { + startOffset = 0; + } + Integer startPos = positions.get(startKeyword); + Integer endPos = positions.get(endKeyword); + + String content = ""; + if (startPos != null && endPos != null && startPos + startOffset < endPos) { + content = sql.substring(startPos + startOffset, endPos); + } + + return content; + } + + public boolean containsKeyword(String keyword) { + if (keyword == null || keyword.isEmpty()) { + return false; + } + + return positions.containsKey(keyword.toUpperCase(Locale.ROOT)); + } + + public boolean hasCompressAlgorithm() { + return this.compressAlgorithm != null && !this.compressAlgorithm.isEmpty(); + } + + public boolean hasCompressLevel() { + return this.compressLevel != null && !this.compressLevel.isEmpty(); + } + + public boolean hasFormat() { + return this.format != null && !this.format.isEmpty(); + } + + public boolean hasInput() { + return this.input != null && !this.input.isEmpty(); + } + + public boolean hasFile() { + return this.file != null && !this.file.isEmpty(); + } + + public boolean hasSettings() { + return !this.settings.isEmpty(); + } + + public boolean hasWithTotals() { + return this.positions.containsKey(KEYWORD_TOTALS); + } + + public boolean hasValues() { + return this.positions.containsKey(KEYWORD_VALUES); + } + + public boolean hasTempTable() { + return !this.tempTables.isEmpty(); + } + + public List getParameters() { + return this.parameters; + } + + public int getStartPosition(String keyword) { + int position = -1; + + if (!this.positions.isEmpty() && keyword != null) { + Integer p = this.positions.get(keyword.toUpperCase(Locale.ROOT)); + if (p != null) { + position = p.intValue(); + } + } + + return position; + } + + public int getEndPosition(String keyword) { + int position = getStartPosition(keyword); + + return position != -1 && keyword != null ? position + keyword.length() : position; + } + + public Map getPositions() { + return this.positions; + } + + public int getValueGroups() { + return valueGroups; + } + + public Map getSettings() { + return this.settings; + } + + public Set getTempTables() { + return this.tempTables; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append('[').append(stmtType.name()).append(']').append(" cluster=").append(cluster).append(", database=") + .append(database).append(", table=").append(table).append(", input=").append(input) + .append(", compressAlgorithm=").append(compressAlgorithm).append(", compressLevel=") + .append(compressLevel).append(", format=").append(format).append(", outfile=").append(file) + .append(", parameters=").append(parameters).append(", positions=").append(positions) + .append(", settings=").append(settings).append(", tempTables=").append(settings).append("\nSQL:\n") + .append(sql); + + return sb.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((sql == null) ? 0 : sql.hashCode()); + result = prime * result + ((cluster == null) ? 0 : cluster.hashCode()); + result = prime * result + ((database == null) ? 0 : database.hashCode()); + result = prime * result + table.hashCode(); + result = prime * result + ((input == null) ? 0 : input.hashCode()); + result = prime * result + ((compressAlgorithm == null) ? 0 : compressAlgorithm.hashCode()); + result = prime * result + ((compressLevel == null) ? 0 : compressLevel.hashCode()); + result = prime * result + ((format == null) ? 0 : format.hashCode()); + result = prime * result + ((file == null) ? 0 : file.hashCode()); + result = prime * result + ((stmtType == null) ? 0 : stmtType.hashCode()); + + result = prime * result + parameters.hashCode(); + result = prime * result + positions.hashCode(); + result = prime * result + settings.hashCode(); + result = prime * result + tempTables.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + ClickHouseSqlStatement other = (ClickHouseSqlStatement) obj; + return stmtType == other.stmtType && Objects.equals(sql, other.sql) && Objects.equals(cluster, other.cluster) + && Objects.equals(database, other.database) && Objects.equals(table, other.table) + && Objects.equals(input, other.input) && Objects.equals(compressAlgorithm, other.compressAlgorithm) + && Objects.equals(compressLevel, other.compressLevel) && Objects.equals(format, other.format) + && Objects.equals(file, other.file) && parameters.equals(other.parameters) + && positions.equals(other.positions) && settings.equals(other.settings) + && tempTables.equals(other.tempTables); + } +} diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/ClickHouseSqlUtils.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/ClickHouseSqlUtils.java new file mode 100644 index 000000000..6faf418c4 --- /dev/null +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/ClickHouseSqlUtils.java @@ -0,0 +1,73 @@ +package com.clickhouse.jdbc.internal.parser.javacc; + +public final class ClickHouseSqlUtils { + public static boolean isQuote(char ch) { + return ch == '"' || ch == '\'' || ch == '`'; + } + + /** + * Escape quotes in given string. + * + * @param str string + * @param quote quote to escape + * @return escaped string + */ + public static String escape(String str, char quote) { + if (str == null) { + return str; + } + + int len = str.length(); + StringBuilder sb = new StringBuilder(len + 10).append(quote); + + for (int i = 0; i < len; i++) { + char ch = str.charAt(i); + if (ch == quote || ch == '\\') { + sb.append('\\'); + } + sb.append(ch); + } + + return sb.append(quote).toString(); + } + + /** + * Unescape quoted string. + * + * @param str quoted string + * @return unescaped string + */ + public static String unescape(String str) { + if (str == null || str.isEmpty()) { + return str; + } + + int len = str.length(); + char quote = str.charAt(0); + if (!isQuote(quote) || quote != str.charAt(len - 1)) { // not a quoted string + return str; + } + + StringBuilder sb = new StringBuilder(len = len - 1); + for (int i = 1; i < len; i++) { + char ch = str.charAt(i); + + if (++i >= len) { + sb.append(ch); + } else { + char nextChar = str.charAt(i); + if (ch == '\\' || (ch == quote && nextChar == quote)) { + sb.append(nextChar); + } else { + sb.append(ch); + i--; + } + } + } + + return sb.toString(); + } + + private ClickHouseSqlUtils() { + } +} diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/JdbcParseHandler.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/JdbcParseHandler.java new file mode 100644 index 000000000..4f36c5729 --- /dev/null +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/JdbcParseHandler.java @@ -0,0 +1,165 @@ +package com.clickhouse.jdbc.internal.parser.javacc; + +import com.clickhouse.client.config.ClickHouseDefaults; +import com.clickhouse.data.ClickHouseChecker; +import com.clickhouse.data.ClickHouseFormat; +import com.clickhouse.data.ClickHouseUtils; + + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class JdbcParseHandler extends ParseHandler { + private static final String SETTING_MUTATIONS_SYNC = "mutations_sync"; + + private static final JdbcParseHandler INSTANCE; + + static { + INSTANCE = new JdbcParseHandler(true, true, true); + }; + + public static JdbcParseHandler getInstance() { + return INSTANCE; + } + + private final boolean allowLocalFile; + private final boolean allowLightWeightDelete; + private final boolean allowLightWeightUpdate; + + private void addMutationSetting(String sql, StringBuilder builder, Map positions, + Map settings, int index) { + boolean hasSetting = settings != null && !settings.isEmpty(); + String setting = hasSetting ? settings.get(SETTING_MUTATIONS_SYNC) : null; + if (setting == null) { + String keyword = "SETTINGS"; + Integer settingsIndex = positions.get(keyword); + + if (settingsIndex == null) { + builder.append(sql.substring(index)).append(" SETTINGS mutations_sync=1"); + if (hasSetting) { + builder.append(','); + } + } else { + builder.append(sql.substring(index, settingsIndex)).append("SETTINGS mutations_sync=1,") + .append(sql.substring(settingsIndex + keyword.length())); + } + } else { + builder.append(sql.substring(index)); + } + } + + private ClickHouseSqlStatement handleDelete(String sql, StatementType stmtType, String cluster, String database, + String table, String input, String compressAlgorithm, String compressLevel, String format, String file, + List parameters, Map positions, Map settings, + Set tempTables) { + StringBuilder builder = new StringBuilder(); + int index = positions.get("DELETE"); + if (index > 0) { + builder.append(sql.substring(0, index)); + } + index = positions.get("FROM"); + Integer whereIdx = positions.get("WHERE"); + if (whereIdx != null) { + builder.append("ALTER TABLE "); + if (!ClickHouseChecker.isNullOrEmpty(database)) { + builder.append('`').append(database).append('`').append('.'); + } + builder.append('`').append(table).append('`').append(" DELETE "); + addMutationSetting(sql, builder, positions, settings, whereIdx); + } else { + builder.append("TRUNCATE TABLE").append(sql.substring(index + 4)); + } + return new ClickHouseSqlStatement(builder.toString(), stmtType, cluster, database, table, input, + compressAlgorithm, compressLevel, format, file, parameters, null, settings, null, 0); + } + + private ClickHouseSqlStatement handleUpdate(String sql, StatementType stmtType, String cluster, String database, + String table, String input, String compressAlgorithm, String compressLevel, String format, String file, + List parameters, Map positions, Map settings, + Set tempTables) { + StringBuilder builder = new StringBuilder(); + int index = positions.get("UPDATE"); + if (index > 0) { + builder.append(sql.substring(0, index)); + } + builder.append("ALTER TABLE "); + index = positions.get("SET"); + if (!ClickHouseChecker.isNullOrEmpty(database)) { + builder.append('`').append(database).append('`').append('.'); + } + builder.append('`').append(table).append('`').append(" UPDATE"); // .append(sql.substring(index + 3)); + addMutationSetting(sql, builder, positions, settings, index + 3); + return new ClickHouseSqlStatement(builder.toString(), stmtType, cluster, database, table, input, + compressAlgorithm, compressLevel, format, file, parameters, null, settings, null, 0); + } + + private ClickHouseSqlStatement handleInFileForInsertQuery(String sql, StatementType stmtType, String cluster, + String database, String table, String input, String compressAlgorithm, String compressLevel, String format, + String file, List parameters, Map positions, Map settings, + Set tempTables, int valueGroups) { + StringBuilder builder = new StringBuilder(sql.length()); + builder.append(sql.substring(0, positions.get("FROM"))); + Integer index = positions.get("SETTINGS"); + if (index == null || index < 0) { + index = positions.get("FORMAT"); + } + if (index != null && index > 0) { + builder.append(sql.substring(index)); + } else { + ClickHouseFormat f = ClickHouseFormat.fromFileName(ClickHouseUtils.unescape(file)); + if (f == null) { + f = (ClickHouseFormat) ClickHouseDefaults.FORMAT.getDefaultValue(); + } + format = f.name(); + builder.append("FORMAT ").append(format); + } + return new ClickHouseSqlStatement(builder.toString(), stmtType, cluster, database, table, input, + compressAlgorithm, compressLevel, format, file, parameters, null, settings, null, valueGroups); + } + + private ClickHouseSqlStatement handleOutFileForSelectQuery(String sql, StatementType stmtType, String cluster, + String database, String table, String input, String compressAlgorithm, String compressLevel, String format, + String file, List parameters, Map positions, Map settings, + Set tempTables) { + StringBuilder builder = new StringBuilder(sql.length()); + builder.append(sql.substring(0, positions.get("INTO"))); + Integer index = positions.get("FORMAT"); + if (index != null && index > 0) { + builder.append(sql.substring(index)); + } + return new ClickHouseSqlStatement(builder.toString(), stmtType, cluster, database, table, input, + compressAlgorithm, compressLevel, format, file, parameters, null, settings, null, 0); + } + + @Override + public ClickHouseSqlStatement handleStatement(String sql, StatementType stmtType, String cluster, String database, + String table, String input, String compressAlgorithm, String compressLevel, String format, String file, + List parameters, Map positions, Map settings, + Set tempTables, int valueGroups) { + boolean hasFile = allowLocalFile && !ClickHouseChecker.isNullOrEmpty(file) && file.charAt(0) == '\''; + ClickHouseSqlStatement s = null; + if (stmtType == StatementType.DELETE) { + s = allowLightWeightDelete ? s + : handleDelete(sql, stmtType, cluster, database, table, input, compressAlgorithm, compressLevel, + format, file, parameters, positions, settings, tempTables); + } else if (stmtType == StatementType.UPDATE) { + s = allowLightWeightUpdate ? s + : handleUpdate(sql, stmtType, cluster, database, table, input, compressAlgorithm, compressLevel, + format, file, parameters, positions, settings, tempTables); + } else if (stmtType == StatementType.INSERT && hasFile) { + s = handleInFileForInsertQuery(sql, stmtType, cluster, database, table, input, compressAlgorithm, + compressLevel, format, file, parameters, positions, settings, tempTables, valueGroups); + } else if (stmtType == StatementType.SELECT && hasFile) { + s = handleOutFileForSelectQuery(sql, stmtType, cluster, database, table, input, compressAlgorithm, + compressLevel, format, file, parameters, positions, settings, tempTables); + } + return s; + } + + private JdbcParseHandler(boolean allowLightWeightDelete, boolean allowLightWeightUpdate, boolean allowLocalFile) { + this.allowLightWeightDelete = allowLightWeightDelete; + this.allowLightWeightUpdate = allowLightWeightUpdate; + this.allowLocalFile = allowLocalFile; + } +} diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/LanguageType.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/LanguageType.java new file mode 100644 index 000000000..1198930e8 --- /dev/null +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/LanguageType.java @@ -0,0 +1,9 @@ +package com.clickhouse.jdbc.internal.parser.javacc; + +public enum LanguageType { + UNKNOWN, // unknown language + DCL, // data control language + DDL, // data definition language + DML, // data manipulation language + TCL // transaction control language +} diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/OperationType.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/OperationType.java new file mode 100644 index 000000000..06650cade --- /dev/null +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/OperationType.java @@ -0,0 +1,5 @@ +package com.clickhouse.jdbc.internal.parser.javacc; + +public enum OperationType { + UNKNOWN, READ, WRITE +} diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/ParseHandler.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/ParseHandler.java new file mode 100644 index 000000000..6f1af61a6 --- /dev/null +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/ParseHandler.java @@ -0,0 +1,58 @@ +package com.clickhouse.jdbc.internal.parser.javacc; + + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public abstract class ParseHandler { + /** + * Handle macro like "#include('/tmp/template.sql')". + * + * @param name name of the macro + * @param parameters parameters + * @return output of the macro, could be null or empty string + */ + public String handleMacro(String name, List parameters) { + return null; + } + + /** + * Handle parameter. + * + * @param cluster cluster + * @param database database + * @param table table + * @param columnIndex columnIndex(starts from 1 not 0) + * @return parameter value + */ + public String handleParameter(String cluster, String database, String table, int columnIndex) { + return null; + } + + /** + * Hanlde statemenet. + * + * @param sql sql statement + * @param stmtType statement type + * @param cluster cluster + * @param database database + * @param table table + * @param compressAlgorithm compression algorithm + * @param compressLevel compression level + * @param format format + * @param input input + * @param file infile or outfile + * @param parameters positions of parameters + * @param positions keyword positions + * @param settings settings + * @param tempTables temporary tables + * @return sql statement, or null means no change + */ + public ClickHouseSqlStatement handleStatement(String sql, StatementType stmtType, String cluster, String database, + String table, String input, String compressAlgorithm, String compressLevel, String format, String file, + List parameters, Map positions, Map settings, + Set tempTables, int valueGroup) { + return null; + } +} diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/StatementType.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/StatementType.java new file mode 100644 index 000000000..ecf7af12d --- /dev/null +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/StatementType.java @@ -0,0 +1,58 @@ +package com.clickhouse.jdbc.internal.parser.javacc; + +public enum StatementType { + UNKNOWN(LanguageType.UNKNOWN, OperationType.UNKNOWN, false), // unknown statement + ALTER(LanguageType.DDL, OperationType.UNKNOWN, false), // alter statement + ALTER_DELETE(LanguageType.DML, OperationType.WRITE, false), // delete statement + ALTER_UPDATE(LanguageType.DML, OperationType.WRITE, false), // update statement + ATTACH(LanguageType.DDL, OperationType.UNKNOWN, false), // attach statement + CHECK(LanguageType.DDL, OperationType.UNKNOWN, true), // check statement + CREATE(LanguageType.DDL, OperationType.UNKNOWN, false), // create statement + DELETE(LanguageType.DML, OperationType.WRITE, false), // the upcoming light-weight delete statement + DESCRIBE(LanguageType.DDL, OperationType.READ, true), // describe/desc statement + DETACH(LanguageType.DDL, OperationType.UNKNOWN, false), // detach statement + DROP(LanguageType.DDL, OperationType.UNKNOWN, false), // drop statement + EXISTS(LanguageType.DML, OperationType.READ, true), // exists statement + EXPLAIN(LanguageType.DDL, OperationType.READ, true), // explain statement + GRANT(LanguageType.DCL, OperationType.UNKNOWN, true), // grant statement + INSERT(LanguageType.DML, OperationType.WRITE, false), // insert statement + KILL(LanguageType.DCL, OperationType.UNKNOWN, false), // kill statement + OPTIMIZE(LanguageType.DDL, OperationType.UNKNOWN, false), // optimize statement + RENAME(LanguageType.DDL, OperationType.UNKNOWN, false), // rename statement + REVOKE(LanguageType.DCL, OperationType.UNKNOWN, true), // revoke statement + SELECT(LanguageType.DML, OperationType.READ, true), // select statement + SET(LanguageType.DCL, OperationType.UNKNOWN, true), // set statement + SHOW(LanguageType.DDL, OperationType.READ, true), // show statement + SYSTEM(LanguageType.DDL, OperationType.UNKNOWN, false), // system statement + TRUNCATE(LanguageType.DDL, OperationType.UNKNOWN, true), // truncate statement + UPDATE(LanguageType.DML, OperationType.WRITE, false), // the upcoming light-weight update statement + USE(LanguageType.DDL, OperationType.UNKNOWN, true), // use statement + WATCH(LanguageType.DDL, OperationType.UNKNOWN, true), // watch statement + TRANSACTION(LanguageType.TCL, OperationType.WRITE, true), // TCL statement + UNDROP(LanguageType.DDL, OperationType.UNKNOWN, false), + MOVE(LanguageType.DCL, OperationType.UNKNOWN, false), + EXCHANGE(LanguageType.DML, OperationType.UNKNOWN, false), + + ; + private LanguageType langType; + private OperationType opType; + private boolean idempotent; + + StatementType(LanguageType langType, OperationType operationType, boolean idempotent) { + this.langType = langType; + this.opType = operationType; + this.idempotent = idempotent; + } + + LanguageType getLanguageType() { + return this.langType; + } + + OperationType getOperationType() { + return this.opType; + } + + boolean isIdempotent() { + return this.idempotent; + } +} diff --git a/jdbc-v2/src/main/javacc/ClickHouseSqlParser.jj b/jdbc-v2/src/main/javacc/ClickHouseSqlParser.jj index e3eadced5..fae8e3cdd 100644 --- a/jdbc-v2/src/main/javacc/ClickHouseSqlParser.jj +++ b/jdbc-v2/src/main/javacc/ClickHouseSqlParser.jj @@ -25,7 +25,7 @@ options { PARSER_BEGIN(ClickHouseSqlParser) -package com.clickhouse.jdbc.parser; +package com.clickhouse.jdbc.internal.parser.javacc; import java.io.StringReader; @@ -51,7 +51,6 @@ public class ClickHouseSqlParser { private final List statements = new ArrayList<>(); - private ClickHouseConfig config; private ParseHandler handler; private int anyArgsListStart = -1; @@ -76,14 +75,7 @@ public class ClickHouseSqlParser { return !(getToken(1).kind == AND && token_source.parentToken == BETWEEN); } - public static ClickHouseSqlStatement[] parse(String sql, ClickHouseConfig config) { - return parse(sql, config, null); - } - - public static ClickHouseSqlStatement[] parse(String sql, ClickHouseConfig config, ParseHandler handler) { - if (config == null) { - config = new ClickHouseConfig(); - } + public static ClickHouseSqlStatement[] parse(String sql, ParseHandler handler) { ClickHouseSqlStatement[] stmts = new ClickHouseSqlStatement[] { new ClickHouseSqlStatement(sql, StatementType.UNKNOWN) }; @@ -92,7 +84,7 @@ public class ClickHouseSqlParser { return stmts; } - ClickHouseSqlParser p = new ClickHouseSqlParser(sql, config, handler); + ClickHouseSqlParser p = new ClickHouseSqlParser(sql, handler); try { stmts = p.sql(); } catch (Exception e) { @@ -106,10 +98,9 @@ public class ClickHouseSqlParser { return stmts; } - public ClickHouseSqlParser(String sql, ClickHouseConfig config, ParseHandler handler) { + public ClickHouseSqlParser(String sql, ParseHandler handler) { this(new StringReader(sql)); - this.config = config; this.handler = handler; } @@ -183,6 +174,7 @@ TOKEN_MGR_DECLS: { String compressLevel = null; String format = null; String file = null; + int valueGroups = 0; final List parameters = new ArrayList<>(); final Map positions = new HashMap<>(); @@ -285,12 +277,12 @@ TOKEN_MGR_DECLS: { if (handler != null) { s = handler.handleStatement( - sqlStmt, stmtType, cluster, database, table, input, compressAlgorithm, compressLevel, format, file, parameters, positions, settings, tempTables); + sqlStmt, stmtType, cluster, database, table, input, compressAlgorithm, compressLevel, format, file, parameters, positions, settings, tempTables, valueGroups); } if (s == null) { s = new ClickHouseSqlStatement( - sqlStmt, stmtType, cluster, database, table, input, compressAlgorithm, compressLevel, format, file, parameters, positions, settings, tempTables); + sqlStmt, stmtType, cluster, database, table, input, compressAlgorithm, compressLevel, format, file, parameters, positions, settings, tempTables, valueGroups); } // reset variables @@ -332,6 +324,9 @@ TOKEN_MGR_DECLS: { this.settings.put(key.toLowerCase(Locale.ROOT), value); } + void incValueGroup() { + this.valueGroups++; + } } SKIP: { @@ -375,7 +370,7 @@ SKIP: { } } } - | { append(image); } + | { append(image); } | "/*" { commentNestingDepth = 1; append(image); }: MULTI_LINE_COMMENT } @@ -420,6 +415,7 @@ void stmt(): {} { | detachStmt() { token_source.stmtType = StatementType.DETACH; } | dropStmt() { token_source.stmtType = StatementType.DROP; } | existsStmt() { token_source.stmtType = StatementType.EXISTS; } + | exchangeStmt() { token_source.stmtType = StatementType.EXCHANGE; } | explainStmt() { token_source.stmtType = StatementType.EXPLAIN; } | insertStmt() { token_source.stmtType = StatementType.INSERT; } | grantStmt() { token_source.stmtType = StatementType.GRANT; } @@ -427,15 +423,18 @@ void stmt(): {} { | optimizeStmt() { token_source.stmtType = StatementType.OPTIMIZE; } | renameStmt() { token_source.stmtType = StatementType.RENAME; } | revokeStmt() { token_source.stmtType = StatementType.REVOKE; } + | selectStmt() { token_source.stmtType = StatementType.SELECT; } | selectStmt() { token_source.stmtType = StatementType.SELECT; } | setStmt() { token_source.stmtType = StatementType.SET; } | showStmt() { token_source.stmtType = StatementType.SHOW; } | systemStmt() { token_source.stmtType = StatementType.SYSTEM; } | truncateStmt() { token_source.stmtType = StatementType.TRUNCATE; } + | undropStmt() { token_source.stmtType = StatementType.UNDROP; } | updateStmt() { token_source.stmtType = StatementType.UPDATE; } | useStmt() { token_source.stmtType = StatementType.USE; } | watchStmt() { token_source.stmtType = StatementType.WATCH; } | txStmt() { token_source.stmtType = StatementType.TRANSACTION; } + | moveStmt() { token_source.stmtType = StatementType.MOVE; } } // https://clickhouse.tech/docs/en/sql-reference/statements/alter/ @@ -480,9 +479,21 @@ void checkStmt(): {} { // not interested anyExprList() } +void undropStmt(): {} { // not interested + anyExprList() +} + +void moveStmt(): {} { // not interested + (LOOKAHEAD(2) | )? anyExprList() +} + +void exchangeStmt(): {} { // not interested + ( | ) anyExprList() +} + // https://clickhouse.tech/docs/en/sql-reference/statements/create/ void createStmt(): {} { - ( + (LOOKAHEAD(2) )? ( LOOKAHEAD(2) ( { token_source.addPosition(token); } @@ -578,12 +589,12 @@ void dataClause(): {} { try { LOOKAHEAD(2) { token_source.addPosition(token); } { token_source.addCustomKeywordPosition(ClickHouseSqlStatement.KEYWORD_VALUES_START, token); } - columnExprList() + columnExprList() { token_source.incValueGroup(); } { token_source.addCustomKeywordPosition(ClickHouseSqlStatement.KEYWORD_VALUES_END, token); } ( LOOKAHEAD(2) ()? - { token_source.removePosition(ClickHouseSqlStatement.KEYWORD_VALUES_START); } + { token_source.removePosition(ClickHouseSqlStatement.KEYWORD_VALUES_START); token_source.incValueGroup(); } columnExprList() { token_source.removePosition(ClickHouseSqlStatement.KEYWORD_VALUES_END); } )* @@ -621,7 +632,6 @@ void revokeStmt(): {} { // not interested // https://clickhouse.tech/docs/en/sql-reference/statements/select/ void selectStmt(): {} { - // FIXME with (select 1), (select 2), 3 select * (withClause())? | t = | t = | t = - | t = | t = | t = | t = + | t = | t = | t = | t = | t = | t = | t = | t = + | t = | t = | t = | t = | t = | t = | t = | t = - | t = | t = | t = | t = + | t = | t = | t = | t = | t = | t = | t = | t = + | t = | t = | t = | t = | t =