Skip to content

Commit 555e9d9

Browse files
authored
Updated parser of IN clause, added support of tuples (#145)
2 parents 9379e23 + 534eacc commit 555e9d9

File tree

7 files changed

+342
-88
lines changed

7 files changed

+342
-88
lines changed

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

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -383,42 +383,46 @@ private int parseOffsetLimitParameter(char[] query, int offset, QueryStatement s
383383
private int parseInListParameters(char[] query, int offset, QueryStatement st) {
384384
int start = offset;
385385
int listStartedAt = -1;
386-
int listSize = 0;
387-
boolean waitPrm = false;
386+
YqlListParser parser = new YqlListParser();
387+
388388
while (offset < query.length) {
389389
char ch = query[offset];
390390
switch (ch) {
391-
case '(': // start of list
392-
if (listStartedAt >= 0) {
391+
case '(':
392+
if (parser.isNotStarted()) {
393+
listStartedAt = offset;
394+
}
395+
if (!parser.readOpenParen()) {
393396
return start;
394397
}
395-
listStartedAt = offset;
396-
waitPrm = true;
397398
break;
398399
case ',':
399-
if (listStartedAt < 0 || waitPrm) {
400+
if (!parser.readComma()) {
400401
return start;
401402
}
402-
waitPrm = true;
403403
break;
404404
case '?' :
405-
if (!waitPrm || (offset + 1 < query.length && query[offset + 1] == '?')) {
405+
if (offset + 1 < query.length && query[offset + 1] == '?') {
406+
return start;
407+
}
408+
409+
if (!parser.readParameter()) {
406410
return start;
407411
}
408-
listSize++;
409-
waitPrm = false;
410412
break;
411413
case ')':
412-
if (waitPrm || listSize == 0 || listStartedAt < 0) {
414+
if (!parser.readCloseParen()) {
413415
return start;
414416
}
415-
416-
String name = nextJdbcPrmName();
417-
parsed.append(query, start, listStartedAt - start);
418-
parsed.append(' '); // add extra space to avoid IN$jpN
419-
parsed.append(name);
420-
st.addJdbcPrmFactory(JdbcPrm.inListOrm(types, name, listSize));
421-
return offset + 1;
417+
if (parser.isCompleted()) {
418+
String name = nextJdbcPrmName();
419+
parsed.append(query, start, listStartedAt - start);
420+
parsed.append(' '); // add extra space to avoid IN$jpN
421+
parsed.append(name);
422+
st.addJdbcPrmFactory(JdbcPrm.inListOrm(types, name, parser.listSize(), parser.tupleSize()));
423+
return offset + 1;
424+
}
425+
break;
422426
case '-': // possibly -- style comment
423427
offset = parseLineComment(query, offset);
424428
break;
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package tech.ydb.jdbc.query;
2+
3+
/**
4+
*
5+
* @author Aleksandr Gorshenin
6+
*/
7+
public class YqlListParser {
8+
9+
private enum State {
10+
BEFORE_LIST,
11+
BEFORE_PARAM_OR_TUPLE,
12+
13+
BEFORE_PARAM,
14+
BEFORE_TUPLE,
15+
BEFORE_TUPLE_PARAM,
16+
17+
AFTER_TUPLE,
18+
AFTER_PARAM,
19+
AFTER_TUPLE_PARAM,
20+
21+
AFTER_LIST,
22+
23+
ERROR
24+
}
25+
26+
private State state = State.BEFORE_LIST;
27+
28+
private int listSize = 0;
29+
private int tupleSize = -1;
30+
31+
private int currentTupleSize = 0;
32+
33+
public boolean isNotStarted() {
34+
return state == State.BEFORE_LIST;
35+
}
36+
37+
public boolean isCompleted() {
38+
return state == State.AFTER_LIST;
39+
}
40+
41+
public int listSize() {
42+
return listSize;
43+
}
44+
45+
public int tupleSize() {
46+
return tupleSize > 0 ? tupleSize : 1;
47+
}
48+
49+
public boolean readOpenParen() {
50+
if (state == State.BEFORE_LIST) {
51+
state = State.BEFORE_PARAM_OR_TUPLE;
52+
return true;
53+
}
54+
55+
if (state == State.BEFORE_PARAM_OR_TUPLE || state == State.BEFORE_TUPLE) {
56+
state = State.BEFORE_TUPLE_PARAM;
57+
return true;
58+
}
59+
60+
state = State.ERROR;
61+
return false;
62+
}
63+
64+
public boolean readCloseParen() {
65+
if (state == State.AFTER_TUPLE_PARAM) {
66+
if (tupleSize >= 0 && currentTupleSize != tupleSize) { // all tuples must have the same count of parameters
67+
state = State.ERROR;
68+
return false;
69+
}
70+
71+
tupleSize = currentTupleSize;
72+
currentTupleSize = 0;
73+
listSize++;
74+
state = State.AFTER_TUPLE;
75+
return true;
76+
}
77+
78+
if (state == State.AFTER_PARAM || state == State.AFTER_TUPLE) {
79+
state = State.AFTER_LIST;
80+
return true;
81+
}
82+
83+
return false;
84+
}
85+
86+
public boolean readComma() {
87+
if (state == State.AFTER_PARAM) {
88+
state = State.BEFORE_PARAM;
89+
return true;
90+
}
91+
if (state == State.AFTER_TUPLE_PARAM) {
92+
state = State.BEFORE_TUPLE_PARAM;
93+
return true;
94+
}
95+
if (state == State.AFTER_TUPLE) {
96+
state = State.BEFORE_TUPLE;
97+
return true;
98+
}
99+
100+
state = State.ERROR;
101+
return false;
102+
}
103+
104+
public boolean readParameter() {
105+
if (state == State.BEFORE_PARAM_OR_TUPLE || state == State.BEFORE_PARAM) {
106+
listSize++;
107+
state = State.AFTER_PARAM;
108+
return true;
109+
}
110+
if (state == State.BEFORE_TUPLE_PARAM) {
111+
currentTupleSize++;
112+
state = State.AFTER_TUPLE_PARAM;
113+
return true;
114+
}
115+
116+
return false;
117+
}
118+
}

jdbc/src/main/java/tech/ydb/jdbc/query/params/AsTableJdbcPrm.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public List<? extends JdbcPrm> toJdbcPrmList() {
4242

4343
private Value<?> buildList() throws SQLException {
4444
if (type == null) {
45-
throw new SQLException(YdbConst.PARAMETER_TYPE_UNKNOWN);
45+
throw new SQLException(YdbConst.MISSING_VALUE_FOR_PARAMETER + items.get(0).name);
4646
}
4747

4848
boolean hasNull = false;

jdbc/src/main/java/tech/ydb/jdbc/query/params/InListJdbcPrm.java

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

33
import java.sql.SQLException;
44
import java.util.ArrayList;
5+
import java.util.Arrays;
56
import java.util.List;
67

78
import tech.ydb.jdbc.YdbConst;
@@ -10,6 +11,7 @@
1011
import tech.ydb.table.query.Params;
1112
import tech.ydb.table.values.ListType;
1213
import tech.ydb.table.values.OptionalType;
14+
import tech.ydb.table.values.TupleType;
1315
import tech.ydb.table.values.Type;
1416
import tech.ydb.table.values.Value;
1517
import tech.ydb.table.values.VoidValue;
@@ -20,16 +22,24 @@
2022
*/
2123
public class InListJdbcPrm {
2224
private static final Value<?> NULL = VoidValue.of();
23-
private final YdbTypes types;
25+
private final YdbTypes ydbTypes;
2426
private final String listName;
2527
private final List<Item> items = new ArrayList<>();
26-
private TypeDescription type;
28+
private final Item[][] tuples;
29+
private final TypeDescription[] tupleTypes;
2730

28-
public InListJdbcPrm(YdbTypes types, String listName, int listSize) {
29-
this.types = types;
31+
public InListJdbcPrm(YdbTypes types, String listName, int listSize, int tupleSize) {
32+
this.ydbTypes = types;
3033
this.listName = listName;
31-
for (int idx = 0; idx < listSize; idx++) {
32-
items.add(new Item(listName, idx));
34+
this.tupleTypes = new TypeDescription[tupleSize];
35+
this.tuples = new Item[listSize][];
36+
for (int idx = 0; idx < listSize; idx += 1) {
37+
Item[] tuple = new Item[tupleSize];
38+
for (int memberIdx = 0; memberIdx < tupleSize; memberIdx += 1) {
39+
tuple[memberIdx] = new Item(listName, idx * tupleSize + memberIdx, memberIdx);
40+
items.add(tuple[memberIdx]);
41+
}
42+
tuples[idx] = tuple;
3343
}
3444
}
3545

@@ -38,47 +48,90 @@ public List<? extends JdbcPrm> toJdbcPrmList() {
3848
}
3949

4050
private Value<?> buildList() throws SQLException {
41-
if (type == null) {
42-
throw new SQLException(YdbConst.PARAMETER_TYPE_UNKNOWN);
51+
TypeBuilder[] types = new TypeBuilder[tupleTypes.length];
52+
for (int idx = 0; idx < tupleTypes.length; idx += 1) {
53+
if (tupleTypes[idx] == null) {
54+
throw new SQLException(YdbConst.MISSING_VALUE_FOR_PARAMETER + tuples[0][idx].name);
55+
}
56+
types[idx] = new TypeBuilder(tupleTypes[idx].ydbType());
4357
}
4458

45-
boolean hasNull = false;
4659
for (Item item: items) {
4760
if (item.value == null) {
4861
throw new SQLException(YdbConst.MISSING_VALUE_FOR_PARAMETER + item.name);
4962
}
50-
hasNull = hasNull || item.value == NULL;
63+
types[item.memberId].validateOptional(item.value);
5164
}
5265

53-
List<Value<?>> values = new ArrayList<>();
54-
if (!hasNull) {
66+
if (types.length == 1) { // Simple list
67+
TypeBuilder type = types[0];
68+
List<Value<?>> values = new ArrayList<>();
5569
for (Item item: items) {
56-
values.add(item.value);
70+
values.add(type.makeValue(item.value));
5771
}
58-
return ListType.of(type.ydbType()).newValue(values);
72+
return ListType.of(type.makeType()).newValue(values);
5973
}
6074

61-
OptionalType optional = type.ydbType().makeOptional();
62-
for (Item item: items) {
63-
if (item.value == NULL) {
64-
values.add(optional.emptyValue());
65-
} else {
66-
values.add(item.value.makeOptional());
75+
Type[] tupleMemberTypes = new Type[tupleTypes.length];
76+
for (int idx = 0; idx < tupleTypes.length; idx += 1) {
77+
tupleMemberTypes[idx] = types[idx].makeType();
78+
}
79+
80+
TupleType tupleType = TupleType.ofOwn(tupleMemberTypes);
81+
List<Value<?>> values = new ArrayList<>();
82+
for (Item[] tupleItems : tuples) {
83+
Value<?>[] tupleValues = new Value<?>[tupleItems.length];
84+
for (int idx = 0; idx < tupleItems.length; idx += 1) {
85+
tupleValues[idx] = types[idx].makeValue(tupleItems[idx].value);
6786
}
87+
values.add(tupleType.newValueOwn(tupleValues));
6888
}
6989

70-
return ListType.of(optional).newValue(values);
90+
return ListType.of(tupleType).newValue(values);
91+
}
92+
93+
private class TypeBuilder {
94+
private final Type type;
95+
private final OptionalType optional;
7196

97+
private boolean isOptional = false;
98+
99+
TypeBuilder(Type type) {
100+
this.type = type;
101+
this.optional = type.makeOptional();
102+
}
103+
104+
void validateOptional(Value<?> value) {
105+
this.isOptional = isOptional || value == NULL;
106+
}
107+
108+
Value<?> makeValue(Value<?> value) {
109+
if (!isOptional) {
110+
return value;
111+
}
112+
113+
if (value == NULL) {
114+
return optional.emptyValue();
115+
}
116+
117+
return value.makeOptional();
118+
}
119+
120+
Type makeType() {
121+
return isOptional ? optional : type;
122+
}
72123
}
73124

74125
private class Item implements JdbcPrm {
75126
private final String name;
76127
private final int index;
128+
private final int memberId;
77129
private Value<?> value = null;
78130

79-
Item(String listName, int index) {
131+
Item(String listName, int index, int tupleIdx) {
80132
this.name = listName + "[" + index + "]";
81133
this.index = index;
134+
this.memberId = tupleIdx;
82135
}
83136

84137
@Override
@@ -88,13 +141,13 @@ public String getName() {
88141

89142
@Override
90143
public TypeDescription getType() {
91-
return type;
144+
return tupleTypes[memberId];
92145
}
93146

94147
@Override
95148
public void setValue(Object obj, int sqlType) throws SQLException {
96-
if (type == null) {
97-
Type ydbType = types.findType(obj, sqlType);
149+
if (tupleTypes[memberId] == null) {
150+
Type ydbType = ydbTypes.findType(obj, sqlType);
98151
if (ydbType == null) {
99152
if (obj == null) {
100153
value = NULL;
@@ -104,15 +157,15 @@ public void setValue(Object obj, int sqlType) throws SQLException {
104157
}
105158
}
106159

107-
type = types.find(ydbType);
160+
tupleTypes[memberId] = ydbTypes.find(ydbType);
108161
}
109162

110163
if (obj == null) {
111164
value = NULL;
112165
return;
113166
}
114167

115-
value = type.toYdbValue(obj);
168+
value = tupleTypes[memberId].toYdbValue(obj);
116169
}
117170

118171
@Override
@@ -126,7 +179,7 @@ public void copyToParams(Params params) throws SQLException {
126179
public void reset() {
127180
value = null;
128181
if (index == items.size() - 1) { // last prm reset type
129-
type = null;
182+
Arrays.fill(tupleTypes, null);
130183
}
131184
}
132185
}

0 commit comments

Comments
 (0)