Skip to content

Commit 505272d

Browse files
committed
Added javaCC parser implementation. not a default. some tests failing
1 parent 0cbf958 commit 505272d

File tree

5 files changed

+274
-48
lines changed

5 files changed

+274
-48
lines changed

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

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
import com.clickhouse.jdbc.internal.parser.antlr4.ClickHouseLexer;
66
import com.clickhouse.jdbc.internal.parser.antlr4.ClickHouseParser;
77
import com.clickhouse.jdbc.internal.parser.antlr4.ClickHouseParserBaseListener;
8+
import com.clickhouse.jdbc.internal.parser.javacc.ClickHouseSqlParser;
9+
import com.clickhouse.jdbc.internal.parser.javacc.ClickHouseSqlStatement;
10+
import com.clickhouse.jdbc.internal.parser.javacc.JdbcParseHandler;
11+
import com.clickhouse.jdbc.internal.parser.javacc.StatementType;
812
import org.antlr.v4.runtime.BaseErrorListener;
913
import org.antlr.v4.runtime.CharStream;
1014
import org.antlr.v4.runtime.CharStreams;
@@ -29,6 +33,74 @@ public abstract class SqlParserFacade {
2933

3034
public abstract ParsedPreparedStatement parsePreparedStatement(String sql);
3135

36+
private static class JavaCCParser extends SqlParserFacade {
37+
38+
@Override
39+
public ParsedStatement parsedStatement(String sql) {
40+
ParsedStatement stmt = new ParsedStatement();
41+
ClickHouseSqlStatement parsedStmt = parse(sql);
42+
if (parsedStmt.getStatementType() == StatementType.USE) {
43+
stmt.setUseDatabase(parsedStmt.getDatabase());
44+
}
45+
// TODO: set roles
46+
stmt.setInsert(parsedStmt.getStatementType() == StatementType.INSERT);
47+
stmt.setHasErrors(false);
48+
stmt.setHasResultSet(isStmtWithResultSet(parsedStmt));
49+
return stmt;
50+
}
51+
52+
private boolean isStmtWithResultSet(ClickHouseSqlStatement parsedStmt) {
53+
return parsedStmt.getStatementType() == StatementType.SELECT || parsedStmt.getStatementType() == StatementType.SHOW
54+
|| parsedStmt.getStatementType() == StatementType.EXPLAIN || parsedStmt.getStatementType() == StatementType.DESCRIBE
55+
|| parsedStmt.getStatementType() == StatementType.EXISTS || parsedStmt.getStatementType() == StatementType.CHECK;
56+
57+
}
58+
59+
@Override
60+
public ParsedPreparedStatement parsePreparedStatement(String sql) {
61+
ParsedPreparedStatement stmt = new ParsedPreparedStatement();
62+
ClickHouseSqlStatement parsedStmt = parse(sql);
63+
if (parsedStmt.getStatementType() == StatementType.USE) {
64+
stmt.setUseDatabase(parsedStmt.getDatabase());
65+
}
66+
stmt.setInsert(parsedStmt.getStatementType() == StatementType.INSERT);
67+
stmt.setHasErrors(false);
68+
stmt.setHasResultSet(isStmtWithResultSet(parsedStmt));
69+
stmt.setTable(parsedStmt.getTable());
70+
stmt.setInsertWithSelect(parsedStmt.containsKeyword("SELECT") && (parsedStmt.getStatementType() == StatementType.INSERT));
71+
72+
Integer startIndex = parsedStmt.getPositions().get(ClickHouseSqlStatement.KEYWORD_VALUES_START);
73+
if (startIndex != null) {
74+
stmt.setAssignValuesGroups(1);
75+
int endIndex = parsedStmt.getPositions().get(ClickHouseSqlStatement.KEYWORD_VALUES_END);
76+
stmt.setAssignValuesListStartPosition(startIndex);
77+
stmt.setAssignValuesListStopPosition(endIndex);
78+
String query = parsedStmt.getSQL();
79+
for (int i = startIndex + 1; i < endIndex; i++) {
80+
char ch = query.charAt(i);
81+
if (ch != '?' && ch != ',' && !Character.isWhitespace(ch)) {
82+
stmt.setUseFunction(true);
83+
break;
84+
}
85+
}
86+
}
87+
88+
stmt.setUseFunction(false);
89+
parseParameters(sql, stmt);
90+
return stmt;
91+
}
92+
93+
94+
public ClickHouseSqlStatement parse(String sql) {
95+
JdbcParseHandler handler = JdbcParseHandler.getInstance();
96+
ClickHouseSqlStatement[] stmts = ClickHouseSqlParser.parse(sql, handler);
97+
if (stmts.length > 1) {
98+
throw new RuntimeException("More than one SQL statement found: " + sql);
99+
}
100+
return stmts[0];
101+
}
102+
}
103+
32104
private static class ANTLR4Parser extends SqlParserFacade {
33105

34106
@Override
@@ -314,7 +386,7 @@ public static SqlParserFacade getParser(String name) throws SQLException {
314386
SQLParser parserSelection = SQLParser.valueOf(name);
315387
switch (parserSelection) {
316388
case JAVACC:
317-
return null;
389+
return new JavaCCParser();
318390
case ANTLR4_PARAMS_PARSER:
319391
return new ANTLR4AndParamsParser();
320392
case ANTLR4:
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package com.clickhouse.jdbc.internal.parser.javacc;
2+
3+
import com.clickhouse.client.config.ClickHouseDefaults;
4+
import com.clickhouse.data.ClickHouseChecker;
5+
import com.clickhouse.data.ClickHouseFormat;
6+
import com.clickhouse.data.ClickHouseUtils;
7+
8+
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.Set;
12+
13+
public class JdbcParseHandler extends ParseHandler {
14+
private static final String SETTING_MUTATIONS_SYNC = "mutations_sync";
15+
16+
private static final JdbcParseHandler INSTANCE;
17+
18+
static {
19+
INSTANCE = new JdbcParseHandler(true, true, true);
20+
};
21+
22+
public static JdbcParseHandler getInstance() {
23+
return INSTANCE;
24+
}
25+
26+
private final boolean allowLocalFile;
27+
private final boolean allowLightWeightDelete;
28+
private final boolean allowLightWeightUpdate;
29+
30+
private void addMutationSetting(String sql, StringBuilder builder, Map<String, Integer> positions,
31+
Map<String, String> settings, int index) {
32+
boolean hasSetting = settings != null && !settings.isEmpty();
33+
String setting = hasSetting ? settings.get(SETTING_MUTATIONS_SYNC) : null;
34+
if (setting == null) {
35+
String keyword = "SETTINGS";
36+
Integer settingsIndex = positions.get(keyword);
37+
38+
if (settingsIndex == null) {
39+
builder.append(sql.substring(index)).append(" SETTINGS mutations_sync=1");
40+
if (hasSetting) {
41+
builder.append(',');
42+
}
43+
} else {
44+
builder.append(sql.substring(index, settingsIndex)).append("SETTINGS mutations_sync=1,")
45+
.append(sql.substring(settingsIndex + keyword.length()));
46+
}
47+
} else {
48+
builder.append(sql.substring(index));
49+
}
50+
}
51+
52+
private ClickHouseSqlStatement handleDelete(String sql, StatementType stmtType, String cluster, String database,
53+
String table, String input, String compressAlgorithm, String compressLevel, String format, String file,
54+
List<Integer> parameters, Map<String, Integer> positions, Map<String, String> settings,
55+
Set<String> tempTables) {
56+
StringBuilder builder = new StringBuilder();
57+
int index = positions.get("DELETE");
58+
if (index > 0) {
59+
builder.append(sql.substring(0, index));
60+
}
61+
index = positions.get("FROM");
62+
Integer whereIdx = positions.get("WHERE");
63+
if (whereIdx != null) {
64+
builder.append("ALTER TABLE ");
65+
if (!ClickHouseChecker.isNullOrEmpty(database)) {
66+
builder.append('`').append(database).append('`').append('.');
67+
}
68+
builder.append('`').append(table).append('`').append(" DELETE ");
69+
addMutationSetting(sql, builder, positions, settings, whereIdx);
70+
} else {
71+
builder.append("TRUNCATE TABLE").append(sql.substring(index + 4));
72+
}
73+
return new ClickHouseSqlStatement(builder.toString(), stmtType, cluster, database, table, input,
74+
compressAlgorithm, compressLevel, format, file, parameters, null, settings, null);
75+
}
76+
77+
private ClickHouseSqlStatement handleUpdate(String sql, StatementType stmtType, String cluster, String database,
78+
String table, String input, String compressAlgorithm, String compressLevel, String format, String file,
79+
List<Integer> parameters, Map<String, Integer> positions, Map<String, String> settings,
80+
Set<String> tempTables) {
81+
StringBuilder builder = new StringBuilder();
82+
int index = positions.get("UPDATE");
83+
if (index > 0) {
84+
builder.append(sql.substring(0, index));
85+
}
86+
builder.append("ALTER TABLE ");
87+
index = positions.get("SET");
88+
if (!ClickHouseChecker.isNullOrEmpty(database)) {
89+
builder.append('`').append(database).append('`').append('.');
90+
}
91+
builder.append('`').append(table).append('`').append(" UPDATE"); // .append(sql.substring(index + 3));
92+
addMutationSetting(sql, builder, positions, settings, index + 3);
93+
return new ClickHouseSqlStatement(builder.toString(), stmtType, cluster, database, table, input,
94+
compressAlgorithm, compressLevel, format, file, parameters, null, settings, null);
95+
}
96+
97+
private ClickHouseSqlStatement handleInFileForInsertQuery(String sql, StatementType stmtType, String cluster,
98+
String database, String table, String input, String compressAlgorithm, String compressLevel, String format,
99+
String file, List<Integer> parameters, Map<String, Integer> positions, Map<String, String> settings,
100+
Set<String> tempTables) {
101+
StringBuilder builder = new StringBuilder(sql.length());
102+
builder.append(sql.substring(0, positions.get("FROM")));
103+
Integer index = positions.get("SETTINGS");
104+
if (index == null || index < 0) {
105+
index = positions.get("FORMAT");
106+
}
107+
if (index != null && index > 0) {
108+
builder.append(sql.substring(index));
109+
} else {
110+
ClickHouseFormat f = ClickHouseFormat.fromFileName(ClickHouseUtils.unescape(file));
111+
if (f == null) {
112+
f = (ClickHouseFormat) ClickHouseDefaults.FORMAT.getDefaultValue();
113+
}
114+
format = f.name();
115+
builder.append("FORMAT ").append(format);
116+
}
117+
return new ClickHouseSqlStatement(builder.toString(), stmtType, cluster, database, table, input,
118+
compressAlgorithm, compressLevel, format, file, parameters, null, settings, null);
119+
}
120+
121+
private ClickHouseSqlStatement handleOutFileForSelectQuery(String sql, StatementType stmtType, String cluster,
122+
String database, String table, String input, String compressAlgorithm, String compressLevel, String format,
123+
String file, List<Integer> parameters, Map<String, Integer> positions, Map<String, String> settings,
124+
Set<String> tempTables) {
125+
StringBuilder builder = new StringBuilder(sql.length());
126+
builder.append(sql.substring(0, positions.get("INTO")));
127+
Integer index = positions.get("FORMAT");
128+
if (index != null && index > 0) {
129+
builder.append(sql.substring(index));
130+
}
131+
return new ClickHouseSqlStatement(builder.toString(), stmtType, cluster, database, table, input,
132+
compressAlgorithm, compressLevel, format, file, parameters, null, settings, null);
133+
}
134+
135+
@Override
136+
public ClickHouseSqlStatement handleStatement(String sql, StatementType stmtType, String cluster, String database,
137+
String table, String input, String compressAlgorithm, String compressLevel, String format, String file,
138+
List<Integer> parameters, Map<String, Integer> positions, Map<String, String> settings,
139+
Set<String> tempTables) {
140+
boolean hasFile = allowLocalFile && !ClickHouseChecker.isNullOrEmpty(file) && file.charAt(0) == '\'';
141+
ClickHouseSqlStatement s = null;
142+
if (stmtType == StatementType.DELETE) {
143+
s = allowLightWeightDelete ? s
144+
: handleDelete(sql, stmtType, cluster, database, table, input, compressAlgorithm, compressLevel,
145+
format, file, parameters, positions, settings, tempTables);
146+
} else if (stmtType == StatementType.UPDATE) {
147+
s = allowLightWeightUpdate ? s
148+
: handleUpdate(sql, stmtType, cluster, database, table, input, compressAlgorithm, compressLevel,
149+
format, file, parameters, positions, settings, tempTables);
150+
} else if (stmtType == StatementType.INSERT && hasFile) {
151+
s = handleInFileForInsertQuery(sql, stmtType, cluster, database, table, input, compressAlgorithm,
152+
compressLevel, format, file, parameters, positions, settings, tempTables);
153+
} else if (stmtType == StatementType.SELECT && hasFile) {
154+
s = handleOutFileForSelectQuery(sql, stmtType, cluster, database, table, input, compressAlgorithm,
155+
compressLevel, format, file, parameters, positions, settings, tempTables);
156+
}
157+
return s;
158+
}
159+
160+
private JdbcParseHandler(boolean allowLightWeightDelete, boolean allowLightWeightUpdate, boolean allowLocalFile) {
161+
this.allowLightWeightDelete = allowLightWeightDelete;
162+
this.allowLightWeightUpdate = allowLightWeightUpdate;
163+
this.allowLocalFile = allowLocalFile;
164+
}
165+
}

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

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ public class ClickHouseSqlParser {
5151

5252
private final List<ClickHouseSqlStatement> statements = new ArrayList<>();
5353

54-
private ClickHouseConfig config;
5554
private ParseHandler handler;
5655
private int anyArgsListStart = -1;
5756

@@ -76,14 +75,7 @@ public class ClickHouseSqlParser {
7675
return !(getToken(1).kind == AND && token_source.parentToken == BETWEEN);
7776
}
7877

79-
public static ClickHouseSqlStatement[] parse(String sql, ClickHouseConfig config) {
80-
return parse(sql, config, null);
81-
}
82-
83-
public static ClickHouseSqlStatement[] parse(String sql, ClickHouseConfig config, ParseHandler handler) {
84-
if (config == null) {
85-
config = new ClickHouseConfig();
86-
}
78+
public static ClickHouseSqlStatement[] parse(String sql, ParseHandler handler) {
8779

8880
ClickHouseSqlStatement[] stmts = new ClickHouseSqlStatement[] {
8981
new ClickHouseSqlStatement(sql, StatementType.UNKNOWN) };
@@ -92,7 +84,7 @@ public class ClickHouseSqlParser {
9284
return stmts;
9385
}
9486

95-
ClickHouseSqlParser p = new ClickHouseSqlParser(sql, config, handler);
87+
ClickHouseSqlParser p = new ClickHouseSqlParser(sql, handler);
9688
try {
9789
stmts = p.sql();
9890
} catch (Exception e) {
@@ -106,10 +98,9 @@ public class ClickHouseSqlParser {
10698
return stmts;
10799
}
108100

109-
public ClickHouseSqlParser(String sql, ClickHouseConfig config, ParseHandler handler) {
101+
public ClickHouseSqlParser(String sql, ParseHandler handler) {
110102
this(new StringReader(sql));
111103

112-
this.config = config;
113104
this.handler = handler;
114105
}
115106

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

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -105,45 +105,32 @@ public void testPreparedStatementCreateSQL() {
105105
}
106106

107107

108-
@Test
109-
public void testPreparedStatementInsertSQL() {
108+
@Test(dataProvider = "testPreparedStatementInsertSQLDP")
109+
public void testPreparedStatementInsertSQL(String sql, int assignGroups, boolean insertWithSelect, int args) {
110110

111-
String sql = "INSERT INTO `test_stmt_split2` VALUES (1, 'abc'), (2, '?'), (3, '?')";
112111
ParsedPreparedStatement parsed = parser.parsePreparedStatement(sql);
113112
// TODO: extend test expecting no errors
114-
assertTrue(parsed.isInsert());
115-
assertFalse(parsed.isHasResultSet());
116-
assertFalse(parsed.isInsertWithSelect());
117-
assertEquals(parsed.getAssignValuesGroups(), 3);
118-
119-
sql = "-- line comment1 ?\n"
120-
+ "# line comment2 ?\n"
121-
+ "#! line comment3 ?\n"
122-
+ "/* block comment ? \n */"
123-
+ "INSERT INTO `with_complex_id`(`v?``1`, \"v?\"\"2\",`v?\\`3`, \"v?\\\"4\") VALUES (?, ?, ?, ?);";
124-
parsed = parser.parsePreparedStatement(sql);
125-
// TODO: extend test expecting no errors
126-
assertTrue(parsed.isInsert());
127-
assertFalse(parsed.isHasResultSet());
128-
assertFalse(parsed.isInsertWithSelect());
129-
assertEquals(parsed.getAssignValuesGroups(), 1);
130-
131-
sql = "INSERT INTO tt SELECT now(), 10, 20.0, 30";
132-
parsed = parser.parsePreparedStatement(sql);
133-
// TODO: extend test expecting no errors
134-
assertTrue(parsed.isInsert());
135-
assertFalse(parsed.isHasResultSet());
136-
assertTrue(parsed.isInsertWithSelect());
137-
113+
assertTrue(parsed.isInsert(), "Should be of insert type");
114+
assertFalse(parsed.isHasResultSet(), "Should not have result set");
115+
assertEquals(parsed.isInsertWithSelect(), insertWithSelect, "Insert with select attribute does not match");
116+
assertEquals(parsed.getAssignValuesGroups(), assignGroups, "Assign values groups do not match");
117+
assertEquals(parsed.getArgCount(), args, "Args do not match");
118+
}
138119

139-
sql = "INSERT INTO `users` (`name`, `last_login`, `password`, `id`) VALUES\n" +
140-
" (?, `parseDateTimeBestEffort`(?, ?), ?, 1)\n";
141-
parsed = parser.parsePreparedStatement(sql);
142-
// TODO: extend test expecting no errors
143-
assertTrue(parsed.isInsert());
144-
assertFalse(parsed.isHasResultSet());
145-
assertFalse(parsed.isInsertWithSelect());
146-
assertEquals(parsed.getAssignValuesGroups(), 1);
120+
@DataProvider
121+
public static Object[][] testPreparedStatementInsertSQLDP() {
122+
return new Object[][] {
123+
{"-- line comment1 ?\n"
124+
+ "# line comment2 ?\n"
125+
+ "#! line comment3 ?\n"
126+
+ "/* block comment ? \n */"
127+
+ "INSERT INTO `with_complex_id`(`v?``1`, \"v?\"\"2\",`v?\\`3`, \"v?\\\"4\") VALUES (?, ?, ?, ?);", 1, false, 4},
128+
{ "INSERT INTO `test_stmt_split2` VALUES (1, 'abc'), (2, '?'), (3, '?')", 3, false, 0 },
129+
{ "INSERT INTO `with_complex_id`(`v?``1`, \"v?\"\"2\",`v?\\`3`, \"v?\\\"4\") VALUES (?, ?, ?, ?);", 1, false, 4},
130+
{ "INSERT INTO tt SELECT now(), 10, 20.0, 30", -1, true, 0 },
131+
{ "INSERT INTO `users` (`name`, `last_login`, `password`, `id`) VALUES\n" +
132+
" (?, `parseDateTimeBestEffort`(?, ?), ?, 1)\n", 1, false, 4 },
133+
};
147134
}
148135

149136
@Test
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.clickhouse.jdbc.internal;
2+
3+
4+
import org.testng.annotations.Ignore;
5+
6+
@Ignore
7+
public class JavaCCParserTest extends BaseSqlParserFacadeTest {
8+
public JavaCCParserTest() throws Exception {
9+
super(SqlParserFacade.SQLParser.JAVACC.name());
10+
}
11+
}

0 commit comments

Comments
 (0)