Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ ATTACH : A T T A C H;
BETWEEN : B E T W E E N;
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;
CASE : C A S E;
CAST : C A S T;
CHECK : C H E C K;
CLEAR : C L E A R;
CLUSTER : C L U S T E R;
CN : C N;
CODEC : C O D E C;
COLLATE : C O L L A T E;
COLUMN : C O L U M N;
Expand Down Expand Up @@ -59,6 +62,8 @@ 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;
DOUBLE_SHA1_HASH : D O U B L E '_' S H A '1' '_' H A S H;
DROP : D R O P;
ELSE : E L S E;
END : E N D;
Expand All @@ -82,11 +87,15 @@ FULL : F U L L;
FUNCTION : F U N C T I O N;
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;
GROUP : G R O U P;
HAVING : H A V I N G;
HIERARCHICAL : H I E R A R C H I C A L;
HTTP : H T T P;
HOST : H O S T;
HOUR : H O U R;
ID : I D;
IDENTIFIED : I D E N T I F I E D;
IF : I F;
ILIKE : I L I K E;
IN : I N;
Expand All @@ -97,13 +106,16 @@ 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;
IP : I P;
IS : I S;
IS_OBJECT_ID : I S UNDERSCORE O B J E C T UNDERSCORE I D;
JOIN : J O I N;
KEY : K E Y;
KERBEROS : K E R B E R O S;
KILL : K I L L;
LAST : L A S T;
LAYOUT : L A Y O U T;
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;
Expand All @@ -123,7 +135,9 @@ MONTH : M O N T H;
MOVE : M O V E;
MUTATION : M U T A T I O N;
NAN_SQL : N A N; // conflicts with macro NAN
NAME : N A M 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
Expand All @@ -142,8 +156,11 @@ 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;
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;
QUARTER : Q U A R T E R;
RANGE : R A N G E;
REALM : R E A L M;
REGEXP : R E G E X P;
RELOAD : R E L O A D;
REMOVE : R E M O V E;
RENAME : R E N A M E;
Expand All @@ -156,13 +173,21 @@ ROLLUP : R O L L U P;
ROW : R O W;
ROWS : R O W S;
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;
SECOND : S E C O N D;
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;
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;
SOURCE : S O U R C E;
START : S T A R T;
STOP : S T O P;
Expand Down Expand Up @@ -190,6 +215,7 @@ UNBOUNDED : U N B O U N D E D;
UNION : U N I O N;
UPDATE : U P D A T E;
USE : U S E;
USER : U S E R;
USING : U S I N G;
UUID : U U I D;
VALUES : V A L U E S;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,50 @@ createStmt
engineClause? subqueryClause? # CreateTableStmt
| (ATTACH | CREATE) (OR REPLACE)? VIEW (IF NOT EXISTS)? tableIdentifier uuidClause? clusterClause? tableSchemaClause? subqueryClause #
CreateViewStmt
| CREATE USER ((IF NOT EXISTS) | (OR REPLACE))? userIdentifier (COMMA userIdentifier)* clusterClause?
userIdentifiedClause?
userCreateHostClause?
validUntilClause?
(DEFAULT ROLE identifier (COMMA identifier)*)?
(DEFAULT DATABASE identifier | NONE)?

settingsClause? #CreateUserStmt
Comment on lines +145 to +152
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests are required for the new CREATE USER statement grammar. Please add unit and/or integration tests to ensure the parser correctly handles valid and invalid CREATE USER statements.

;

userIdentifier
: (IDENTIFIER | STRING_LITERAL)
;

userIdentifiedClause
: IDENTIFIED BY literal
| IDENTIFIED WITH userIdentifiedWithClause validUntilClause? (COMMA userIdentifiedWithClause VALID UNTIL literal)*
| NOT IDENTIFIED
;

