Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix batch params #33

Merged
merged 4 commits into from
Nov 30, 2023
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
46 changes: 40 additions & 6 deletions jdbc/src/main/java/tech/ydb/jdbc/impl/params/BatchedParams.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import tech.ydb.jdbc.YdbConst;
import tech.ydb.jdbc.common.TypeDescription;
Expand Down Expand Up @@ -37,14 +41,44 @@ private BatchedParams(String listName, StructType structType) {
this.paramsByName = new HashMap<>();
this.params = new ParamDescription[structType.getMembersCount()];

Map<String, Type> types = new HashMap<>();
for (int idx = 0; idx < structType.getMembersCount(); idx += 1) {
String paramName = structType.getMemberName(idx);
String displayName = YdbConst.VARIABLE_PARAMETER_PREFIX + paramName;
TypeDescription type = TypeDescription.of(structType.getMemberType(idx));
types.put(structType.getMemberName(idx), structType.getMemberType(idx));
}

// Firstly put all indexed params (p1, p2, ..., pN) in correct places of paramNames
Set<String> indexedNames = new HashSet<>();
for (int idx = 0; idx < structType.getMembersCount(); idx += 1) {
String indexedName = YdbConst.INDEXED_PARAMETER_PREFIX + (1 + idx);
if (types.containsKey(indexedName)) {
String displayName = YdbConst.VARIABLE_PARAMETER_PREFIX + indexedName;
TypeDescription typeDesc = TypeDescription.of(types.get(indexedName));
ParamDescription paramDesc = new ParamDescription(idx, indexedName, displayName, typeDesc);

params[idx] = paramDesc;
paramsByName.put(indexedName, paramDesc);
indexedNames.add(indexedName);
}
}

// Then put all others params in free places of paramNames in alphabetic order
Iterator<String> sortedIter = new TreeSet<>(types.keySet()).iterator();
for (int idx = 0; idx < params.length; idx += 1) {
if (params[idx] != null) {
continue;
}

String param = sortedIter.next();
while (indexedNames.contains(param)) {
param = sortedIter.next();
}

String displayName = YdbConst.VARIABLE_PARAMETER_PREFIX + param;
TypeDescription typeDesc = TypeDescription.of(types.get(param));
ParamDescription paramDesc = new ParamDescription(idx, param, displayName, typeDesc);

ParamDescription param = new ParamDescription(idx, paramName, displayName, type);
params[idx] = param;
paramsByName.put(paramName, param);
params[idx] = paramDesc;
paramsByName.put(param, paramDesc);
}
}

