From e1bc500a94074048813fef641dea405746e53345 Mon Sep 17 00:00:00 2001 From: Alexandr Gorshenin Date: Thu, 6 Nov 2025 15:43:17 +0000 Subject: [PATCH 1/2] Added support of IN clause with tuple list --- .../tech/ydb/jdbc/query/YdbQueryParser.java | 42 ++++--- .../tech/ydb/jdbc/query/YqlListParser.java | 118 ++++++++++++++++++ .../ydb/jdbc/query/params/AsTableJdbcPrm.java | 2 +- .../ydb/jdbc/query/params/InListJdbcPrm.java | 109 +++++++++++----- .../tech/ydb/jdbc/query/params/JdbcPrm.java | 4 +- 5 files changed, 225 insertions(+), 50 deletions(-) create mode 100644 jdbc/src/main/java/tech/ydb/jdbc/query/YqlListParser.java diff --git a/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQueryParser.java b/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQueryParser.java index 9c9c511..c4193fe 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQueryParser.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQueryParser.java @@ -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; diff --git a/jdbc/src/main/java/tech/ydb/jdbc/query/YqlListParser.java b/jdbc/src/main/java/tech/ydb/jdbc/query/YqlListParser.java new file mode 100644 index 0000000..b93040b --- /dev/null +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/YqlListParser.java @@ -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; + } +} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/query/params/AsTableJdbcPrm.java b/jdbc/src/main/java/tech/ydb/jdbc/query/params/AsTableJdbcPrm.java index 9128f0b..1e37a5e 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/query/params/AsTableJdbcPrm.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/params/AsTableJdbcPrm.java @@ -42,7 +42,7 @@ public List 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; diff --git a/jdbc/src/main/java/tech/ydb/jdbc/query/params/InListJdbcPrm.java b/jdbc/src/main/java/tech/ydb/jdbc/query/params/InListJdbcPrm.java index b59127d..0213606 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/query/params/InListJdbcPrm.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/params/InListJdbcPrm.java @@ -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; @@ -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; @@ -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 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; } } @@ -38,47 +48,90 @@ public List 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> values = new ArrayList<>(); - if (!hasNull) { + if (types.length == 1) { // Simple list + TypeBuilder type = types[0]; + List> 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> 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 @@ -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; @@ -104,7 +157,7 @@ public void setValue(Object obj, int sqlType) throws SQLException { } } - type = types.find(ydbType); + tupleTypes[memberId] = ydbTypes.find(ydbType); } if (obj == null) { @@ -112,7 +165,7 @@ public void setValue(Object obj, int sqlType) throws SQLException { return; } - value = type.toYdbValue(obj); + value = tupleTypes[memberId].toYdbValue(obj); } @Override @@ -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); } } } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/query/params/JdbcPrm.java b/jdbc/src/main/java/tech/ydb/jdbc/query/params/JdbcPrm.java index 5adec26..3742d5b 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/query/params/JdbcPrm.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/params/JdbcPrm.java @@ -34,8 +34,8 @@ static Factory uint64Prm(YdbTypes types, String name) { return () -> Collections.singletonList(new UInt64JdbcPrm(types, name)); } - static Factory inListOrm(YdbTypes types, String name, int count) { - return () -> new InListJdbcPrm(types, name, count).toJdbcPrmList(); + static Factory inListOrm(YdbTypes types, String name, int listSize, int tupleSize) { + return () -> new InListJdbcPrm(types, name, listSize, tupleSize).toJdbcPrmList(); } static Factory jdbcTableListOrm(YdbTypes types, String name, int count) { From 534eacca98596b1e1718ed67a273f6b4651c6d52 Mon Sep 17 00:00:00 2001 From: Alexandr Gorshenin Date: Thu, 6 Nov 2025 15:43:34 +0000 Subject: [PATCH 2/2] Updated tests for IN processing --- .../jdbc/impl/YdbPreparedStatementTest.java | 40 +++++- .../ydb/jdbc/query/YdbQueryParserTest.java | 115 +++++++++++++----- 2 files changed, 117 insertions(+), 38 deletions(-) diff --git a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementTest.java b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementTest.java index d0a4dba..6471c67 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementTest.java @@ -665,11 +665,14 @@ private void assertResultSetCount(ResultSet rs, int count) throws SQLException { @ValueSource(strings = {"true", "false"}) public void inListTest(boolean convertInToList) throws SQLException { String option = String.valueOf(convertInToList); + String arg1Name = convertInToList ? "$jp1[0]" : "$jp1"; String arg2Name = convertInToList ? "$jp1[1]" : "$jp2"; try (Connection conn = jdbc.createCustomConnection("replaceJdbcInByYqlList", option)) { String upsert = TEST_TABLE.upsertOne(SqlQueries.JdbcQuery.STANDARD, "c_Text", "Text"); - String selectByIds = TEST_TABLE.withTableName("select count(*) from #tableName where key in (?, ?)"); - String selectByValue = TEST_TABLE.withTableName("select count(*) from #tableName where c_Text in (?, ?)"); + String selectPrefix = TEST_TABLE.withTableName("select count(*) from #tableName "); + String selectByIds = selectPrefix + "where key in (?, ?)"; + String selectByValue = selectPrefix + "where c_Text in (?, ?)"; + String selectByTuple = selectPrefix + "where (key, c_Text) in ((?, ?), (?, ?))"; try (PreparedStatement ps = conn.prepareStatement(upsert)) { ps.setInt(1, 1); @@ -717,6 +720,8 @@ public void inListTest(boolean convertInToList) throws SQLException { } try (PreparedStatement ps = conn.prepareStatement(selectByValue)) { + ExceptionAssert.sqlException("Missing value for parameter: " + arg1Name, ps::executeQuery); + ps.setString(1, null); ExceptionAssert.sqlException("Missing value for parameter: " + arg2Name, ps::executeQuery); @@ -744,12 +749,35 @@ public void inListTest(boolean convertInToList) throws SQLException { ps.setString(2, "3"); assertResultSetCount(ps.executeQuery(), 2); } + + try (PreparedStatement ps = conn.prepareStatement(selectByTuple)) { + ExceptionAssert.sqlException("Missing value for parameter: " + arg1Name, ps::executeQuery); + + ps.setInt(1, 1); + ExceptionAssert.sqlException("Missing value for parameter: " + arg2Name, ps::executeQuery); + + ps.setInt(1, 1); + ps.setInt(3, 2); + ps.setString(4, "3"); + ExceptionAssert.sqlException("Missing value for parameter: " + arg2Name, ps::executeQuery); + + ps.setInt(1, 1); + ps.setString(2, null); + ps.setInt(3, 2); + ps.setString(4, "3"); + assertResultSetCount(ps.executeQuery(), 0); + + ps.setInt(1, 1); + ps.setString(2, "1"); + ps.setInt(3, 3); + ps.setString(4, "3"); + assertResultSetCount(ps.executeQuery(), 2); + } } } @Test public void jdbcTableListTest() throws SQLException { - String arg2Name = "$jp1[1]"; String upsert = TEST_TABLE.upsertOne(SqlQueries.JdbcQuery.STANDARD, "c_Text", "Text"); String selectByIds = TEST_TABLE.withTableName( "select count(*) from jdbc_table(?,?) as j join #tableName t on t.key=j.x" @@ -779,8 +807,10 @@ public void jdbcTableListTest() throws SQLException { } try (PreparedStatement ps = jdbc.connection().prepareStatement(selectByIds)) { + ExceptionAssert.sqlException("Missing value for parameter: $jp1[0]", ps::executeQuery); + ps.setInt(1, 1); - ExceptionAssert.sqlException("Missing value for parameter: " + arg2Name, ps::executeQuery); + ExceptionAssert.sqlException("Missing value for parameter: $jp1[1]", ps::executeQuery); ps.setInt(1, 1); ps.setInt(2, 2); @@ -798,7 +828,7 @@ public void jdbcTableListTest() throws SQLException { try (PreparedStatement ps = jdbc.connection().prepareStatement(selectByValue)) { ps.setString(1, null); - ExceptionAssert.sqlException("Missing value for parameter: " + arg2Name, ps::executeQuery); + ExceptionAssert.sqlException("Missing value for parameter: $jp1[1]", ps::executeQuery); ps.setString(1, null); ps.setString(2, null); diff --git a/jdbc/src/test/java/tech/ydb/jdbc/query/YdbQueryParserTest.java b/jdbc/src/test/java/tech/ydb/jdbc/query/YdbQueryParserTest.java index 53bfffc..217a334 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/query/YdbQueryParserTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/query/YdbQueryParserTest.java @@ -287,6 +287,10 @@ public void noOffsetParameterTest(String query) throws SQLException { + "@'select * from test_table where id in $jp1'", "'select * from test_table where id In(?--comment\n,?,?/**other /** inner */ comment*/)'" + "@'select * from test_table where id In $jp1'", + "'select * from test_table where (id, value) in ((?, ?), (?, ?))'" + + "@'select * from test_table where (id, value) in $jp1'", + "'select * from test_table where tuple in ((?,\n?),(?, ?))'" + + "@'select * from test_table where tuple in $jp1'", }, delimiter = '@') public void inListParameterTest(String query, String parsed) throws SQLException { YdbQueryParser parser = new YdbQueryParser(types, query, props); @@ -307,44 +311,18 @@ public void inListParameterTest(String query, String parsed) throws SQLException } } - @ParameterizedTest(name = "[{index}] {0} has as_table list parameter") - @CsvSource(value = { - "'select * from jdbc_table(?) as t join test_table on id=t.x'" - + "@'select * from AS_TABLE($jp1) as t join test_table on id=t.x'", - "'select * from jdbc_table(?,\n?, ?, \t?)'" - + "@'select * from AS_TABLE($jp1)'", - "'select * from JDbc_Table (?--comment\n,?,?/**other /** inner */ comment*/)'" - + "@'select * from AS_TABLE($jp1)'", - }, delimiter = '@') - public void jdbcTableinListParameterTest(String query, String parsed) throws SQLException { - YdbQueryParser parser = new YdbQueryParser(types, query, props); - Assertions.assertEquals(parsed, parser.parseSQL()); - - Assertions.assertEquals(1, parser.getStatements().size()); - - QueryStatement statement = parser.getStatements().get(0); - Assertions.assertEquals(QueryType.DATA_QUERY, statement.getType()); - - Assertions.assertTrue(statement.hasJdbcParameters()); - int idx = 0; - for (JdbcPrm.Factory factory : statement.getJdbcPrmFactories()) { - for (JdbcPrm prm: factory.create()) { - Assertions.assertEquals("$jp1[" + idx + "]", prm.getName()); - idx++; - } - } - } - @ParameterizedTest(name = "[{index}] {0} has not in list parameter") @CsvSource(value = { "'select * from test_table where id in (?)'" + "@'select * from test_table where id in ($jp1)'", + "'select * from test_table where (id, value) in ((?, ?), (?, ?))'" + + "@'select * from test_table where (id, value) in (($jp1, $jp2), ($jp3, $jp4))'", "'select * from test_table where id in (?,\n?, ?, \t?)'" + "@'select * from test_table where id in ($jp1,\n$jp2, $jp3, \t$jp4)'", "'select * from test_table where id In(?--comment\n,?,?/**other /** inner */ comment*/)'" + "@'select * from test_table where id In($jp1--comment\n,$jp2,$jp3/**other /** inner */ comment*/)'", }, delimiter = '@') - public void disabledInListParameterTest(String query, String parsed) throws SQLException { + public void inListParameterDisabledTest(String query, String parsed) throws SQLException { Properties config = new Properties(); config.put("replaceJdbcInByYqlList", "false"); YdbQueryParser parser = new YdbQueryParser(types, query, new YdbQueryProperties(config)); @@ -365,7 +343,7 @@ public void disabledInListParameterTest(String query, String parsed) throws SQLE } } - @ParameterizedTest(name = "[{index}] {0} has in list parameter") + @ParameterizedTest(name = "[{index}] {0} doesn't have in list parameter") @CsvSource(value = { "'select * from test_table where id in (?, 1, ?)'" + "@'select * from test_table where id in ($jp1, 1, $jp2)'", @@ -379,12 +357,81 @@ public void disabledInListParameterTest(String query, String parsed) throws SQLE + "@'select * from test_table where id in($jp1, $jp2, $jp3'", "'select * from test_table where id in ?, ?, ?'" + "@'select * from test_table where id in $jp1, $jp2, $jp3'", - "'select * from test_table where id in ((?))'" - + "@'select * from test_table where id in (($jp1))'", + "'select * from test_table where id in (((?)))'" + + "@'select * from test_table where id in ((($jp1)))'", "'select * from test_table where id in ,?)'" + "@'select * from test_table where id in ,$jp1)'", + "'select * from test_table where id in (())'" + + "@'select * from test_table where id in (())'", + "'select * from test_table where id in ((?, ?), ?)'" + + "@'select * from test_table where id in (($jp1, $jp2), $jp3)'", + "'select * from test_table where id in ((?,?),(?,?,?))'" + + "@'select * from test_table where id in (($jp1,$jp2),($jp3,$jp4,$jp5))'", + }, delimiter = '@') + public void inListParametersUnparsableTest(String query, String parsed) throws SQLException { + YdbQueryParser parser = new YdbQueryParser(types, query, props); + Assertions.assertEquals(parsed, parser.parseSQL()); + + Assertions.assertEquals(1, parser.getStatements().size()); + + QueryStatement statement = parser.getStatements().get(0); + Assertions.assertEquals(QueryType.DATA_QUERY, statement.getType()); + + int idx = 0; + for (JdbcPrm.Factory factory : statement.getJdbcPrmFactories()) { + for (JdbcPrm prm: factory.create()) { + idx++; + Assertions.assertEquals("$jp" + idx, prm.getName()); + } + } + } + + @ParameterizedTest(name = "[{index}] {0} has as_table list parameter") + @CsvSource(value = { + "'select * from jdbc_table(?) as t join test_table on id=t.x'" + + "@'select * from AS_TABLE($jp1) as t join test_table on id=t.x'", + "'select * from jdbc_table(?,\n?, ?, \t?)'" + + "@'select * from AS_TABLE($jp1)'", + "'select * from JDbc_Table (?--comment\n,?,?/**other /** inner */ comment*/)'" + + "@'select * from AS_TABLE($jp1)'", + }, delimiter = '@') + public void jdbcTableParameterTest(String query, String parsed) throws SQLException { + YdbQueryParser parser = new YdbQueryParser(types, query, props); + Assertions.assertEquals(parsed, parser.parseSQL()); + + Assertions.assertEquals(1, parser.getStatements().size()); + + QueryStatement statement = parser.getStatements().get(0); + Assertions.assertEquals(QueryType.DATA_QUERY, statement.getType()); + + Assertions.assertTrue(statement.hasJdbcParameters()); + int idx = 0; + for (JdbcPrm.Factory factory : statement.getJdbcPrmFactories()) { + for (JdbcPrm prm: factory.create()) { + Assertions.assertEquals("$jp1[" + idx + "]", prm.getName()); + idx++; + } + } + } + + @ParameterizedTest(name = "[{index}] {0} doesn't have as_table list parameter") + @CsvSource(value = { + "'select * from jdbc_table((?)) as t join test_table on id=t.x'" + + "@'select * from jdbc_table(($jp1)) as t join test_table on id=t.x'", + "'select * from jdbc_table(?,) as t join test_table on id=t.x'" + + "@'select * from jdbc_table($jp1,) as t join test_table on id=t.x'", + "'select * from jdbc_table where id = ?'" + + "@'select * from jdbc_table where id = $jp1'", + "'select * from jdbc_table,other_table where id = ?'" + + "@'select * from jdbc_table,other_table where id = $jp1'", + "'select * from jdbc_table(?,,?) as t join test_table on id=t.x'" + + "@'select * from jdbc_table($jp1,,$jp2) as t join test_table on id=t.x'", + "'select * from jdbc_table(?,'" + + "@'select * from jdbc_table($jp1,'", + "'select * from jdbc_table(??) where id=?'" + + "@'select * from jdbc_table(?) where id=$jp1'", }, delimiter = '@') - public void wrongInListParameterTest(String query, String parsed) throws SQLException { + public void jdbcTableParameterUnparsableTest(String query, String parsed) throws SQLException { YdbQueryParser parser = new YdbQueryParser(types, query, props); Assertions.assertEquals(parsed, parser.parseSQL()); @@ -393,6 +440,7 @@ public void wrongInListParameterTest(String query, String parsed) throws SQLExce QueryStatement statement = parser.getStatements().get(0); Assertions.assertEquals(QueryType.DATA_QUERY, statement.getType()); + Assertions.assertTrue(statement.hasJdbcParameters()); int idx = 0; for (JdbcPrm.Factory factory : statement.getJdbcPrmFactories()) { for (JdbcPrm prm: factory.create()) { @@ -402,6 +450,7 @@ public void wrongInListParameterTest(String query, String parsed) throws SQLExce } } + @ParameterizedTest(name = "[{index}] {0} is batched insert query") @ValueSource(strings = { "Insert into table_name(c1, c2, c3) values (?, ? , ?)",