userIdentifiedWithClause
: (PLAINTEXT_PASSWORD | SHA256_PASSWORD | SHA256_HASH | DOUBLE_SHA1_PASSWORD | DOUBLE_SHA1_HASH | SCRAM_SHA256_PASSWORD | SCRAM_SHA256_HASH | BCRYPT_PASSWORD | BCRYPT_HASH) BY literal
| NO_PASSWORD
| LDAP SERVER literal
| KERBEROS (REALM literal)?
| SSL_CERTIFICATE CN literal
| SSH_KEY BY KEY literal TYPE literal (COMMA KEY literal TYPE literal)*
| HTTP SERVER literal (SCHEMA literal)?
;

userCreateHostClause
: HOST (userCreateHostDef (COMMA userCreateHostDef)*) | ANY | NONE
;

userCreateHostDef
: LOCAL | NAME literal | REGEXP literal | IP literal | LIKE literal
;

userCreateGranteesClause
: GRANTEES (identifier | STRING_LITERAL | ANY | NONE ) (COMMA (identifier | STRING_LITERAL | ANY | NONE ))*
(EXCEPT (identifier | STRING_LITERAL) (COMMA (identifier | STRING_LITERAL ))*)
;
validUntilClause
: VALID UNTIL interval
;

dictionarySchemaClause
Expand Down Expand Up @@ -951,4 +995,4 @@ identifierOrNull

enumValue
: STRING_LITERAL EQ_SINGLE numberLiteral
;
;
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ protected ResultSetImpl executeQueryImpl(String sql, QuerySettings settings) thr

try {
lastStatementSql = parseJdbcEscapeSyntax(sql);
LOG.debug("SQL Query: {}", lastStatementSql);
LOG.trace("SQL Query: {}", lastStatementSql); // this is not secure for create statements because of passwords
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging full SQL queries, even at TRACE level, may expose sensitive information such as passwords in CREATE USER statements. Consider redacting sensitive data from the SQL before logging, or ensure users are aware of this risk in documentation or configuration.

QueryResponse response;
if (queryTimeout == 0) {
response = connection.client.query(lastStatementSql, mergedSettings).get();
Expand Down Expand Up @@ -179,7 +179,7 @@ protected int executeUpdateImpl(String sql, QuerySettings settings) throws SQLEx
}

lastStatementSql = parseJdbcEscapeSyntax(sql);
LOG.debug("SQL Query: {}", lastStatementSql);
LOG.trace("SQL Query: {}", lastStatementSql);
int updateCount = 0;
try (QueryResponse response = queryTimeout == 0 ? connection.client.query(lastStatementSql, mergedSettings).get()
: connection.client.query(lastStatementSql, mergedSettings).get(queryTimeout, TimeUnit.SECONDS)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,6 @@ public void enterSetRoleStmt(ClickHouseParser.SetRoleStmtContext ctx) {
setRoles(roles);
}
}


}
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
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) {

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();
ParsedStatement parserListener = new ParsedStatement();
IterativeParseTreeWalker.DEFAULT.walk(parserListener, parseTree);


return parserListener;
}