Expand Down
3 changes: 1 addition & 2 deletions jdbc/src/test/java/tech/ydb/jdbc/YdbDriverExampleTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,7 @@ public void testYdb() throws SQLException {
public void testYdbNotNull() throws SQLException {
try (Connection connection = DriverManager.getConnection(jdbcURL())) {
try {
connection.createStatement()
.execute("drop table table_sample");
connection.createStatement().execute("drop table table_sample");
} catch (SQLException e) {
//
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,6 @@ public void testAnsiLexerForIdea() throws SQLException {
@DisplayName("Check unsupported by storage type {arguments}")
@ParameterizedTest()
@ValueSource(strings = {
"Uuid",
"TzDate",
"TzDatetime",
"TzTimestamp",
Expand Down
158 changes: 147 additions & 11 deletions jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementTest.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
package tech.ydb.jdbc.impl;

import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import java.sql.Timestamp;
import java.sql.Types;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;

import org.junit.Assert;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
Expand All @@ -31,6 +41,13 @@ public class YdbPreparedStatementTest {

private static final SqlQueries TEST_TABLE = new SqlQueries("ydb_prepared_test");

private static final Instant INSTANT = Instant.ofEpochMilli(1585932011123l); // Friday, April 3, 2020 4:40:11.123 PM

// remove time part from instant in UTC
private static Instant calcStartDayUTC(Instant instant) {
return instant.atOffset(ZoneOffset.UTC).toLocalDate().atStartOfDay().toInstant(ZoneOffset.UTC);
}

@BeforeAll
public static void createTable() throws SQLException {
try (Statement statement = jdbc.connection().createStatement();) {
Expand All @@ -46,17 +63,12 @@ public static void dropTable() throws SQLException {
}
}

@AfterEach
public void afterEach() throws SQLException {
if (jdbc.connection().isClosed()) {
return;
}

try (Statement statement = jdbc.connection().createStatement()) {
@BeforeEach
public void beforaEach() throws SQLException {
try (Statement statement = jdbc.connection().createStatement();) {
// clean table
statement.execute(TEST_TABLE.deleteAllSQL());
}

jdbc.connection().close();
}

@ParameterizedTest(name = "with {0}")
Expand Down Expand Up @@ -198,4 +210,128 @@ public void batchUpsertTest(SqlQueries.JdbcQuery query) throws SQLException {
.noNextRows();
}
};

private void fillRowValues(PreparedStatement statement, int id) throws SQLException {
statement.setInt(1, id); // id

statement.setBoolean(2, id % 2 == 0); // c_Bool

statement.setByte(3, (byte)(id + 1)); // c_Int8
statement.setShort(4, (short)(id + 2)); // c_Int16
statement.setInt(5, id + 3); // c_Int32
statement.setLong(6, id + 4); // c_Int64

statement.setByte(7, (byte)(id + 5)); // c_Uint8
statement.setShort(8, (short)(id + 6)); // c_Uint16
statement.setInt(9, id + 7); // c_Uint32
statement.setLong(10, id + 8); // c_Uint64

statement.setFloat(11, 1.5f * id); // c_Float
statement.setDouble(12, 2.5d * id); // c_Double

statement.setBytes(13, new byte[] { (byte)id }); // c_Bytes
statement.setString(14, "Text_" + id); // c_Text
statement.setString(15, "{\"json\": " + id + "}"); // c_Json
statement.setString(16, "{\"jsonDoc\": " + id + "}"); // c_JsonDocument
statement.setString(17, "{yson=" + id + "}"); // c_Yson


Date sqlDate = new Date(calcStartDayUTC(INSTANT.plus(id, ChronoUnit.DAYS)).toEpochMilli());
LocalDateTime dateTime = LocalDateTime.ofInstant(INSTANT, ZoneOffset.UTC).plusMinutes(id);
Timestamp timestamp = Timestamp.from(INSTANT.plusSeconds(id));
Duration duration = Duration.ofMinutes(id);

statement.setDate(18, sqlDate); // c_Date
statement.setObject(19, dateTime); // c_Datetime
statement.setTimestamp(20, timestamp); // c_Timestamp
statement.setObject(21, duration); // c_Interval

statement.setNull(22, Types.DECIMAL); // c_Decimal
}

private void assertRowValues(ResultSet rs, int id) throws SQLException {
Assert.assertTrue(rs.next());

Assert.assertEquals(id, rs.getInt("key"));

Assert.assertEquals(id % 2 == 0, rs.getBoolean("c_Bool"));

Assert.assertEquals(id + 1, rs.getByte("c_Int8"));
Assert.assertEquals(id + 2, rs.getShort("c_Int16"));
Assert.assertEquals(id + 3, rs.getInt("c_Int32"));
Assert.assertEquals(id + 4, rs.getLong("c_Int64"));

Assert.assertEquals(id + 5, rs.getByte("c_Uint8"));
Assert.assertEquals(id + 6, rs.getShort("c_Uint16"));
Assert.assertEquals(id + 7, rs.getInt("c_Uint32"));
Assert.assertEquals(id + 8, rs.getLong("c_Uint64"));

Assert.assertEquals(1.5f * id, rs.getFloat("c_Float"), 0.001f);
Assert.assertEquals(2.5d * id, rs.getDouble("c_Double"), 0.001d);

Assert.assertArrayEquals(new byte[] { (byte)id }, rs.getBytes("c_Bytes"));
Assert.assertEquals("Text_" + id, rs.getString("c_Text"));
Assert.assertEquals("{\"json\": " + id + "}", rs.getString("c_Json"));
Assert.assertEquals("{\"jsonDoc\":" + id + "}", rs.getString("c_JsonDocument"));
Assert.assertEquals("{yson=" + id + "}", rs.getString("c_Yson"));


Date sqlDate = new Date(calcStartDayUTC(INSTANT.plus(id, ChronoUnit.DAYS)).toEpochMilli());
LocalDateTime dateTime = LocalDateTime.ofInstant(INSTANT, ZoneOffset.UTC).plusMinutes(id)
.truncatedTo(ChronoUnit.SECONDS);
Timestamp timestamp = Timestamp.from(INSTANT.plusSeconds(id));
Duration duration = Duration.ofMinutes(id);

Date rsDate = rs.getDate("c_Date");

Assert.assertEquals(sqlDate, rsDate);
Assert.assertEquals(dateTime, rs.getObject("c_Datetime"));
Assert.assertEquals(timestamp, rs.getTimestamp("c_Timestamp"));
Assert.assertEquals(duration, rs.getObject("c_Interval"));

Assert.assertNull(rs.getString("c_Decimal"));
Assert.assertTrue(rs.wasNull());
}

@ParameterizedTest(name = "with {0}")
@EnumSource(SqlQueries.JdbcQuery.class)
public void batchUpsertAllTest(SqlQueries.JdbcQuery query) throws SQLException {
String upsert = TEST_TABLE.upsertAll(query);

try (PreparedStatement statement = jdbc.connection().prepareStatement(upsert)) {
// ----- base usage -----
fillRowValues(statement, 1);
statement.addBatch();

fillRowValues(statement, 2);
statement.addBatch();

statement.executeBatch();

// ----- executeBatch without addBatch -----
fillRowValues(statement, 3);
statement.addBatch();

fillRowValues(statement, 4);
statement.executeBatch();

// ----- execute instead of executeBatch -----
fillRowValues(statement, 5);
statement.addBatch();

fillRowValues(statement, 6);
statement.execute();
}

try (Statement statement = jdbc.connection().createStatement()) {
try (ResultSet rs = statement.executeQuery(TEST_TABLE.selectSQL())) {
assertRowValues(rs, 1);
assertRowValues(rs, 2);
assertRowValues(rs, 3);
assertRowValues(rs, 6);

Assert.assertFalse(rs.next());
}
}
};
}
30 changes: 18 additions & 12 deletions jdbc/src/test/java/tech/ydb/jdbc/impl/helper/SqlQueries.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ public enum YqlQuery {

private static final String SELECT = YdbLookup.stringFileReference("classpath:sql/select.sql");

private static final String SIMPLE_UPSERT_ALL = YdbLookup.stringFileReference("classpath:sql/upsert/simple.sql");
private static final String NAMED_UPSERT_ALL = YdbLookup.stringFileReference("classpath:sql/upsert/named.sql");
private static final String BATCHED_UPSERT_ALL = YdbLookup.stringFileReference("classpath:sql/upsert/batched.sql");
private static final String INDEXED_UPSERT_ALL = YdbLookup.stringFileReference("classpath:sql/upsert/types.sql");
private static final String SIMPLE_UPSERT = YdbLookup.stringFileReference("classpath:sql/upsert/simple.sql");
private static final String NAMED_UPSERT = YdbLookup.stringFileReference("classpath:sql/upsert/named.sql");
private static final String TYPED_UPSERT = YdbLookup.stringFileReference("classpath:sql/upsert/typed.sql");

private static final String NAMED_BATCH = YdbLookup.stringFileReference("classpath:sql/upsert/named_batch.sql");
private static final String TYPED_BATCH = YdbLookup.stringFileReference("classpath:sql/upsert/typed_batch.sql");

private static final String SELECT_ALL = "select * from #tableName";
private static final String DELETE_ALL = "delete from #tableName";
Expand Down Expand Up @@ -109,20 +111,24 @@ public String deleteAllSQL() {
public String namedUpsertAll(YqlQuery mode) {
switch (mode) {
case BATCHED:
return withTableName(BATCHED_UPSERT_ALL, tableName);
return withTableName(NAMED_BATCH, tableName);
case SIMPLE:
default:
return withTableName(NAMED_UPSERT_ALL, tableName);
return withTableName(NAMED_UPSERT, tableName);

}
}

public String simpleUpsertAllSQL() {
return withTableName(SIMPLE_UPSERT_ALL, tableName);
}

public String indexesUpsertAllSQL() {
return withTableName(INDEXED_UPSERT_ALL, tableName);
public String upsertAll(JdbcQuery mode) {
switch (mode) {
case BATCHED:
return withTableName(TYPED_BATCH, tableName);
case TYPED:
return withTableName(TYPED_UPSERT, tableName);
case STANDART:
default:
return withTableName(SIMPLE_UPSERT, tableName);
}
}

/**
Expand Down
35 changes: 30 additions & 5 deletions jdbc/src/test/resources/sql/upsert/simple.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
upsert into #tableName (
UPSERT INTO #tableName (
key,

c_Bool,
Expand Down Expand Up @@ -28,8 +28,33 @@ upsert into #tableName (
c_Interval,

c_Decimal
) values (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ? ,? ,?,
?, ?
) VALUES (
?, -- key
?, -- c_Bool

CAST(? AS Int8), -- c_Int8
CAST(? AS Int16), -- c_Int16
?, -- c_Int32
?, -- c_Int64

CAST(? AS Uint8), -- c_Uint8
CAST(? AS Uint16), -- c_Uint16
CAST(? AS Uint32), -- c_Uint32
CAST(? AS Uint64), -- c_Uint64

?, -- c_Float
?, -- c_Double

?, -- c_Bytes
?, -- c_Text
CAST(? AS Json), -- c_Json
CAST(? AS JsonDocument), -- c_JsonDocument
CAST(? AS Yson), -- c_Yson

?, -- c_Date
?, -- c_Datetime
?, -- c_Timestamp
?, -- c_Interval

? -- c_Decimal
)
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,5 @@ upsert into #tableName (
$p19,
$p20,
$p21,
$p22,
$p22
)
Loading