Skip to content

Commit 13535b2

Browse files
authored
Merge pull request #2725 from ClickHouse/01/26/26/sql_parser_keywords
[JDBC-v2] Fix parser to property handle keywords in aliases and table names
2 parents bb676e6 + a4ca320 commit 13535b2

File tree

8 files changed

+1805
-199
lines changed

8 files changed

+1805
-199
lines changed

jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/parser/antlr4/ClickHouseParser.g4

Lines changed: 364 additions & 37 deletions
Large diffs are not rendered by default.

jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/ClickHouseSqlUtils.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,80 @@
11
package com.clickhouse.jdbc.internal.parser.javacc;
22

3+
import java.util.Collections;
4+
import java.util.LinkedHashSet;
5+
import java.util.Set;
6+
37
public final class ClickHouseSqlUtils {
8+
public static final String KEYWORD_GROUP_ALLOWED_ALIASES = "allowed_keyword_aliases";
9+
10+
private static final Set<String> ALLOWED_KEYWORD_ALIASES = initAllowedKeywordAliases();
11+
12+
private static Set<String> initAllowedKeywordAliases() {
13+
return buildKeywordSet(
14+
"ACCESS", "ACTION", "ADD", "ADMIN", "AFTER", "ALGORITHM", "ALIAS", "ALLOWED_LATENESS", "ALTER",
15+
"AND", "APPEND", "APPLY", "ASC", "ASCENDING", "ASSUME", "AST", "ASYNC", "ATTACH",
16+
"AUTHENTICATION", "AUTO_INCREMENT", "AZURE", "BACKUP", "BAGEXPANSION", "BASE_BACKUP",
17+
"BCRYPT_HASH", "BCRYPT_PASSWORD", "BEGIN", "BIDIRECTIONAL", "BOTH", "BY", "CACHE", "CACHES",
18+
"CASCADE", "CASE", "CAST", "CHANGE", "CHANGEABLE_IN_READONLY", "CHANGED", "CHAR", "CHARACTER",
19+
"CHECK", "CLEANUP", "CLEAR", "CLONE", "CLUSTER", "CLUSTERS", "CLUSTER_HOST_IDS", "CN", "CODEC",
20+
"COLLATE", "COLLECTION", "COLUMN", "COLUMNS", "COMMENT", "COMMIT", "COMPRESSION", "CONNECTIONS",
21+
"CONST", "CONSTRAINT", "COPY", "CREATE", "CUBE", "CURRENT", "CURRENT_USER", "CURRENTUSER",
22+
"D", "DATA", "DATABASE", "DATABASES", "DATE", "DAY", "DAYS", "DD", "DDL", "DEALLOCATE",
23+
"DEDUPLICATE", "DEFAULT", "DEFINER", "DELAY", "DELETE", "DELETED", "DEPENDS", "DESC",
24+
"DESCENDING", "DESCRIBE", "DETACH", "DETACHED", "DICTIONARIES", "DICTIONARY", "DISK", "DISTINCT",
25+
"DIV", "DOUBLE_SHA1_HASH", "DOUBLE_SHA1_PASSWORD", "DROP", "EMPTY", "ENABLED", "END", "ENFORCED",
26+
"ENGINE", "ENGINES", "EPHEMERAL", "ESTIMATE", "EVENT", "EVENTS", "EVERY", "EXCHANGE", "EXECUTE",
27+
"EXISTS", "EXPLAIN", "EXPRESSION", "EXTENDED", "EXTERNAL", "FAKE", "FALSE", "FETCH", "FIELDS",
28+
"FILE", "FILES", "FILESYSTEM", "FILL", "FILTER", "FIRST", "FOLLOWING", "FOR", "FORCE", "FOREIGN",
29+
"FORGET", "FREEZE", "FULLTEXT", "FUNCTION", "FUNCTIONS", "GRANT", "GRANTEES", "GRANTS",
30+
"GRANULARITY", "GROUPING", "GROUPS", "H", "HASH", "HDFS", "HH", "HIERARCHICAL", "HOST", "HOUR",
31+
"HOURS", "HTTP", "ID", "IDENTIFIED", "IF", "IGNORE", "IMPLICIT", "IN", "INDEX", "INDEXES",
32+
"INDICES", "INFILE", "INHERIT", "INJECTIVE", "INSERT", "INTERPOLATE", "INTERVAL", "INVISIBLE",
33+
"INVOKER", "IP", "IS", "IS_OBJECT_ID", "JWT", "KERBEROS", "KEY", "KEYED", "KEYS", "KILL", "KIND",
34+
"LARGE", "LAST", "LAYOUT", "LDAP", "LEADING", "LESS", "LEVEL", "LIFETIME", "LIGHTWEIGHT",
35+
"LIMITS", "LINEAR", "LIST", "LIVE", "LOCAL", "M", "MASK", "MASKING", "MASTER", "MATCH",
36+
"MATERIALIZE", "MATERIALIZED", "MAX", "MCS", "MEMORY", "MERGES", "METHODS", "METRICS", "MI",
37+
"MICROSECOND", "MICROSECONDS", "MILLISECOND", "MILLISECONDS", "MIN", "MINUTE", "MINUTES", "MM",
38+
"MOD", "MODIFY", "MONTH", "MONTHS", "MOVE", "MS", "MUTATION", "N", "NAME", "NAMED", "NANOSECOND",
39+
"NANOSECONDS", "NEW", "NEXT", "NO", "NO_AUTHENTICATION", "NONE", "NO_PASSWORD", "NS", "NULL",
40+
"NULLS", "OBJECT", "OPTIMIZE", "OPTION", "OR", "OUTER", "OUTFILE", "OVER", "OVERRIDABLE",
41+
"OVERRIDE", "PART", "PARTIAL", "PARTITION", "PARTITIONS", "PART_MOVE_TO_SHARD", "PARTS",
42+
"PATCHES", "PAUSE", "PERIODIC", "PERMANENTLY", "PERMISSIVE", "PERSISTENT", "PIPELINE", "PLAN",
43+
"PLAINTEXT_PASSWORD", "POLICY", "POPULATE", "PRECEDING", "PRECISION", "PREFIX", "PREPARE",
44+
"PRIMARY", "PRIORITY", "PRIVILEGES", "PROCESSLIST", "PROFILE", "PROFILES", "PROJECTION",
45+
"PROTOBUF", "PULL", "Q", "QQ", "QUARTER", "QUARTERS", "QUERY", "QUOTA", "RANDOMIZE",
46+
"RANDOMIZED", "RANGE", "READ", "READONLY", "REALM", "RECOMPRESS", "RECURSIVE", "REFERENCES",
47+
"REFRESH", "REGEXP", "REMOVE", "RENAME", "REPLACE", "REPLICATED", "RESET", "RESOURCE", "RESPECT",
48+
"RESTORE", "RESTRICT", "RESTRICTIVE", "RESUME", "REVOKE", "REWRITE", "ROLE", "ROLES", "ROLLBACK",
49+
"ROLLUP", "ROW", "ROWS", "S", "S3", "SALT", "SAN", "SCHEME", "SCRAM_SHA256_HASH",
50+
"SCRAM_SHA256_PASSWORD", "SECOND", "SECONDS", "SECURITY", "SELECT", "SEQUENTIAL", "SERVER",
51+
"SET", "SETS", "SETTING", "SHA256_HASH", "SHA256_PASSWORD", "SHARD", "SHOW", "SIGNED", "SIMPLE",
52+
"SKIP", "SNAPSHOT", "SOURCE", "SPATIAL", "SQL", "SQL_TSI_DAY", "SQL_TSI_HOUR",
53+
"SQL_TSI_MICROSECOND", "SQL_TSI_MILLISECOND", "SQL_TSI_MINUTE", "SQL_TSI_MONTH",
54+
"SQL_TSI_NANOSECOND", "SQL_TSI_QUARTER", "SQL_TSI_SECOND", "SQL_TSI_WEEK", "SQL_TSI_YEAR", "SS",
55+
"SSH_KEY", "SSL_CERTIFICATE", "STALENESS", "START", "STATISTIC", "STATISTICS", "STDOUT", "STEP",
56+
"STORAGE", "STRICT", "STRICTLY_ASCENDING", "SUBPARTITION", "SUBPARTITIONS", "SUSPEND", "SYNC",
57+
"SYNTAX", "SYSTEM", "TABLE", "TABLES", "TAG", "TAGS", "TEMPORARY", "TEST", "THAN", "THEN",
58+
"THREAD", "TIES", "TIME", "TIMESTAMP", "TO", "TOP", "TOTALS", "TRACKING", "TRAILING",
59+
"TRANSACTION", "TREE", "TRIGGER", "TRUE", "TRUNCATE", "TTL", "TYPE", "TYPEOF", "UNBOUNDED",
60+
"UNDROP", "UNFREEZE", "UNIQUE", "UNLOCK", "UNSET", "UNSIGNED", "UNTIL", "UPDATE", "URL", "USE",
61+
"USER", "VALID", "VALUES", "VARYING", "VIEW", "VISIBLE", "VOLUME", "WATCH", "WATERMARK", "WEEK",
62+
"WEEKS", "WHEN", "WITH_ITEMINDEX", "WK", "WORKER", "WORKLOAD", "WRITABLE", "WRITE", "WW",
63+
"YEAR", "YEARS", "YY", "YYYY", "ZKPATH");
64+
}
65+
66+
private static Set<String> buildKeywordSet(String... values) {
67+
LinkedHashSet<String> keywords = new LinkedHashSet<>();
68+
if (values != null) {
69+
Collections.addAll(keywords, values);
70+
}
71+
return Collections.unmodifiableSet(keywords);
72+
}
73+
74+
public static Set<String> getKeywordGroup(String groupName) {
75+
return KEYWORD_GROUP_ALLOWED_ALIASES.equals(groupName) ? ALLOWED_KEYWORD_ALIASES : Collections.emptySet();
76+
}
77+
478
public static boolean isQuote(char ch) {
579
return ch == '"' || ch == '\'' || ch == '`';
680
}

jdbc-v2/src/main/javacc/ClickHouseSqlParser.jj

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ public class ClickHouseSqlParser {
4949

5050
private static final Logger log = LoggerFactory.getLogger(ClickHouseSqlParser.class);
5151

52+
private static final Set<String> ALLOWED_ALIAS_KEYWORDS =
53+
ClickHouseSqlUtils.getKeywordGroup(ClickHouseSqlUtils.KEYWORD_GROUP_ALLOWED_ALIASES);
54+
55+
private static boolean isAllowedAlias(Token t) {
56+
return t != null && ALLOWED_ALIAS_KEYWORDS.contains(t.image.toUpperCase(Locale.ROOT));
57+
}
58+
5259
private final List<ClickHouseSqlStatement> statements = new ArrayList<>();
5360

5461
private ParseHandler handler;
@@ -520,7 +527,12 @@ void deleteStmt(): {} {
520527
// https://clickhouse.tech/docs/en/sql-reference/statements/describe-table/
521528
void describeStmt(): {} {
522529
(<DESCRIBE> | <DESC>) { token_source.table = "columns"; }
523-
(LOOKAHEAD({ getToken(1).kind == TABLE }) <TABLE>)? tableIdentifier(true) (anyExprList())?
530+
(LOOKAHEAD({ getToken(1).kind == TABLE }) <TABLE>)?
531+
(
532+
LOOKAHEAD({ getToken(1).kind == LPAREN }) <LPAREN> anyExprList() <RPAREN>
533+
| tableIdentifier(true)
534+
)
535+
(anyExprList())?
524536
}
525537

526538
// https://clickhouse.tech/docs/en/sql-reference/statements/detach/
@@ -575,8 +587,13 @@ void grantStmt(): {} { // not interested
575587
void insertStmt(): {} {
576588
<INSERT> <INTO>
577589
(
578-
LOOKAHEAD({ getToken(1).kind == FUNCTION }) <FUNCTION> functionExpr()
579-
| (LOOKAHEAD(2) <TABLE>)? tableIdentifier(true)
590+
LOOKAHEAD({ getToken(1).kind == FUNCTION
591+
&& !tokenIn(2, VALUES, FORMAT, SETTINGS, SELECT, WITH, INFILE)
592+
&& getToken(3).kind == LPAREN }) <FUNCTION> functionExpr()
593+
| (
594+
LOOKAHEAD({ getToken(1).kind == TABLE
595+
&& !tokenIn(2, VALUES, FORMAT, SETTINGS, SELECT, WITH, LPAREN) }) <TABLE>
596+
)? tableIdentifier(true)
580597
)
581598
(
582599
LOOKAHEAD(2) infilePart()
@@ -678,6 +695,7 @@ void showStmt(): {} {
678695
(<DATABASE> databaseIdentifier(true))
679696
| LOOKAHEAD(2) (LOOKAHEAD(1) <CREATE>)? (LOOKAHEAD(1) <SETTINGS>)? <PROFILE> anyIdentifier()
680697
| LOOKAHEAD(2) (<DICTIONARY> tableIdentifier(true))
698+
| LOOKAHEAD(2) (LOOKAHEAD(1) <CHANGED>)? <SETTINGS> { token_source.table = "settings"; }
681699
| LOOKAHEAD(2) ((LOOKAHEAD(2) <TEMPORARY>)? (LOOKAHEAD(2) <TABLE>)? tableIdentifier(true))
682700
)
683701
)
@@ -725,12 +743,12 @@ void columnExprList(): {} {
725743

726744
void withExpr(): {} {
727745
nestedExpr()
746+
(LOOKAHEAD(2) <LBRACKET> anyExprList() <RBRACKET>)*
728747
(
729748
(
730749
LOOKAHEAD({ getToken(1).kind == FLOATING_LITERAL })
731750
<FLOATING_LITERAL> | <DOT> <DECIMAL_LITERAL>
732751
)+
733-
| (LOOKAHEAD(2) <LBRACKET> anyExprList() <RBRACKET>)+
734752
| LOOKAHEAD(2) <IS> (<NOT>)? <NULL>
735753
| LOOKAHEAD(2) (<NOT>)? betweenExpr()
736754
| LOOKAHEAD(2) (<NOT>)? (<ILIKE> | <LIKE>) nestedExpr()
@@ -747,12 +765,12 @@ void columnsExpr(): {} {
747765
LOOKAHEAD(2) (<APPLY> | <EXCEPT> | <REPLACE>) <LPAREN> anyExprList() <RPAREN>
748766
)*
749767
| nestedExpr()
768+
(LOOKAHEAD(2) <LBRACKET> anyExprList() <RBRACKET>)*
750769
(
751770
(
752771
LOOKAHEAD({ getToken(1).kind == FLOATING_LITERAL })
753772
<FLOATING_LITERAL> | <DOT> <DECIMAL_LITERAL>
754773
)+
755-
| (LOOKAHEAD(2) <LBRACKET> anyExprList() <RBRACKET>)+
756774
| LOOKAHEAD(2) <IS> (<NOT>)? <NULL>
757775
| LOOKAHEAD(2) (<NOT>)? betweenExpr()
758776
| LOOKAHEAD(2) (<NOT>)? (<ILIKE> | <LIKE>) nestedExpr()
@@ -773,11 +791,11 @@ void nestedExpr(): {} {
773791
(<WHEN> nestedExpr() <THEN> nestedExpr())+ (<ELSE> nestedExpr())? <END>
774792
| LOOKAHEAD(2) <INTERVAL> (LOOKAHEAD(2) <STRING_LITERAL> | nestedExpr() interval())
775793
| columnExpr()
794+
(LOOKAHEAD(2) <LBRACKET> anyExprList() <RBRACKET>)*
776795
(
777796
(
778797
<FLOATING_LITERAL> | <DOT> <DECIMAL_LITERAL>
779798
)+
780-
| (LOOKAHEAD(2) <LBRACKET> anyExprList() <RBRACKET>)+
781799
| LOOKAHEAD(2) <IS> (<NOT>)? <NULL>
782800
| LOOKAHEAD(2) (<NOT>)? betweenExpr()
783801
| LOOKAHEAD(2) (<NOT>)? (<ILIKE> | <LIKE>) nestedExpr()
@@ -843,7 +861,7 @@ void outfilePart(): {} {
843861
}
844862

845863
void settingsPart(): {} {
846-
<SETTINGS> { token_source.addPosition(token); } settingExprList()
864+
(LOOKAHEAD(1) <CHANGED>)? <SETTINGS> { token_source.addPosition(token); } (LOOKAHEAD(2) settingExprList())?
847865
}
848866

849867
void withTotalPart(): {} {
@@ -888,13 +906,25 @@ void anyColumnExpr(): {} {
888906
| nestedIdentifier()
889907
}
890908

909+
Token aliasIdentifier(): { Token t; } {
910+
(
911+
t = <IDENTIFIER>
912+
| t = <BACK_QUOTED_NAME>
913+
| t = <DOUBLE_QUOTED_NAME>
914+
| t = variable()
915+
| LOOKAHEAD({ isAllowedAlias(getToken(1)) })
916+
t = anyKeyword()
917+
)
918+
{ return t; }
919+
}
920+
891921
Token aliasExpr(): { Token t = null; } {
892922
(
893-
LOOKAHEAD(2) <AS> t = anyIdentifier()
923+
LOOKAHEAD(2) <AS> t = aliasIdentifier()
894924
| LOOKAHEAD(2) formatPart()
895925
| LOOKAHEAD(2) settingsPart()
896926
| LOOKAHEAD(2) outfilePart()
897-
| t = identifier()
927+
| t = aliasIdentifier()
898928
)
899929
{ return t; }
900930
}
@@ -950,10 +980,12 @@ void settingExprList(): {} {
950980
}
951981

952982
void settingExpr(): { String key; } {
953-
identifier() { key = token.image; } <EQ_SINGLE> literal() { token_source.addSetting(key, token.image); }
983+
anyIdentifier() { key = token.image; } <EQ_SINGLE> literal() { token_source.addSetting(key, token.image); }
954984
}
955985

956-
// basics
986+
// --- Base definitions
987+
988+
957989
Token anyIdentifier(): { Token t; } {
958990
(
959991
t = <BACK_QUOTED_NAME>
@@ -965,17 +997,6 @@ Token anyIdentifier(): { Token t; } {
965997
{ return t; }
966998
}
967999

968-
Token identifier(): { Token t; } {
969-
(
970-
t = <BACK_QUOTED_NAME>
971-
| t = <DOUBLE_QUOTED_NAME>
972-
| t = variable()
973-
| t = <IDENTIFIER>
974-
| t = keyword()
975-
)
976-
{ return t; }
977-
}
978-
9791000
void interval(): {} {
9801001
<SECOND> | <MINUTE> | <HOUR> | <DAY> | <WEEK> | <MONTH> | <QUARTER> | <YEAR>
9811002
}
@@ -1045,15 +1066,15 @@ Token anyKeyword(): { Token t; } {
10451066
| t = <MOVE> | t = <OPTIMIZE> | t = <RENAME> | t = <REVOKE> | t = <SELECT> | t = <SET> | t = <SHOW> | t = <SYSTEM>
10461067
| t = <TRUNCATE> | t = <UPDATE> | t = <USE> | t = <WATCH> | t = <UNDROP>
10471068
// others
1048-
| t = <ALL> | t = <AND> | t = <APPLY> | t = <ARRAY> | t = <AS> | t = <ASOF> | t = <BETWEEN> | t = <CASE>
1049-
| t = <CLUSTER> | t = <COMPRESSION> | t = <DATE> | t = <DATABASE> | t = <DATABASES> | t = <DICTIONARY> | t = <DICTIONARIES>
1069+
| t = <ALL> | t = <AND> | t = <APPLY> | t = <ARRAY> | t = <AS> | t = <ASOF> | t = <BEGIN> | t = <BETWEEN> | t = <CASE> | t = <CHANGED>
1070+
| t = <CLUSTER> | t = <COMMIT> | t = <COMPRESSION> | t = <DATE> | t = <DATABASE> | t = <DATABASES> | t = <DICTIONARY> | t = <DICTIONARIES>
10501071
| t = <DISTINCT> | t = <ELSE> | t = <END> | t = <EXCEPT> | t = <FORMAT> | t = <FROM> | t = <FINAL> | t = <FULL>
10511072
| t = <FUNCTION> | t = <GLOBAL> | t = <GROUP> | t = <HAVING> | t = <IF> | t = <ILIKE> | t = <IN> | t = <INFILE> | t = <INNER>
10521073
| t = <INPUT> | t = <INTERVAL> | t = <INTO> | t = <IS> | t = <LEVEL> | t = <JOIN> | t = <LEFT> | t = <LIKE> | t = <LIMIT> | t = <LIVE>
10531074
| t = <MATERIALIZED> | t = <NOT> | t = <OFFSET> | t = <ON> | t = <OR> | t = <ORDER> | t = <OUTFILE> | t = <POLICY>
1054-
| t = <PREWHERE> | t = <PROFILE> | t = <QUOTA> | t = <REPLACE> | t = <RIGHT> | t = <ROLE> | t = <ROW> | t = <SAMPLE>
1075+
| t = <PREWHERE> | t = <PROFILE> | t = <QUOTA> | t = <REPLACE> | t = <RIGHT> | t = <ROLE> | t = <ROLLBACK> | t = <ROW> | t = <SAMPLE>
10551076
| t = <SETTINGS> | t = <STDOUT> | t = <TEMPORARY> | t = <TABLE> | t = <TABLES> | t = <THEN> | t = <TIES> | t = <TIMESTAMP>
1056-
| t = <TOP> | t = <TOTALS> | t = <VALUES> | t = <VIEW> | t = <USER> | t = <UNION> | t = <USING>
1077+
| t = <TOP> | t = <TOTALS> | t = <TRANSACTION> | t = <VALUES> | t = <VIEW> | t = <USER> | t = <UNION> | t = <USING>
10571078
| t = <WHEN> | t = <WHERE> | t = <WITH> | t = <REGEXP>
10581079
// interval
10591080
| t = <SECOND> | t = <MINUTE> | t = <HOUR> | t = <DAY> | t = <WEEK> | t = <MONTH> | t = <QUARTER> | t = <YEAR>
@@ -1063,32 +1084,11 @@ Token anyKeyword(): { Token t; } {
10631084
{ return t; }
10641085
}
10651086

1066-
Token keyword(): { Token t; } {
1067-
(
1068-
// leading keywords(except with)
1069-
t = <ALTER> | t = <ATTACH> | t = <CHECK> | t = <CREATE> | t = <DELETE> | t = <DESC> | t = <DESCRIBE>
1070-
| t = <DETACH> | t = <DROP> | t = <EXCHANGE> | t = <EXISTS> | t = <EXPLAIN> | t = <GRANT> | t = <INSERT> | t = <KILL>
1071-
| t = <MOVE> | t = <OPTIMIZE> | t = <RENAME> | t = <REVOKE> | t = <SELECT> | t = <SET> | t = <SHOW> | t = <SYSTEM>
1072-
| t = <TRUNCATE> | t = <UPDATE> | t = <USE> | t = <WATCH> | t = <UNDROP>
1073-
// others
1074-
| t = <CASE> | t = <CLUSTER> | t = <DATE> | t = <DATABASE> | t = <DATABASES> | t = <DICTIONARY>
1075-
| t = <DICTIONARIES> | t = <DISTINCT> | t = <ELSE> | t = <END> | t = <EXCEPT>| t = <FUNCTION>
1076-
| t = <IF> | t = <INTERVAL> | t = <IS> | t = <INFILE> | t = <LIVE> | t = <MATERIALIZED> | t = <OUTFILE> | t = <POLICY>
1077-
| t = <PROFILE> | t = <QUOTA> | t = <REPLACE> | t = <ROLE> | t = <ROW> | t = <TEMPORARY>
1078-
| t = <TABLE> | t = <TABLES> | t = <THEN> | t = <TIES> | t = <TIMESTAMP> | t = <TOP> | t = <TOTALS>
1079-
| t = <VALUES> | t = <VIEW> | t = <WHEN> | t = <USER> | t = <REGEXP>
1080-
// interval
1081-
| t = <SECOND> | t = <MINUTE> | t = <HOUR> | t = <DAY> | t = <WEEK> | t = <MONTH> | t = <QUARTER> | t = <YEAR>
1082-
// values
1083-
| t = <INF> | t = <NAN> | t = <NULL>
1084-
)
1085-
{ return t; }
1086-
}
1087-
10881087
// keywords
10891088
TOKEN: {
10901089
<ALTER : <A> <L> <T> <E> <R>>
10911090
| <ATTACH : <A> <T> <T> <A> <C> <H> >
1091+
| <CHANGED : <C> <H> <A> <N> <G> <E> <D> >
10921092
| <CHECK : <C> <H> <E> <C> <K> >
10931093
| <CREATE : <C> <R> <E> <A> <T> <E> >
10941094
| <DELETE : <D> <E> <L> <E> <T> <E> >

0 commit comments

Comments
 (0)