Skip to content

Commit 9626ff3

Browse files
authored
Fixed bulk upsert validation (#103)
2 parents c51d456 + b6dcb57 commit 9626ff3

12 files changed

+121
-31
lines changed

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

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,9 @@ public void clearBatch() {
9595

9696
protected StructValue getCurrentValues() throws SQLException {
9797
for (ParamDescription prm: params) {
98-
if (currentValues.containsKey(prm.name())) {
99-
continue;
98+
if (!currentValues.containsKey(prm.name())) {
99+
throw new SQLDataException(YdbConst.MISSING_VALUE_FOR_PARAMETER + prm.displayName());
100100
}
101-
102-
if (prm.type().isOptional()) {
103-
currentValues.put(prm.name(), prm.type().nullValue());
104-
continue;
105-
}
106-
107-
throw new SQLDataException(YdbConst.MISSING_VALUE_FOR_PARAMETER + prm.displayName());
108101
}
109102
return StructValue.of(currentValues);
110103
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ public static BulkUpsertQuery build(String tablePath, List<String> columns, Tabl
5757

5858
Map<String, Type> structTypes = new HashMap<>();
5959
for (String column: columns) {
60+
if (!columnTypes.containsKey(column)) {
61+
throw new SQLException("Cannot parse BULK upsert: column " + column + " not found");
62+
}
6063
structTypes.put(column, columnTypes.get(column));
6164
}
6265

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

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

33

44

5+
import java.sql.SQLDataException;
56
import java.sql.SQLException;
67
import java.util.ArrayList;
78
import java.util.HashMap;
@@ -109,15 +110,15 @@ public Params getCurrentParams() throws SQLException {
109110
@Override
110111
public String getNameByIndex(int index) throws SQLException {
111112
if (index <= 0 || index > parameters.size()) {
112-
throw new SQLException(YdbConst.PARAMETER_NUMBER_NOT_FOUND + index);
113+
throw new SQLDataException(YdbConst.PARAMETER_NUMBER_NOT_FOUND + index);
113114
}
114115
return parameters.get(index - 1).getName();
115116
}
116117

117118
@Override
118119
public TypeDescription getDescription(int index) throws SQLException {
119120
if (index <= 0 || index > parameters.size()) {
120-
throw new SQLException(YdbConst.PARAMETER_NUMBER_NOT_FOUND + index);
121+
throw new SQLDataException(YdbConst.PARAMETER_NUMBER_NOT_FOUND + index);
121122
}
122123

123124
JdbcPrm p = parameters.get(index - 1);
@@ -127,7 +128,7 @@ public TypeDescription getDescription(int index) throws SQLException {
127128
@Override
128129
public void setParam(int index, Object obj, int sqlType) throws SQLException {
129130
if (index <= 0 || index > parameters.size()) {
130-
throw new SQLException(YdbConst.PARAMETER_NUMBER_NOT_FOUND + index);
131+
throw new SQLDataException(YdbConst.PARAMETER_NUMBER_NOT_FOUND + index);
131132
}
132133

133134
parameters.get(index - 1).setValue(obj, sqlType);
@@ -137,7 +138,7 @@ public void setParam(int index, Object obj, int sqlType) throws SQLException {
137138
public void setParam(String name, Object obj, int sqlType) throws SQLException {
138139
JdbcPrm param = parametersByName.get(name);
139140
if (param == null) {
140-
throw new SQLException(YdbConst.PARAMETER_NUMBER_NOT_FOUND + name);
141+
throw new SQLDataException(YdbConst.PARAMETER_NUMBER_NOT_FOUND + name);
141142
}
142143
param.setValue(obj, sqlType);
143144
}

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

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

3+
import java.sql.SQLDataException;
34
import java.sql.SQLException;
45

56
import tech.ydb.jdbc.YdbConst;
@@ -34,7 +35,7 @@ public void reset() {
3435
@Override
3536
public void copyToParams(Params params) throws SQLException {
3637
if (value == null) {
37-
throw new SQLException(YdbConst.MISSING_VALUE_FOR_PARAMETER + name);
38+
throw new SQLDataException(YdbConst.MISSING_VALUE_FOR_PARAMETER + name);
3839
}
3940
params.put(name, value);
4041
}
@@ -56,7 +57,7 @@ public void setValue(Object obj, int sqlType) throws SQLException {
5657

5758
Type type = YdbTypes.findType(obj, sqlType);
5859
if (type == null) {
59-
throw new SQLException(String.format(YdbConst.PARAMETER_TYPE_UNKNOWN, sqlType, obj));
60+
throw new SQLDataException(String.format(YdbConst.PARAMETER_TYPE_UNKNOWN, sqlType, obj));
6061
}
6162

6263
if (obj == null) {

jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementImplTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,10 @@ public void addAndClearBatch(SqlQueries.YqlQuery mode) throws SQLException {
231231
@EnumSource(SqlQueries.YqlQuery.class)
232232
public void executeEmptyBatch(SqlQueries.YqlQuery mode) throws SQLException {
233233
String yql = TEST_TABLE.upsertOne(mode, "c_Text", "Text");
234+
String prm = mode == SqlQueries.YqlQuery.SIMPLE ? "$key" : "$c_Text";
234235
try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(yql)) {
235-
ExceptionAssert.sqlDataException("Missing value for parameter", () -> statement.execute());
236-
ExceptionAssert.sqlDataException("Missing value for parameter", () -> statement.executeUpdate());
236+
ExceptionAssert.sqlDataException("Missing value for parameter: " + prm, statement::execute);
237+
ExceptionAssert.sqlDataException("Missing value for parameter: " + prm, statement::executeUpdate);
237238
statement.executeBatch();
238239
}
239240

jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementTest.java

Lines changed: 87 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.time.temporal.ChronoField;
2121
import java.time.temporal.ChronoUnit;
2222
import java.util.UUID;
23+
import java.util.function.Function;
2324

2425
import org.junit.jupiter.api.AfterAll;
2526
import org.junit.jupiter.api.Assertions;
@@ -126,21 +127,40 @@ public void executeUpdateTest(SqlQueries.JdbcQuery query) throws SQLException {
126127
}
127128
}
128129

129-
@ParameterizedTest(name = "with {0}")
130-
@EnumSource(value=SqlQueries.JdbcQuery.class, names = { "BATCHED", "TYPED" })
131-
public void executeWithMissingParameter(SqlQueries.JdbcQuery query) throws SQLException {
132-
String sql = TEST_TABLE.upsertOne(query, "c_Text", "Text");
130+
@Test
131+
public void executeWithMissingParameter() throws SQLException {
132+
Function<SqlQueries.JdbcQuery, String> upsert = mode -> TEST_TABLE.upsertOne(mode, "c_Text", "Text");
133133

134-
try (PreparedStatement statement = jdbc.connection().prepareStatement(sql)) {
135-
statement.setInt(1, 1);
136-
ExceptionAssert.sqlDataException("Missing value for parameter", statement::execute);
134+
try (PreparedStatement ps = jdbc.connection().prepareStatement(upsert.apply(SqlQueries.JdbcQuery.STANDARD))) {
135+
ps.setInt(1, 1);
136+
ExceptionAssert.sqlDataException("Missing value for parameter: $p2", ps::execute);
137+
}
138+
139+
try (PreparedStatement ps = jdbc.connection().prepareStatement(upsert.apply(SqlQueries.JdbcQuery.IN_MEMORY))) {
140+
ps.setInt(1, 1);
141+
ExceptionAssert.sqlDataException("Missing value for parameter: $jp2", ps::execute);
142+
}
143+
144+
try (PreparedStatement ps = jdbc.connection().prepareStatement(upsert.apply(SqlQueries.JdbcQuery.TYPED))) {
145+
ps.setInt(1, 1);
146+
ExceptionAssert.sqlDataException("Missing value for parameter: $p2", ps::execute);
147+
}
148+
149+
try (PreparedStatement ps = jdbc.connection().prepareStatement(upsert.apply(SqlQueries.JdbcQuery.BATCHED))) {
150+
ps.setInt(1, 1);
151+
ExceptionAssert.sqlDataException("Missing value for parameter: $p2", ps::execute);
152+
}
153+
154+
try (PreparedStatement ps = jdbc.connection().prepareStatement(upsert.apply(SqlQueries.JdbcQuery.BULK))) {
155+
ps.setInt(1, 1);
156+
ExceptionAssert.sqlDataException("Missing value for parameter: $c_Text", ps::execute);
137157
}
138158
}
139159

140160
@ParameterizedTest(name = "with {0}")
141161
@EnumSource(value=SqlQueries.JdbcQuery.class, names = { "BATCHED", "TYPED" })
142162
public void executeWithWrongType(SqlQueries.JdbcQuery query) throws SQLException {
143-
String sql = TEST_TABLE.upsertOne(query, "c_Text", "Text"); // Must be Optional<Text>
163+
String sql = TEST_TABLE.upsertOne(query, "c_Text", "Text");
144164

145165
try (PreparedStatement statement = jdbc.connection().prepareStatement(sql)) {
146166
statement.setInt(1, 1);
@@ -150,6 +170,65 @@ public void executeWithWrongType(SqlQueries.JdbcQuery query) throws SQLException
150170
}
151171
}
152172

173+
@ParameterizedTest(name = "with {0}")
174+
@ValueSource(strings = { "c_NotText" , "C_TEXT", "c_text" })
175+
public void executeWithWrongColumnName(String columnName) throws SQLException {
176+
String errorMessage = "No such column: " + columnName;
177+
178+
String standard = TEST_TABLE.upsertOne(SqlQueries.JdbcQuery.STANDARD, columnName, "Text");
179+
String inMemory = TEST_TABLE.upsertOne(SqlQueries.JdbcQuery.IN_MEMORY, columnName, "Text");
180+
String typed = TEST_TABLE.upsertOne(SqlQueries.JdbcQuery.TYPED, columnName, "Text");
181+
String batched = TEST_TABLE.upsertOne(SqlQueries.JdbcQuery.BATCHED, columnName, "Text");
182+
String bulk = TEST_TABLE.upsertOne(SqlQueries.JdbcQuery.BULK, columnName, "Text");
183+
184+
try (PreparedStatement statement = jdbc.connection().prepareStatement(standard)) {
185+
statement.setInt(1, 1);
186+
statement.setString(2, "value-1");
187+
ExceptionAssert.ydbException(errorMessage, statement::execute);
188+
}
189+
190+
try (PreparedStatement statement = jdbc.connection().prepareStatement(inMemory)) {
191+
statement.setInt(1, 1);
192+
statement.setString(2, "value-1");
193+
ExceptionAssert.ydbException(errorMessage, statement::execute);
194+
}
195+
196+
ExceptionAssert.ydbException(errorMessage, () -> jdbc.connection().prepareStatement(typed));
197+
ExceptionAssert.ydbException(errorMessage, () -> jdbc.connection().prepareStatement(batched));
198+
ExceptionAssert.sqlException("Cannot parse BULK upsert: column " + columnName + " not found",
199+
() -> jdbc.connection().prepareStatement(bulk));
200+
};
201+
202+
@ParameterizedTest(name = "with {0}")
203+
@ValueSource(strings = { "unknown_table"/*, "YDB_PREPARED_TEST", "ydD_prepared_test"*/ })
204+
public void executeWithWrongTableName(String tableName) throws SQLException {
205+
String errorMessage = "Cannot find table 'db.[" + jdbc.database() + "/" + tableName + "]";
206+
SqlQueries queries = new SqlQueries(tableName);
207+
208+
String standard = queries.upsertOne(SqlQueries.JdbcQuery.STANDARD, "c_Text", "Text");
209+
String inMemory = queries.upsertOne(SqlQueries.JdbcQuery.IN_MEMORY, "c_Text", "Text");
210+
String typed = queries.upsertOne(SqlQueries.JdbcQuery.TYPED, "c_Text", "Text");
211+
String batched = queries.upsertOne(SqlQueries.JdbcQuery.BATCHED, "c_Text", "Text");
212+
String bulk = queries.upsertOne(SqlQueries.JdbcQuery.BULK, "c_Text", "Text");
213+
214+
try (PreparedStatement statement = jdbc.connection().prepareStatement(standard)) {
215+
statement.setInt(1, 1);
216+
statement.setString(2, "value-1");
217+
ExceptionAssert.ydbException(errorMessage, statement::execute);
218+
}
219+
220+
try (PreparedStatement statement = jdbc.connection().prepareStatement(inMemory)) {
221+
statement.setInt(1, 1);
222+
statement.setString(2, "value-1");
223+
ExceptionAssert.ydbException(errorMessage, statement::execute);
224+
}
225+
226+
ExceptionAssert.ydbException(errorMessage, () -> jdbc.connection().prepareStatement(typed));
227+
ExceptionAssert.ydbException(errorMessage, () -> jdbc.connection().prepareStatement(batched));
228+
ExceptionAssert.sqlException("Cannot parse BULK upsert: Status{code = SCHEME_ERROR(code=400070)}",
229+
() -> jdbc.connection().prepareStatement(bulk));
230+
};
231+
153232
@ParameterizedTest(name = "with {0}")
154233
@EnumSource(SqlQueries.JdbcQuery.class)
155234
public void simpleUpsertTest(SqlQueries.JdbcQuery query) throws SQLException {

jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementWithDataQueryBatchedImplTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ public void executeEmpty() throws SQLException {
130130
@Test
131131
public void executeEmptyNoResultSet() throws SQLException {
132132
try (YdbPreparedStatement statement = prepareBatchUpsert("c_Text", "Optional<Text>")) {
133-
statement.setInt("key", 1); // key is requiered value
133+
statement.setInt("key", 1);
134+
statement.setString("c_Text", "value-1");
134135
ExceptionAssert.sqlException("Query must return ResultSet", statement::executeQuery);
135136
}
136137
jdbc.connection().commit();

jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementWithDataQueryImplTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public void executeWrongParameters() throws SQLException {
119119
String sql = upsertSql("c_Text", "Text"); // Must be Text?
120120
try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(sql)) {
121121
statement.setInt("key", 1);
122-
ExceptionAssert.sqlDataException("Missing value for parameter", statement::execute);
122+
ExceptionAssert.sqlDataException("Missing value for parameter: $c_Text", statement::execute);
123123
}
124124

125125
try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(sql)) {

jdbc/src/test/java/tech/ydb/jdbc/impl/YdbTablePreparedStatementImplTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,10 @@ public void addAndClearBatch(SqlQueries.YqlQuery mode) throws SQLException {
249249
@EnumSource(SqlQueries.YqlQuery.class)
250250
public void executeEmptyBatch(SqlQueries.YqlQuery mode) throws SQLException {
251251
String yql = TEST_TABLE.upsertOne(mode, "c_Text", "Text");
252+
String prm = mode == SqlQueries.YqlQuery.SIMPLE ? "$key" : "$c_Text";
252253
try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(yql)) {
253-
ExceptionAssert.sqlDataException("Missing value for parameter", () -> statement.execute());
254-
ExceptionAssert.sqlDataException("Missing value for parameter", () -> statement.executeUpdate());
254+
ExceptionAssert.sqlDataException("Missing value for parameter: " + prm, statement::execute);
255+
ExceptionAssert.sqlDataException("Missing value for parameter: " + prm, statement::executeUpdate);
255256
statement.executeBatch();
256257
}
257258

jdbc/src/test/java/tech/ydb/jdbc/impl/helper/ExceptionAssert.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ public static void sqlDataException(String message, Executable exec) {
2929
SQLDataException ex = Assertions.assertThrows(SQLDataException.class, exec,
3030
"Invalid statement must throw SQLDataException"
3131
);
32-
Assertions.assertTrue(ex.getMessage().contains(message),
33-
"SQLDataException '" + ex.getMessage() + "' doesn't contain message '" + message + "'");
32+
Assertions.assertEquals(message, ex.getMessage());
33+
// Assertions.assertTrue(ex.getMessage().contains(message),
34+
// "SQLDataException '" + ex.getMessage() + "' doesn't contain message '" + message + "'");
3435
}
3536

3637
public static void sqlRecoverable(String message, Executable exec) {

0 commit comments

Comments
 (0)