Skip to content

Commit 0f626bb

Browse files
authored
Added support of convertion IN (?, ?, ?) to IN $list (#87)
2 parents 0e44158 + 1cc496a commit 0f626bb

File tree

13 files changed

+798
-200
lines changed

13 files changed

+798
-200
lines changed

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package tech.ydb.jdbc.query;
22

3+
34
import java.util.ArrayList;
45
import java.util.List;
56

6-
import tech.ydb.jdbc.common.TypeDescription;
7+
import tech.ydb.jdbc.query.params.JdbcPrm;
8+
79

810
/**
911
*
@@ -12,7 +14,7 @@
1214
public class QueryStatement {
1315
private final QueryType queryType;
1416
private final QueryCmd command;
15-
private final List<ParamDescription> parameters = new ArrayList<>();
17+
private final List<JdbcPrm.Factory> parameters = new ArrayList<>();
1618
private boolean hasReturinng = false;
1719

1820
public QueryStatement(QueryType custom, QueryType baseType, QueryCmd command) {
@@ -28,12 +30,16 @@ public QueryCmd getCmd() {
2830
return command;
2931
}
3032

31-
public List<ParamDescription> getParams() {
33+
public boolean hasJdbcParameters() {
34+
return !parameters.isEmpty();
35+
}
36+
37+
public List<JdbcPrm.Factory> getJdbcPrmFactories() {
3238
return parameters;
3339
}
3440

35-
public void addParameter(String name, TypeDescription type) {
36-
this.parameters.add(new ParamDescription(name, type));
41+
public void addJdbcPrmFactory(JdbcPrm.Factory prm) {
42+
this.parameters.add(prm);
3743
}
3844

3945
public void setHasReturning(boolean hasReturning) {

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ public class YdbQuery {
2727
this.type = type;
2828
this.batcher = batcher;
2929

30-
boolean hasJdbcParamters = false;
30+
boolean hasJdbcParameters = false;
3131
for (QueryStatement st: statements) {
32-
hasJdbcParamters = hasJdbcParamters || !st.getParams().isEmpty();
32+
hasJdbcParameters = hasJdbcParameters || st.hasJdbcParameters();
3333
}
34-
this.isPlainYQL = !hasJdbcParamters;
34+
this.isPlainYQL = !hasJdbcParameters;
3535
}
3636

3737
public QueryType getType() {
@@ -59,8 +59,10 @@ public List<QueryStatement> getStatements() {
5959
}
6060

6161
public static YdbQuery parseQuery(String query, YdbQueryProperties opts) throws SQLException {
62-
YdbQueryParser parser = new YdbQueryParser(opts.isDetectQueryType(), opts.isDetectJdbcParameters());
63-
String preparedYQL = parser.parseSQL(query);
62+
YdbQueryParser parser = new YdbQueryParser(
63+
query, opts.isDetectQueryType(), opts.isDetectJdbcParameters(), opts.isReplaceJdbcInByYqlList()
64+
);
65+
String preparedYQL = parser.parseSQL();
6466

6567
QueryType type = null;
6668
YqlBatcher batcher = parser.getYqlBatcher();

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

Lines changed: 136 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
import java.util.List;
88

99
import tech.ydb.jdbc.YdbConst;
10-
import tech.ydb.jdbc.common.TypeDescription;
11-
import tech.ydb.table.values.PrimitiveType;
10+
import tech.ydb.jdbc.query.params.JdbcPrm;
1211

1312

1413
/**
@@ -18,13 +17,21 @@
1817
public class YdbQueryParser {
1918
private final boolean isDetectQueryType;
2019
private final boolean isDetectJdbcParameters;
20+
private final boolean isConvertJdbcInToList;
2121

2222
private final List<QueryStatement> statements = new ArrayList<>();
2323
private final YqlBatcher batcher = new YqlBatcher();
24+
private final String origin;
25+
private final StringBuilder parsed;
2426

25-
public YdbQueryParser(boolean isDetectQueryType, boolean isDetectJdbcParameters) {
27+
private int jdbcPrmIndex = 0;
28+
29+
public YdbQueryParser(String origin, boolean isDetectQueryType, boolean isDetectParameters, boolean isConvertIn) {
2630
this.isDetectQueryType = isDetectQueryType;
27-
this.isDetectJdbcParameters = isDetectJdbcParameters;
31+
this.isDetectJdbcParameters = isDetectParameters;
32+
this.isConvertJdbcInToList = isConvertIn;
33+
this.origin = origin;
34+
this.parsed = new StringBuilder(origin.length() + 10);
2835
}
2936

3037
public List<QueryStatement> getStatements() {
@@ -56,7 +63,7 @@ public QueryType detectQueryType() throws SQLException {
5663
}
5764

5865
@SuppressWarnings("MethodLength")
59-
public String parseSQL(String origin) throws SQLException {
66+
public String parseSQL() throws SQLException {
6067
int fragmentStart = 0;
6168
boolean detectJdbcArgs = false;
6269

@@ -65,16 +72,13 @@ public String parseSQL(String origin) throws SQLException {
6572

6673
int parenLevel = 0;
6774
int keywordStart = -1;
68-
boolean lastKeywordIsOffsetLimit = false;
6975

7076
char[] chars = origin.toCharArray();
7177

72-
StringBuilder parsed = new StringBuilder(origin.length() + 10);
73-
ArgNameGenerator argNameGenerator = new ArgNameGenerator();
74-
7578
for (int i = 0; i < chars.length; ++i) {
7679
char ch = chars[i];
7780
boolean isInsideKeyword = false;
81+
7882
int keywordEnd = i; // parseSingleQuotes, parseDoubleQuotes, etc move index so we keep old value
7983
switch (ch) {
8084
case '\'': // single-quotes
@@ -102,6 +106,7 @@ public String parseSQL(String origin) throws SQLException {
102106
case '/': // possibly /* */ style comment
103107
i = parseBlockComment(chars, i);
104108
break;
109+
105110
case '?':
106111
if (detectJdbcArgs && statement != null) {
107112
parsed.append(chars, fragmentStart, i - fragmentStart);
@@ -110,22 +115,15 @@ public String parseSQL(String origin) throws SQLException {
110115
batcher.readIdentifier(chars, i, 1);
111116
i++; // make sure the coming ? is not treated as a bind
112117
} else {
113-
String binded = argNameGenerator.createArgName(origin);
114-
// force type UInt64 for OFFSET and LIMIT parameters
115-
TypeDescription forcedType = lastKeywordIsOffsetLimit
116-
? TypeDescription.of(PrimitiveType.Uint64)
117-
: null;
118-
statement.addParameter(binded, forcedType);
119-
parsed.append(binded);
120-
118+
String name = nextJdbcPrmName();
119+
statement.addJdbcPrmFactory(JdbcPrm.simplePrm(name));
120+
parsed.append(name);
121121
batcher.readParameter();
122122
}
123123
fragmentStart = i + 1;
124124
}
125125
break;
126126
default:
127-
lastKeywordIsOffsetLimit = lastKeywordIsOffsetLimit && Character.isWhitespace(ch);
128-
129127
if (keywordStart >= 0) {
130128
isInsideKeyword = Character.isJavaIdentifierPart(ch);
131129
break;
@@ -140,7 +138,6 @@ public String parseSQL(String origin) throws SQLException {
140138

141139

142140
if (keywordStart >= 0 && (!isInsideKeyword || (i == chars.length - 1))) {
143-
lastKeywordIsOffsetLimit = false;
144141
int keywordLength = (isInsideKeyword ? i + 1 : keywordEnd) - keywordStart;
145142

146143
if (statement != null) {
@@ -151,9 +148,23 @@ public String parseSQL(String origin) throws SQLException {
151148
statement.setHasReturning(true);
152149
}
153150

154-
if (parseOffsetKeyword(chars, keywordStart, keywordLength)
155-
|| parseLimitKeyword(chars, keywordStart, keywordLength)) {
156-
lastKeywordIsOffsetLimit = Character.isWhitespace(ch);
151+
// Process ? after OFFSET and LIMIT
152+
if (i < chars.length && detectJdbcArgs && Character.isWhitespace(ch)) {
153+
if (parseOffsetKeyword(chars, keywordStart, keywordLength)
154+
|| parseLimitKeyword(chars, keywordStart, keywordLength)) {
155+
parsed.append(chars, fragmentStart, i - fragmentStart);
156+
i = parseOffsetLimitParameter(chars, i, statement);
157+
fragmentStart = i;
158+
}
159+
}
160+
161+
// Process IN (?, ?, ... )
162+
if (i < chars.length && detectJdbcArgs && isConvertJdbcInToList) {
163+
if (parseInKeyword(chars, keywordStart, keywordLength)) {
164+
parsed.append(chars, fragmentStart, i - fragmentStart);
165+
i = parseInListParameters(chars, i, statement);
166+
fragmentStart = i;
167+
}
157168
}
158169
} else {
159170
boolean skipped = false;
@@ -272,18 +283,102 @@ public String parseSQL(String origin) throws SQLException {
272283
return parsed.toString();
273284
}
274285

275-
private static class ArgNameGenerator {
276-
private int index = 0;
286+
private String nextJdbcPrmName() {
287+
while (true) {
288+
jdbcPrmIndex += 1;
289+
String name = YdbConst.AUTO_GENERATED_PARAMETER_PREFIX + jdbcPrmIndex;
290+
if (!origin.contains(name)) {
291+
return name;
292+
}
293+
}
294+
}
277295

278-
public String createArgName(String origin) {
279-
while (true) {
280-
index += 1;
281-
String name = YdbConst.AUTO_GENERATED_PARAMETER_PREFIX + index;
282-
if (!origin.contains(name)) {
283-
return name;
284-
}
296+
private int parseOffsetLimitParameter(char[] query, int offset, QueryStatement st) {
297+
int start = offset;
298+
while (++offset < query.length) {
299+
char ch = query[offset];
300+
switch (ch) {
301+
case '?' :
302+
if (offset + 1 < query.length && query[offset + 1] == '?') {
303+
return start;
304+
}
305+
String name = nextJdbcPrmName();
306+
parsed.append(query, start, offset - start);
307+
parsed.append(name);
308+
st.addJdbcPrmFactory(JdbcPrm.uint64Prm(name));
309+
return offset + 1;
310+
case '-': // possibly -- style comment
311+
offset = parseLineComment(query, offset);
312+
break;
313+
case '/': // possibly /* */ style comment
314+
offset = parseBlockComment(query, offset);
315+
break;
316+
default:
317+
if (!Character.isWhitespace(query[offset])) {
318+
return start;
319+
}
320+
break;
321+
}
322+
}
323+
324+
return start;
325+
}
326+
327+
private int parseInListParameters(char[] query, int offset, QueryStatement st) {
328+
int start = offset;
329+
int listStartedAt = -1;
330+
int listSize = 0;
331+
boolean waitPrm = false;
332+
while (offset < query.length) {
333+
char ch = query[offset];
334+
switch (ch) {
335+
case '(': // start of list
336+
if (listStartedAt >= 0) {
337+
return start;
338+
}
339+
listStartedAt = offset;
340+
waitPrm = true;
341+
break;
342+
case ',':
343+
if (listStartedAt < 0 || waitPrm) {
344+
return start;
345+
}
346+
waitPrm = true;
347+
break;
348+
case '?' :
349+
if (!waitPrm || (offset + 1 < query.length && query[offset + 1] == '?')) {
350+
return start;
351+
}
352+
listSize++;
353+
waitPrm = false;
354+
break;
355+
case ')':
356+
if (waitPrm || listSize == 0 || listStartedAt < 0) {
357+
return start;
358+
}
359+
360+
String name = nextJdbcPrmName();
361+
parsed.append(query, start, listStartedAt - start);
362+
parsed.append(' '); // add extra space to avoid IN$jpN
363+
parsed.append(name);
364+
st.addJdbcPrmFactory(JdbcPrm.inListOrm(name, listSize));
365+
return offset + 1;
366+
case '-': // possibly -- style comment
367+
offset = parseLineComment(query, offset);
368+
break;
369+
case '/': // possibly /* */ style comment
370+
offset = parseBlockComment(query, offset);
371+
break;
372+
default:
373+
if (!Character.isWhitespace(query[offset])) {
374+
return start;
375+
}
376+
break;
285377
}
378+
offset++;
286379
}
380+
381+
return start;
287382
}
288383

289384
private static int parseSingleQuotes(final char[] query, int offset) {
@@ -553,4 +648,13 @@ private static boolean parseLimitKeyword(char[] query, int offset, int length) {
553648
&& (query[offset + 3] | 32) == 'i'
554649
&& (query[offset + 4] | 32) == 't';
555650
}
651+
652+
private static boolean parseInKeyword(char[] query, int offset, int length) {
653+
if (length != 2) {
654+
return false;
655+
}
656+
657+
return (query[offset] | 32) == 'i'
658+
&& (query[offset + 1] | 32) == 'n';
659+
}
556660
}

0 commit comments

Comments
 (0)