Skip to content

Commit fbc2248

Browse files
authored
Merge pull request #2421 from ClickHouse/jdbc_fix_parser_issue
[jdbc] Adds `CREATE USER` statement to parser grammar
2 parents 5860516 + 5ca334b commit fbc2248

File tree

8 files changed

+151
-8
lines changed

8 files changed

+151
-8
lines changed

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ ATTACH : A T T A C H;
2727
BETWEEN : B E T W E E N;
2828
BOTH : B O T H;
2929
BY : B Y;
30+
BCRYPT_PASSWORD : B C R Y P T '_' P A S S W O R D;
31+
BCRYPT_HASH : B C R Y P T '_' H A S H;
3032
CASE : C A S E;
3133
CAST : C A S T;
3234
CHECK : C H E C K;
3335
CLEAR : C L E A R;
3436
CLUSTER : C L U S T E R;
37+
CN : C N;
3538
CODEC : C O D E C;
3639
COLLATE : C O L L A T E;
3740
COLUMN : C O L U M N;
@@ -59,6 +62,8 @@ DICTIONARY : D I C T I O N A R Y;
5962
DISK : D I S K;
6063
DISTINCT : D I S T I N C T;
6164
DISTRIBUTED : D I S T R I B U T E D;
65+
DOUBLE_SHA1_PASSWORD : D O U B L E '_' S H A '1' '_' P A S S W O R D;
66+
DOUBLE_SHA1_HASH : D O U B L E '_' S H A '1' '_' H A S H;
6267
DROP : D R O P;
6368
ELSE : E L S E;
6469
END : E N D;
@@ -82,11 +87,15 @@ FULL : F U L L;
8287
FUNCTION : F U N C T I O N;
8388
GLOBAL : G L O B A L;
8489
GRANULARITY : G R A N U L A R I T Y;
90+
GRANTEES : G R A N T E E S;
8591
GROUP : G R O U P;
8692
HAVING : H A V I N G;
8793
HIERARCHICAL : H I E R A R C H I C A L;
94+
HTTP : H T T P;
95+
HOST : H O S T;
8896
HOUR : H O U R;
8997
ID : I D;
98+
IDENTIFIED : I D E N T I F I E D;
9099
IF : I F;
91100
ILIKE : I L I K E;
92101
IN : I N;
@@ -97,13 +106,16 @@ INNER : I N N E R;
97106
INSERT : I N S E R T;
98107
INTERVAL : I N T E R V A L;
99108
INTO : I N T O;
109+
IP : I P;
100110
IS : I S;
101111
IS_OBJECT_ID : I S UNDERSCORE O B J E C T UNDERSCORE I D;
102112
JOIN : J O I N;
103113
KEY : K E Y;
114+
KERBEROS : K E R B E R O S;
104115
KILL : K I L L;
105116
LAST : L A S T;
106117
LAYOUT : L A Y O U T;
118+
LDAP : L D A P;
107119
LEADING : L E A D I N G;
108120
LEFT : L E F T;
109121
LIFETIME : L I F E T I M E;
@@ -123,7 +135,9 @@ MONTH : M O N T H;
123135
MOVE : M O V E;
124136
MUTATION : M U T A T I O N;
125137
NAN_SQL : N A N; // conflicts with macro NAN
138+
NAME : N A M E;
126139
NO : N O;
140+
NO_PASSWORD : N O '_' P A S S W O R D;
127141
NONE : N O N E;
128142
NOT : N O T;
129143
NULL_SQL : N U L L; // conflicts with macro NULL
@@ -142,8 +156,11 @@ PRECEDING : P R E C E D I N G;
142156
PREWHERE : P R E W H E R E;
143157
PRIMARY : P R I M A R Y;
144158
PROJECTION : P R O J E C T I O N;
159+
PLAINTEXT_PASSWORD : P L A I N T E X T '_' P A S S W O R D;
145160
QUARTER : Q U A R T E R;
146161
RANGE : R A N G E;
162+
REALM : R E A L M;
163+
REGEXP : R E G E X P;
147164
RELOAD : R E L O A D;
148165
REMOVE : R E M O V E;
149166
RENAME : R E N A M E;
@@ -156,13 +173,21 @@ ROLLUP : R O L L U P;
156173
ROW : R O W;
157174
ROWS : R O W S;
158175
SAMPLE : S A M P L E;
176+
SCHEMA : S C H E M A;
177+
SCRAM_SHA256_PASSWORD : S C R A M '_' S H A '2' '5' '6' '_' P A S S W O R D;
178+
SCRAM_SHA256_HASH : S C R A M '_' S H A '2' '5' '6' '_' H A S H;
159179
SECOND : S E C O N D;
160180
SELECT : S E L E C T;
161181
SEMI : S E M I;
162182
SENDS : S E N D S;
183+
SERVER : S E R V E R;
184+
SSL_CERTIFICATE : S S L '_' C E R T I F I C A T E;
185+
SSH_KEY : S S H '_' K E Y;
163186
SET : S E T;
164187
SETTINGS : S E T T I N G S;
165188
SHOW : S H O W;
189+
SHA256_PASSWORD : S H A '2' '5' '6' '_' P A S S W O R D;
190+
SHA256_HASH : S H A '2' '5' '6' '_' H A S H;
166191
SOURCE : S O U R C E;
167192
START : S T A R T;
168193
STOP : S T O P;
@@ -190,6 +215,7 @@ UNBOUNDED : U N B O U N D E D;
190215
UNION : U N I O N;
191216
UPDATE : U P D A T E;
192217
USE : U S E;
218+
USER : U S E R;
193219
USING : U S I N G;
194220
UUID : U U I D;
195221
VALUES : V A L U E S;

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

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,50 @@ createStmt
142142
engineClause? subqueryClause? # CreateTableStmt
143143
| (ATTACH | CREATE) (OR REPLACE)? VIEW (IF NOT EXISTS)? tableIdentifier uuidClause? clusterClause? tableSchemaClause? subqueryClause #
144144
CreateViewStmt
145+
| CREATE USER ((IF NOT EXISTS) | (OR REPLACE))? userIdentifier (COMMA userIdentifier)* clusterClause?
146+
userIdentifiedClause?
147+
userCreateHostClause?
148+
validUntilClause?
149+
(DEFAULT ROLE identifier (COMMA identifier)*)?
150+
(DEFAULT DATABASE identifier | NONE)?
151+
152+
settingsClause? #CreateUserStmt
153+
;
154+
155+
userIdentifier
156+
: (IDENTIFIER | STRING_LITERAL)
157+
;
158+
159+
userIdentifiedClause
160+
: IDENTIFIED BY literal
161+
| IDENTIFIED WITH userIdentifiedWithClause validUntilClause? (COMMA userIdentifiedWithClause VALID UNTIL literal)*
162+
| NOT IDENTIFIED
163+
;
164+
165+
userIdentifiedWithClause
166+
: (PLAINTEXT_PASSWORD | SHA256_PASSWORD | SHA256_HASH | DOUBLE_SHA1_PASSWORD | DOUBLE_SHA1_HASH | SCRAM_SHA256_PASSWORD | SCRAM_SHA256_HASH | BCRYPT_PASSWORD | BCRYPT_HASH) BY literal
167+
| NO_PASSWORD
168+
| LDAP SERVER literal
169+
| KERBEROS (REALM literal)?
170+
| SSL_CERTIFICATE CN literal
171+
| SSH_KEY BY KEY literal TYPE literal (COMMA KEY literal TYPE literal)*
172+
| HTTP SERVER literal (SCHEMA literal)?
173+
;
174+
175+
userCreateHostClause
176+
: HOST (userCreateHostDef (COMMA userCreateHostDef)*) | ANY | NONE
177+
;
178+
179+
userCreateHostDef
180+
: LOCAL | NAME literal | REGEXP literal | IP literal | LIKE literal
181+
;
182+
183+
userCreateGranteesClause
184+
: GRANTEES (identifier | STRING_LITERAL | ANY | NONE ) (COMMA (identifier | STRING_LITERAL | ANY | NONE ))*
185+
(EXCEPT (identifier | STRING_LITERAL) (COMMA (identifier | STRING_LITERAL ))*)
186+
;
187+
validUntilClause
188+
: VALID UNTIL interval
145189
;
146190

