Skip to content

Commit 8b81fe8

Browse files
committed
Added detector of simple INSERT/UPSERT
1 parent 92cc3ce commit 8b81fe8

File tree

3 files changed

+354
-23
lines changed

3 files changed

+354
-23
lines changed

jdbc/src/main/java/tech/ydb/jdbc/query/YdbQueryParser.java

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class YdbQueryParser {
2020
private final boolean isDetectJdbcParameters;
2121

2222
private final List<QueryStatement> statements = new ArrayList<>();
23+
private final YqlBatcher batcher = new YqlBatcher();
2324

2425
public YdbQueryParser(boolean isDetectQueryType, boolean isDetectJdbcParameters) {
2526
this.isDetectQueryType = isDetectQueryType;
@@ -30,6 +31,10 @@ public List<QueryStatement> getStatements() {
3031
return this.statements;
3132
}
3233

34+
public YqlBatcher getYqlBatcher() {
35+
return this.batcher;
36+
}
37+
3338
public QueryType detectQueryType() throws SQLException {
3439
QueryType type = null;
3540
for (QueryStatement st: statements) {
@@ -53,6 +58,7 @@ public QueryType detectQueryType() throws SQLException {
5358
@SuppressWarnings("MethodLength")
5459
public String parseSQL(String origin) throws SQLException {
5560
this.statements.clear();
61+
this.batcher.clear();
5662

5763
int fragmentStart = 0;
5864

@@ -73,20 +79,22 @@ public String parseSQL(String origin) throws SQLException {
7379
char ch = chars[i];
7480
boolean isInsideKeyword = false;
7581
switch (ch) {
76-
case '(':
77-
parenLevel++;
78-
break;
79-
80-
case ')':
81-
parenLevel--;
82-
break;
83-
8482
case '\'': // single-quotes
85-
i = parseSingleQuotes(chars, i);
83+
int singleQuitesEnd = parseSingleQuotes(chars, i);
84+
batcher.readSingleQuoteLiteral(chars, i, singleQuitesEnd - i + 1);
85+
i = singleQuitesEnd;
8686
break;
8787

8888
case '"': // double-quotes
89-
i = parseDoubleQuotes(chars, i);
89+
int doubleQuitesEnd = parseDoubleQuotes(chars, i);
90+
batcher.readDoubleQuoteLiteral(chars, i, doubleQuitesEnd - i + 1);
91+
i = doubleQuitesEnd;
92+
break;
93+
94+
case '`': // backtick-quotes
95+
int backstickQuitesEnd = parseBacktickQuotes(chars, i);
96+
batcher.readIdentifier(chars, i, backstickQuitesEnd - i + 1);
97+
i = backstickQuitesEnd;
9098
break;
9199

92100
case '-': // possibly -- style comment
@@ -101,6 +109,7 @@ public String parseSQL(String origin) throws SQLException {
101109
parsed.append(chars, fragmentStart, i - fragmentStart);
102110
if (i + 1 < chars.length && chars[i + 1] == '?') /* replace ?? with ? */ {
103111
parsed.append('?');
112+
batcher.readIdentifier(chars, i, 1);
104113
i++; // make sure the coming ? is not treated as a bind
105114
} else {
106115
String binded = argNameGenerator.createArgName(origin);
@@ -110,6 +119,8 @@ public String parseSQL(String origin) throws SQLException {
110119
: null;
111120
currStatement.addParameter(binded, type);
112121
parsed.append(binded);
122+
123+
batcher.readParameter();
113124
}
114125
fragmentStart = i + 1;
115126
}
@@ -129,8 +140,11 @@ public String parseSQL(String origin) throws SQLException {
129140

130141
if (keywordStart >= 0 && (!isInsideKeyword || (i == chars.length - 1))) {
131142
lastKeywordIsOffsetLimit = false;
143+
int keywordLength = isInsideKeyword ? i - keywordStart - 1 : i - keywordStart;
132144

133145
if (currStatement != null) {
146+
batcher.readIdentifier(chars, keywordStart, keywordLength);
147+
134148
// Detect RETURNING keyword
135149
if (parenLevel == 0 && parseReturningKeyword(chars, keywordStart)) {
136150
currStatement.setHasReturning(true);
@@ -147,11 +161,17 @@ public String parseSQL(String origin) throws SQLException {
147161
if (parseSelectKeyword(chars, keywordStart)) {
148162
currStatement = new QueryStatement(QueryType.DATA_QUERY, QueryCmd.SELECT);
149163
}
164+
150165
// starts with INSERT, UPSERT
151-
if (parseInsertKeyword(chars, keywordStart)
152-
|| parseUpsertKeyword(chars, keywordStart)) {
166+
if (parseInsertKeyword(chars, keywordStart)) {
153167
currStatement = new QueryStatement(QueryType.DATA_QUERY, QueryCmd.INSERT_UPSERT);
168+
batcher.readInsert();
154169
}
170+
if (parseUpsertKeyword(chars, keywordStart)) {
171+
currStatement = new QueryStatement(QueryType.DATA_QUERY, QueryCmd.INSERT_UPSERT);
172+
batcher.readUpsert();
173+
}
174+
155175
// starts with UPDATE, REPLACE, DELETE
156176
if (parseUpdateKeyword(chars, keywordStart)
157177
|| parseDeleteKeyword(chars, keywordStart)
@@ -165,6 +185,7 @@ public String parseSQL(String origin) throws SQLException {
165185
|| parseDropKeyword(chars, keywordStart)) {
166186
currStatement = new QueryStatement(QueryType.SCHEME_QUERY, QueryCmd.CREATE_ALTER_DROP);
167187
}
188+
168189
if (isDetectQueryType) {
169190
// Detect scan expression - starts with SCAN
170191
if (parseScanKeyword(chars, keywordStart)) {
@@ -186,21 +207,33 @@ public String parseSQL(String origin) throws SQLException {
186207
detectJdbcArgs = currStatement.getType() != QueryType.SCHEME_QUERY
187208
&& currStatement.getType() != QueryType.UNKNOWN
188209
&& isDetectJdbcParameters;
189-
190-
if (parseAlterKeyword(chars, i)
191-
|| parseCreateKeyword(chars, i)
192-
|| parseDropKeyword(chars, i)) {
193-
currStatement = new QueryStatement(QueryType.SCHEME_QUERY, QueryCmd.CREATE_ALTER_DROP);
194-
statements.add(currStatement);
195-
}
196210
}
197211

198212
keywordStart = -1;
199213
}
200214

201-
if (ch == ';' && parenLevel == 0) {
202-
currStatement = null;
203-
detectJdbcArgs = false;
215+
switch (ch) {
216+
case '(':
217+
parenLevel++;
218+
batcher.readOpenParen();
219+
break;
220+
case ')':
221+
parenLevel--;
222+
batcher.readCloseParen();
223+
break;
224+
case ',':
225+
batcher.readComma();
226+
break;
227+
case ';':
228+
batcher.readSemiColon();
229+
if (parenLevel == 0) {
230+
currStatement = null;
231+
detectJdbcArgs = false;
232+
}
233+
break;
234+
default:
235+
// nothing
236+
break;
204237
}
205238
}
206239

@@ -250,6 +283,14 @@ private static int parseDoubleQuotes(final char[] query, int offset) {
250283
return offset;
251284
}
252285

286+
@SuppressWarnings("EmptyBlock")
287+
private static int parseBacktickQuotes(final char[] query, int offset) {
288+
while (++offset < query.length && query[offset] != '`') {
289+
// do nothing
290+
}
291+
return offset;
292+
}
293+
253294
private static int parseLineComment(final char[] query, int offset) {
254295
if (offset + 1 < query.length && query[offset + 1] == '-') {
255296
while (offset + 1 < query.length) {
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package tech.ydb.jdbc.query;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
/**
7+
*
8+
* @author Aleksandr Gorshenin
9+
*/
10+
public class YqlBatcher {
11+
private enum Cmd {
12+
UPSERT,
13+
INSERT
14+
}
15+
private enum State {
16+
INIT,
17+
CMD,
18+
INTO,
19+
TABLE_NAME,
20+
COLUMNS_OPEN_PAREN_OR_COMMA,
21+
COLUMN_NAME,
22+
COLUMNS_CLOSE_PAREN,
23+
VALUES,
24+
VALUES_OPEN_PAREN_OR_COMMA,
25+
COLUMN_VALUE,
26+
VALUES_CLOSE_PAREN,
27+
28+
ERROR
29+
}
30+
31+
private State state = State.INIT;
32+
private Cmd cmd = null;
33+
private String tableName = null;
34+
private final List<String> columns = new ArrayList<>();
35+
private final List<String> values = new ArrayList<>();
36+
37+
public boolean isInsert() {
38+
return cmd == Cmd.INSERT;
39+
}
40+
41+
public boolean isUpsert() {
42+
return cmd == Cmd.UPSERT;
43+
}
44+
45+
public String getTableName() {
46+
return tableName;
47+
}
48+
49+
public List<String> getColumns() {
50+
return columns;
51+
}
52+
53+
public List<String> getValues() {
54+
return values;
55+
}
56+
57+
public boolean isValidBatch() {
58+
return state == State.VALUES_CLOSE_PAREN && cmd != null
59+
&& tableName != null && !tableName.isEmpty()
60+
&& !columns.isEmpty() && columns.size() == values.size();
61+
}
62+
63+
public void clear() {
64+
this.state = State.INIT;
65+
this.cmd = null;
66+
this.tableName = null;
67+
this.columns.clear();
68+
this.values.clear();
69+
}
70+
71+
public void readInsert() {
72+
if (state == State.INIT) {
73+
state = State.CMD;
74+
cmd = Cmd.INSERT;
75+
return;
76+
}
77+
state = State.ERROR;
78+
}
79+
80+
public void readUpsert() {
81+
if (state == State.INIT) {
82+
state = State.CMD;
83+
cmd = Cmd.UPSERT;
84+
return;
85+
}
86+
state = State.ERROR;
87+
}
88+
89+
public void readOpenParen() {
90+
if (state == State.TABLE_NAME) {
91+
state = State.COLUMNS_OPEN_PAREN_OR_COMMA;
92+
return;
93+
}
94+
if (state == State.VALUES) {
95+
state = State.VALUES_OPEN_PAREN_OR_COMMA;
96+
return;
97+
}
98+
state = State.ERROR;
99+
}
100+
101+
public void readCloseParen() {
102+
if (state == State.COLUMN_NAME) {
103+
state = State.COLUMNS_CLOSE_PAREN;
104+
return;
105+
}
106+
if (state == State.COLUMN_VALUE) {
107+
state = State.VALUES_CLOSE_PAREN;
108+
return;
109+
}
110+
state = State.ERROR;
111+
}
112+
113+
public void readComma() {
114+
if (state == State.COLUMN_NAME) {
115+
state = State.COLUMNS_OPEN_PAREN_OR_COMMA;
116+
return;
117+
}
118+
if (state == State.COLUMN_VALUE) {
119+
state = State.VALUES_OPEN_PAREN_OR_COMMA;
120+
return;
121+
}
122+
state = State.ERROR;
123+
}
124+
125+
public void readSemiColon() {
126+
if (state == State.INIT || state == State.VALUES_CLOSE_PAREN) {
127+
return;
128+
}
129+
state = State.ERROR;
130+
}
131+
132+
public void readParameter() {
133+
if (state == State.VALUES_OPEN_PAREN_OR_COMMA) {
134+
values.add("?");
135+
state = State.COLUMN_VALUE;
136+
return;
137+
}
138+
state = State.ERROR;
139+
}
140+
141+
public void readSingleQuoteLiteral(char[] query, int start, int length) {
142+
// NOT SUPPORTED YET
143+
state = State.ERROR;
144+
}
145+
146+
public void readDoubleQuoteLiteral(char[] query, int start, int length) {
147+
// NOT SUPPORTED YET
148+
state = State.ERROR;
149+
}
150+
151+
public void readIdentifier(char[] query, int start, int length) {
152+
if (state == State.CMD) {
153+
if (length == 4
154+
&& (query[start] | 32) == 'i'
155+
&& (query[start + 1] | 32) == 'n'
156+
&& (query[start + 2] | 32) == 't'
157+
&& (query[start + 3] | 32) == 'o') {
158+
state = State.INTO;
159+
return;
160+
}
161+
}
162+
if (state == State.COLUMNS_CLOSE_PAREN) {
163+
if (length == 6
164+
&& (query[start] | 32) == 'v'
165+
&& (query[start + 1] | 32) == 'a'
166+
&& (query[start + 2] | 32) == 'l'
167+
&& (query[start + 3] | 32) == 'u'
168+
&& (query[start + 4] | 32) == 'e'
169+
&& (query[start + 5] | 32) == 's') {
170+
state = State.VALUES;
171+
return;
172+
}
173+
}
174+
175+
if (state == State.INTO) {
176+
tableName = unquote(query, start, length);
177+
state = State.TABLE_NAME;
178+
return;
179+
}
180+
181+
if (state == State.COLUMNS_OPEN_PAREN_OR_COMMA) {
182+
columns.add(unquote(query, start, length));
183+
state = State.COLUMN_NAME;
184+
return;
185+
}
186+
187+
state = State.ERROR;
188+
}
189+
190+
private String unquote(char[] chars, int start, int length) {
191+
if (chars[start] == '`' && chars[start + length - 1] == '`') {
192+
return String.valueOf(chars, start + 1, length - 2);
193+
}
194+
return String.valueOf(chars, start, length);
195+
}
196+
}

0 commit comments

Comments
 (0)