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
42 changes: 23 additions & 19 deletions jdbc/src/main/java/tech/ydb/jdbc/query/YdbQueryParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -383,42 +383,46 @@ private int parseOffsetLimitParameter(char[] query, int offset, QueryStatement s
private int parseInListParameters(char[] query, int offset, QueryStatement st) {
int start = offset;
int listStartedAt = -1;
int listSize = 0;
boolean waitPrm = false;
YqlListParser parser = new YqlListParser();

while (offset < query.length) {
char ch = query[offset];
switch (ch) {
case '(': // start of list
if (listStartedAt >= 0) {
case '(':
if (parser.isNotStarted()) {
listStartedAt = offset;
}
if (!parser.readOpenParen()) {
return start;
}
listStartedAt = offset;
waitPrm = true;
break;
case ',':
if (listStartedAt < 0 || waitPrm) {
if (!parser.readComma()) {
return start;
}
waitPrm = true;
break;
case '?' :
if (!waitPrm || (offset + 1 < query.length && query[offset + 1] == '?')) {
if (offset + 1 < query.length && query[offset + 1] == '?') {
return start;
}

if (!parser.readParameter()) {
return start;
}
listSize++;
waitPrm = false;
break;
case ')':
if (waitPrm || listSize == 0 || listStartedAt < 0) {
if (!parser.readCloseParen()) {
return start;
}

String name = nextJdbcPrmName();
parsed.append(query, start, listStartedAt - start);
parsed.append(' '); // add extra space to avoid IN$jpN
parsed.append(name);
st.addJdbcPrmFactory(JdbcPrm.inListOrm(types, name, listSize));
return offset + 1;
if (parser.isCompleted()) {
String name = nextJdbcPrmName();
parsed.append(query, start, listStartedAt - start);
parsed.append(' '); // add extra space to avoid IN$jpN
parsed.append(name);
st.addJdbcPrmFactory(JdbcPrm.inListOrm(types, name, parser.listSize(), parser.tupleSize()));
return offset + 1;
}
break;
case '-': // possibly -- style comment
offset = parseLineComment(query, offset);
break;
Expand Down
118 changes: 118 additions & 0 deletions jdbc/src/main/java/tech/ydb/jdbc/query/YqlListParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package tech.ydb.jdbc.query;

/**
*
* @author Aleksandr Gorshenin
*/
public class YqlListParser {

private enum State {
BEFORE_LIST,
BEFORE_PARAM_OR_TUPLE,

BEFORE_PARAM,
BEFORE_TUPLE,
BEFORE_TUPLE_PARAM,

AFTER_TUPLE,
AFTER_PARAM,
AFTER_TUPLE_PARAM,

AFTER_LIST,

ERROR
}

private State state = State.BEFORE_LIST;

private int listSize = 0;
private int tupleSize = -1;

private int currentTupleSize = 0;

public boolean isNotStarted() {
return state == State.BEFORE_LIST;
}

public boolean isCompleted() {
return state == State.AFTER_LIST;
}

public int listSize() {
return listSize;
}

public int tupleSize() {
return tupleSize > 0 ? tupleSize : 1;
}

public boolean readOpenParen() {
if (state == State.BEFORE_LIST) {
state = State.BEFORE_PARAM_OR_TUPLE;
return true;
}

if (state == State.BEFORE_PARAM_OR_TUPLE || state == State.BEFORE_TUPLE) {
state = State.BEFORE_TUPLE_PARAM;
return true;
}

state = State.ERROR;
return false;
}

public boolean readCloseParen() {
if (state == State.AFTER_TUPLE_PARAM) {
if (tupleSize >= 0 && currentTupleSize != tupleSize) { // all tuples must have the same count of parameters
state = State.ERROR;
return false;
}

tupleSize = currentTupleSize;
currentTupleSize = 0;
listSize++;
state = State.AFTER_TUPLE;
return true;
}

if (state == State.AFTER_PARAM || state == State.AFTER_TUPLE) {
state = State.AFTER_LIST;
return true;
}

return false;
}

public boolean readComma() {
if (state == State.AFTER_PARAM) {
state = State.BEFORE_PARAM;
return true;
}
if (state == State.AFTER_TUPLE_PARAM) {
state = State.BEFORE_TUPLE_PARAM;
return true;
}
if (state == State.AFTER_TUPLE) {
state = State.BEFORE_TUPLE;
return true;
}

state = State.ERROR;
return false;
}

public boolean readParameter() {
if (state == State.BEFORE_PARAM_OR_TUPLE || state == State.BEFORE_PARAM) {
listSize++;
state = State.AFTER_PARAM;
return true;
}
if (state == State.BEFORE_TUPLE_PARAM) {
currentTupleSize++;
state = State.AFTER_TUPLE_PARAM;
return true;
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public List<? extends JdbcPrm> toJdbcPrmList() {

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

boolean hasNull = false;
Expand Down
109 changes: 81 additions & 28 deletions jdbc/src/main/java/tech/ydb/jdbc/query/params/InListJdbcPrm.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import tech.ydb.jdbc.YdbConst;
Expand All @@ -10,6 +11,7 @@
import tech.ydb.table.query.Params;
import tech.ydb.table.values.ListType;
import tech.ydb.table.values.OptionalType;
import tech.ydb.table.values.TupleType;
import tech.ydb.table.values.Type;
import tech.ydb.table.values.Value;
import tech.ydb.table.values.VoidValue;
Expand All @@ -20,16 +22,24 @@
*/
public class InListJdbcPrm {
private static final Value<?> NULL = VoidValue.of();
private final YdbTypes types;
private final YdbTypes ydbTypes;
private final String listName;
private final List<Item> items = new ArrayList<>();
private TypeDescription type;
private final Item[][] tuples;
private final TypeDescription[] tupleTypes;

public InListJdbcPrm(YdbTypes types, String listName, int listSize) {
this.types = types;
public InListJdbcPrm(YdbTypes types, String listName, int listSize, int tupleSize) {
this.ydbTypes = types;
this.listName = listName;
for (int idx = 0; idx < listSize; idx++) {
items.add(new Item(listName, idx));
this.tupleTypes = new TypeDescription[tupleSize];
this.tuples = new Item[listSize][];
for (int idx = 0; idx < listSize; idx += 1) {
Item[] tuple = new Item[tupleSize];
for (int memberIdx = 0; memberIdx < tupleSize; memberIdx += 1) {
tuple[memberIdx] = new Item(listName, idx * tupleSize + memberIdx, memberIdx);
items.add(tuple[memberIdx]);
}
tuples[idx] = tuple;
}
}

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

private Value<?> buildList() throws SQLException {
if (type == null) {
throw new SQLException(YdbConst.PARAMETER_TYPE_UNKNOWN);
TypeBuilder[] types = new TypeBuilder[tupleTypes.length];
for (int idx = 0; idx < tupleTypes.length; idx += 1) {
if (tupleTypes[idx] == null) {
throw new SQLException(YdbConst.MISSING_VALUE_FOR_PARAMETER + tuples[0][idx].name);
}
types[idx] = new TypeBuilder(tupleTypes[idx].ydbType());
}

boolean hasNull = false;
for (Item item: items) {
if (item.value == null) {
throw new SQLException(YdbConst.MISSING_VALUE_FOR_PARAMETER + item.name);
}
hasNull = hasNull || item.value == NULL;
types[item.memberId].validateOptional(item.value);
}

List<Value<?>> values = new ArrayList<>();
if (!hasNull) {
if (types.length == 1) { // Simple list
TypeBuilder type = types[0];
List<Value<?>> values = new ArrayList<>();
for (Item item: items) {
values.add(item.value);
values.add(type.makeValue(item.value));
}
return ListType.of(type.ydbType()).newValue(values);
return ListType.of(type.makeType()).newValue(values);
}

OptionalType optional = type.ydbType().makeOptional();
for (Item item: items) {
if (item.value == NULL) {
values.add(optional.emptyValue());
} else {
values.add(item.value.makeOptional());
Type[] tupleMemberTypes = new Type[tupleTypes.length];
for (int idx = 0; idx < tupleTypes.length; idx += 1) {
tupleMemberTypes[idx] = types[idx].makeType();
}

TupleType tupleType = TupleType.ofOwn(tupleMemberTypes);
List<Value<?>> values = new ArrayList<>();
for (Item[] tupleItems : tuples) {
Value<?>[] tupleValues = new Value<?>[tupleItems.length];
for (int idx = 0; idx < tupleItems.length; idx += 1) {
tupleValues[idx] = types[idx].makeValue(tupleItems[idx].value);
}
values.add(tupleType.newValueOwn(tupleValues));
}

return ListType.of(optional).newValue(values);
return ListType.of(tupleType).newValue(values);
}

private class TypeBuilder {
private final Type type;
private final OptionalType optional;

private boolean isOptional = false;

TypeBuilder(Type type) {
this.type = type;
this.optional = type.makeOptional();
}

void validateOptional(Value<?> value) {
this.isOptional = isOptional || value == NULL;
}

Value<?> makeValue(Value<?> value) {
if (!isOptional) {
return value;
}

if (value == NULL) {
return optional.emptyValue();
}

return value.makeOptional();
}

Type makeType() {
return isOptional ? optional : type;
}
}

private class Item implements JdbcPrm {
private final String name;
private final int index;
private final int memberId;
private Value<?> value = null;

Item(String listName, int index) {
Item(String listName, int index, int tupleIdx) {
this.name = listName + "[" + index + "]";
this.index = index;
this.memberId = tupleIdx;
}

@Override
Expand All @@ -88,13 +141,13 @@ public String getName() {

@Override
public TypeDescription getType() {
return type;
return tupleTypes[memberId];
}

@Override
public void setValue(Object obj, int sqlType) throws SQLException {
if (type == null) {
Type ydbType = types.findType(obj, sqlType);
if (tupleTypes[memberId] == null) {
Type ydbType = ydbTypes.findType(obj, sqlType);
if (ydbType == null) {
if (obj == null) {
value = NULL;
Expand All @@ -104,15 +157,15 @@ public void setValue(Object obj, int sqlType) throws SQLException {
}
}

type = types.find(ydbType);
tupleTypes[memberId] = ydbTypes.find(ydbType);
}

if (obj == null) {
value = NULL;
return;
}

value = type.toYdbValue(obj);
value = tupleTypes[memberId].toYdbValue(obj);
}

@Override
Expand All @@ -126,7 +179,7 @@ public void copyToParams(Params params) throws SQLException {
public void reset() {
value = null;
if (index == items.size() - 1) { // last prm reset type
type = null;
Arrays.fill(tupleTypes, null);
}
}
}
Expand Down
Loading