Expand Down Expand Up @@ -54,4 +62,11 @@ public static String escapeQuotes(String str) {
.replace("'", "\\'")
.replace("\"", "\\\"");
}

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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,10 @@ void testMultipleWithClauses() throws Exception {

@Test(groups = { "integration" })
void testRecursiveWithClause() throws Exception {
if (ClickHouseVersion.of(getServerVersion()).check("(,24.3]")) {
return; // recursive CTEs were introduced in 24.4
}

try (Connection conn = getJdbcConnection();
PreparedStatement stmt = conn.prepareStatement(
"WITH RECURSIVE numbers AS (" +
Expand Down
29 changes: 25 additions & 4 deletions jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ public void testExecuteQueryTimeout() throws Exception {


@Test(groups = { "integration" })
private void testSettingRole() throws SQLException {
public void testSettingRole() throws SQLException {
if (earlierThan(24, 4)) {//Min version is 24.4
return;
}
Expand Down Expand Up @@ -551,8 +551,8 @@ public void testSwitchDatabase() throws Exception {
}
}
}


@Test(groups = { "integration" })
public void testNewLineSQLParsing() throws Exception {
try (Connection conn = getJdbcConnection()) {
Expand Down Expand Up @@ -617,7 +617,7 @@ public void testNewLineSQLParsing() throws Exception {
}
}


@Test(groups = { "integration" })
public void testNullableFixedStringType() throws Exception {
try (Connection conn = getJdbcConnection()) {
Expand Down Expand Up @@ -708,4 +708,25 @@ public void testExecuteWithMaxRows() throws Exception {
}
}

@Test(groups = {"integration"})
public void testDDLStatements() throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can expand testing with a few more examples (to see that the parser works correctly)
Here we can find the https://clickhouse.com/docs/sql-reference/statements/create/user
like adding

  • "CREATE USER IF NOT EXISTS 'user011' IDENTIFIED WITH no_password"
  • "IDENTIFIED WITH plaintext_password BY 'qwerty'"
    and a few more options

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, will do this.

if (isCloud()) {
return; // skip because we do not want to create extra on cloud instance
}
try (Connection conn = getJdbcConnection()) {
try (Statement stmt = conn.createStatement()){
Assert.assertFalse(stmt.execute("CREATE USER IF NOT EXISTS 'user011' IDENTIFIED BY 'password'"));

try (ResultSet rs = stmt.executeQuery("SHOW USERS")) {
boolean found = false;
while (rs.next()) {
if (rs.getString("name").equals("user011")) {
found = true;
}
}
Assert.assertTrue(found);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import static org.testng.Assert.assertEquals;
Expand Down Expand Up @@ -210,4 +211,34 @@ public void testStmtWithFunction() {
ParsedPreparedStatement stmt = parser.parsePreparedStatement(sql);
Assert.assertEquals(stmt.getArgCount(), 4);
}

@Test(dataProvider = "testCreateStmtDP")
public void testCreateStatement(String sql) {
SqlParser parser = new SqlParser();
ParsedPreparedStatement stmt = parser.parsePreparedStatement(sql);
Assert.assertFalse(stmt.isHasErrors());
}

@DataProvider
public static Object[][] testCreateStmtDP() {
return new Object[][] {
{"CREATE USER 'user01' IDENTIFIED WITH no_password"},
{"CREATE USER 'user01' IDENTIFIED WITH plaintext_password BY 'qwerty'"},
{"CREATE USER 'user01' IDENTIFIED WITH sha256_password BY 'qwerty' or IDENTIFIED BY 'password'"},
{"CREATE USER 'user01' IDENTIFIED WITH sha256_hash BY 'hash' SALT 'salt'"},
{"CREATE USER 'user01' IDENTIFIED WITH sha256_hash BY 'hash'"},
{"CREATE USER 'user01' IDENTIFIED WITH double_sha1_password BY 'qwerty'"},
{"CREATE USER 'user01' IDENTIFIED WITH double_sha1_hash BY 'hash'"},
{"CREATE USER 'user01' IDENTIFIED WITH bcrypt_password BY 'qwerty'"},
{"CREATE USER 'user01' IDENTIFIED WITH bcrypt_hash BY 'hash'"},
{"CREATE USER 'user01' IDENTIFIED WITH ldap SERVER 'server_name'"},
{"CREATE USER 'user01' IDENTIFIED WITH kerberos"},
{"CREATE USER 'user01' IDENTIFIED WITH kerberos REALM 'realm'"},
{"CREATE USER 'user01' IDENTIFIED WITH ssl_certificate CN 'mysite.com:user'"},
{"CREATE USER 'user01' IDENTIFIED WITH ssh_key BY KEY 'public_key' TYPE 'ssh-rsa', KEY 'another_public_key' TYPE 'ssh-ed25519'"},
{"CREATE USER 'user01' IDENTIFIED WITH http SERVER 'http_server' SCHEME 'basic'"},
{"CREATE USER 'user01' IDENTIFIED WITH http SERVER 'http_server'"},
{"CREATE USER 'user01' IDENTIFIED BY 'qwerty'"},
};
}
}
Loading