147191
dictionarySchemaClause
@@ -951,4 +995,4 @@ identifierOrNull
951995

952996
enumValue
953997
: STRING_LITERAL EQ_SINGLE numberLiteral
954-
;
998+
;

jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ protected ResultSetImpl executeQueryImpl(String sql, QuerySettings settings) thr
129129

130130
try {
131131
lastStatementSql = parseJdbcEscapeSyntax(sql);
132-
LOG.debug("SQL Query: {}", lastStatementSql);
132+
LOG.trace("SQL Query: {}", lastStatementSql); // this is not secure for create statements because of passwords
133133
QueryResponse response;
134134
if (queryTimeout == 0) {
135135
response = connection.client.query(lastStatementSql, mergedSettings).get();
@@ -179,7 +179,7 @@ protected int executeUpdateImpl(String sql, QuerySettings settings) throws SQLEx
179179
}
180180

181181
lastStatementSql = parseJdbcEscapeSyntax(sql);
182-
LOG.debug("SQL Query: {}", lastStatementSql);
182+
LOG.trace("SQL Query: {}", lastStatementSql);
183183
int updateCount = 0;
184184
try (QueryResponse response = queryTimeout == 0 ? connection.client.query(lastStatementSql, mergedSettings).get()
185185
: connection.client.query(lastStatementSql, mergedSettings).get(queryTimeout, TimeUnit.SECONDS)) {

jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ParsedStatement.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,6 @@ public void enterSetRoleStmt(ClickHouseParser.SetRoleStmtContext ctx) {
103103
setRoles(roles);
104104
}
105105
}
106+
107+
106108
}

jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/SqlParser.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,33 @@
11
package com.clickhouse.jdbc.internal;
22

3+
import org.antlr.v4.runtime.BaseErrorListener;
34
import org.antlr.v4.runtime.CharStream;
45
import org.antlr.v4.runtime.CharStreams;
56
import org.antlr.v4.runtime.CommonTokenStream;
7+
import org.antlr.v4.runtime.RecognitionException;
8+
import org.antlr.v4.runtime.Recognizer;
69
import org.antlr.v4.runtime.tree.IterativeParseTreeWalker;
10+
import org.slf4j.Logger;
11+
import org.slf4j.LoggerFactory;
712

813
import java.util.regex.Matcher;
914
import java.util.regex.Pattern;
1015

1116
public class SqlParser {
1217

18+
private static final Logger LOG = LoggerFactory.getLogger(SqlParser.class);
19+
1320
public ParsedStatement parsedStatement(String sql) {
1421

1522
CharStream charStream = CharStreams.fromString(sql);
1623
ClickHouseLexer lexer = new ClickHouseLexer(charStream);
1724
ClickHouseParser parser = new ClickHouseParser(new CommonTokenStream(lexer));
25+
parser.removeErrorListeners();
26+
parser.addErrorListener(new ParserErrorListener());
1827
ClickHouseParser.QueryStmtContext parseTree = parser.queryStmt();
1928
ParsedStatement parserListener = new ParsedStatement();
2029
IterativeParseTreeWalker.DEFAULT.walk(parserListener, parseTree);
2130

22-
2331
return parserListener;
2432
}
2533

@@ -54,4 +62,11 @@ public static String escapeQuotes(String str) {
5462
.replace("'", "\\'")
5563
.replace("\"", "\\\"");
5664
}
65+
66+
private static class ParserErrorListener extends BaseErrorListener {
67+
@Override
68+
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
69+
LOG.warn("SQL syntax error at line: " + line + ", pos: " + charPositionInLine + ", " + msg);
70+
}
71+
}
5772
}

jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,10 @@ void testMultipleWithClauses() throws Exception {
362362

363363
@Test(groups = { "integration" })
364364
void testRecursiveWithClause() throws Exception {
365+
if (ClickHouseVersion.of(getServerVersion()).check("(,24.3]")) {
366+
return; // recursive CTEs were introduced in 24.4
367+
}
368+
365369
try (Connection conn = getJdbcConnection();
366370
PreparedStatement stmt = conn.prepareStatement(
367371
"WITH RECURSIVE numbers AS (" +

jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ public void testExecuteQueryTimeout() throws Exception {
342342

343343

344344
@Test(groups = { "integration" })
345-
private void testSettingRole() throws SQLException {
345+
public void testSettingRole() throws SQLException {
346346
if (earlierThan(24, 4)) {//Min version is 24.4
347347
return;
348348
}
@@ -551,8 +551,8 @@ public void testSwitchDatabase() throws Exception {
551551
}
552552
}
553553
}
554-
555-
554+
555+
556556
@Test(groups = { "integration" })
557557
public void testNewLineSQLParsing() throws Exception {
558558
try (Connection conn = getJdbcConnection()) {
@@ -617,7 +617,7 @@ public void testNewLineSQLParsing() throws Exception {
617617
}
618618
}
619619

620-
620+
621621
@Test(groups = { "integration" })
622622
public void testNullableFixedStringType() throws Exception {
623623
try (Connection conn = getJdbcConnection()) {
@@ -708,4 +708,25 @@ public void testExecuteWithMaxRows() throws Exception {
708708
}
709709
}
710710

711+
@Test(groups = {"integration"})
712+
public void testDDLStatements() throws Exception {
713+
if (isCloud()) {
714+
return; // skip because we do not want to create extra on cloud instance
715+
}
716+
try (Connection conn = getJdbcConnection()) {
717+
try (Statement stmt = conn.createStatement()){
718+
Assert.assertFalse(stmt.execute("CREATE USER IF NOT EXISTS 'user011' IDENTIFIED BY 'password'"));
719+
720+
try (ResultSet rs = stmt.executeQuery("SHOW USERS")) {
721+
boolean found = false;
722+
while (rs.next()) {
723+
if (rs.getString("name").equals("user011")) {
724+
found = true;
725+
}
726+
}
727+
Assert.assertTrue(found);
728+
}
729+
}
730+
}
731+
}
711732
}

jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/SqlParserTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33

44
import org.testng.Assert;
5+
import org.testng.annotations.DataProvider;
56
import org.testng.annotations.Test;
67

78
import static org.testng.Assert.assertEquals;
@@ -210,4 +211,34 @@ public void testStmtWithFunction() {
210211
ParsedPreparedStatement stmt = parser.parsePreparedStatement(sql);
211212
Assert.assertEquals(stmt.getArgCount(), 4);
212213
}
214+
215+
@Test(dataProvider = "testCreateStmtDP")
216+
public void testCreateStatement(String sql) {
217+
SqlParser parser = new SqlParser();
218+
ParsedPreparedStatement stmt = parser.parsePreparedStatement(sql);
219+
Assert.assertFalse(stmt.isHasErrors());
220+
}
221+
222+
@DataProvider
223+
public static Object[][] testCreateStmtDP() {
224+
return new Object[][] {
225+
{"CREATE USER 'user01' IDENTIFIED WITH no_password"},
226+
{"CREATE USER 'user01' IDENTIFIED WITH plaintext_password BY 'qwerty'"},
227+
{"CREATE USER 'user01' IDENTIFIED WITH sha256_password BY 'qwerty' or IDENTIFIED BY 'password'"},
228+
{"CREATE USER 'user01' IDENTIFIED WITH sha256_hash BY 'hash' SALT 'salt'"},
229+
{"CREATE USER 'user01' IDENTIFIED WITH sha256_hash BY 'hash'"},
230+
{"CREATE USER 'user01' IDENTIFIED WITH double_sha1_password BY 'qwerty'"},
231+
{"CREATE USER 'user01' IDENTIFIED WITH double_sha1_hash BY 'hash'"},
232+
{"CREATE USER 'user01' IDENTIFIED WITH bcrypt_password BY 'qwerty'"},
233+
{"CREATE USER 'user01' IDENTIFIED WITH bcrypt_hash BY 'hash'"},
234+
{"CREATE USER 'user01' IDENTIFIED WITH ldap SERVER 'server_name'"},
235+
{"CREATE USER 'user01' IDENTIFIED WITH kerberos"},
236+
{"CREATE USER 'user01' IDENTIFIED WITH kerberos REALM 'realm'"},
237+
{"CREATE USER 'user01' IDENTIFIED WITH ssl_certificate CN 'mysite.com:user'"},
238+
{"CREATE USER 'user01' IDENTIFIED WITH ssh_key BY KEY 'public_key' TYPE 'ssh-rsa', KEY 'another_public_key' TYPE 'ssh-ed25519'"},
239+
{"CREATE USER 'user01' IDENTIFIED WITH http SERVER 'http_server' SCHEME 'basic'"},
240+
{"CREATE USER 'user01' IDENTIFIED WITH http SERVER 'http_server'"},
241+
{"CREATE USER 'user01' IDENTIFIED BY 'qwerty'"},
242+
};
243+
}
213244
}

0 commit comments

Comments
 (0)