diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 8a17b07..4664f07 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -7,7 +7,7 @@ on: - develop - release* pull_request: - type: [opened, reopened, edited] + type: [opened, reopened, edited, synchronize] jobs: build: @@ -152,3 +152,5 @@ jobs: - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e55c50..a3dc79e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.1.0 ## + +* Added support for QueryService +* Added configs for custom iam enpoint and metadata URL +* Upgraded to YDB Java SDK 2.2.0 + ## 2.0.7 ## * Added getter for GrpcTransport to YdbContext diff --git a/README.md b/README.md index 0970277..aa1f481 100644 --- a/README.md +++ b/README.md @@ -25,14 +25,14 @@ Specify the YDB JDBC driver in the dependencies: tech.ydb.jdbc ydb-jdbc-driver - 2.0.7 + 2.1.0 tech.ydb.jdbc ydb-jdbc-driver-shaded - 2.0.7 + 2.1.0 ``` @@ -50,8 +50,10 @@ YDB JDBC Driver supports the following [authentication modes](https://ydb.tech/e Driver supports the following configuration properties, which can be specified in the URL or passed via extra properties: * `saFile` - service account key for authentication, can be passed either as literal JSON value or as a file reference; +* `iamEndpoint` - custom IAM endpoint for authentication via service account key; * `token` - token value for authentication, can be passed either as literal value or as a file reference; * `useMetadata` - boolean value, true if metadata authentication should be used, false otherwise (and default); +* `metadataURL` - custom metadata endpoint; * `localDatacenter` - name of the datacenter local to the application being connected; * `secureConnection` - boolean value, true if TLS should be enforced (normally configured via `grpc://` or `grpcs://` scheme in the JDBC URL); * `secureConnectionCertificate` - custom CA certificate for TLS connections, can be passed either as literal value or as a file reference. diff --git a/config/ydb.checkstyle.xml b/config/ydb.checkstyle.xml new file mode 100644 index 0000000..ab26539 --- /dev/null +++ b/config/ydb.checkstyle.xml @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/ydb.suppressions.xml b/config/ydb.suppressions.xml new file mode 100644 index 0000000..fe45ca6 --- /dev/null +++ b/config/ydb.suppressions.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/jdbc-shaded/pom.xml b/jdbc-shaded/pom.xml index 9918411..74af959 100644 --- a/jdbc-shaded/pom.xml +++ b/jdbc-shaded/pom.xml @@ -6,7 +6,7 @@ tech.ydb.jdbc ydb-jdbc-driver-parent - 2.0.7 + 2.1.0 ydb-jdbc-driver-shaded diff --git a/jdbc/pom.xml b/jdbc/pom.xml index 1b0c9ae..49d3988 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -6,7 +6,7 @@ tech.ydb.jdbc ydb-jdbc-driver-parent - 2.0.7 + 2.1.0 ydb-jdbc-driver @@ -14,6 +14,10 @@ JDBC Driver over YDB Java SDK + + tech.ydb + ydb-sdk-query + tech.ydb ydb-sdk-table @@ -53,6 +57,10 @@ + + org.apache.maven.plugins + maven-checkstyle-plugin + org.apache.maven.plugins maven-source-plugin diff --git a/jdbc/src/main/java/tech/ydb/jdbc/YdbConnection.java b/jdbc/src/main/java/tech/ydb/jdbc/YdbConnection.java index 4e6b901..b65dab4 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/YdbConnection.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/YdbConnection.java @@ -2,17 +2,16 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.List; import javax.annotation.Nullable; -import tech.ydb.jdbc.query.YdbQuery; import tech.ydb.jdbc.context.YdbContext; -import tech.ydb.jdbc.context.YdbExecutor; -import tech.ydb.table.query.DataQueryResult; +import tech.ydb.jdbc.context.YdbValidator; +import tech.ydb.jdbc.query.YdbQuery; import tech.ydb.table.query.ExplainDataQueryResult; import tech.ydb.table.query.Params; import tech.ydb.table.result.ResultSetReader; -import tech.ydb.table.settings.ExecuteDataQuerySettings; public interface YdbConnection extends Connection { @@ -38,44 +37,46 @@ public interface YdbConnection extends Connection { * Explicitly execute query as a schema query * * @param query query (DDL) to execute - * @param executor executor for logging and warnings + * @param validator handler for logging and warnings * @throws SQLException if query cannot be executed */ - void executeSchemeQuery(YdbQuery query, YdbExecutor executor) throws SQLException; + void executeSchemeQuery(YdbQuery query, YdbValidator validator) throws SQLException; /** * Explicitly execute query as a data query * * @param query query to execute * @param params parameters for query - * @param settings settings of execution - * @param executor executor for logging and warnings + * @param timeout timeout of operation + * @param keepInCache flag to store query in server-side cache + * @param validator handler for logging and warnings * @return list of result set * @throws SQLException if query cannot be executed */ - DataQueryResult executeDataQuery(YdbQuery query, YdbExecutor executor, ExecuteDataQuerySettings settings, Params params) throws SQLException; + List executeDataQuery(YdbQuery query, YdbValidator validator, + int timeout, boolean keepInCache, Params params) throws SQLException; /** * Explicitly execute query as a scan query * * @param query query to execute * @param params parameters for query - * @param executor executor for logging and warnings + * @param validator handler for logging and warnings * @return single result set with rows * @throws SQLException if query cannot be executed */ - ResultSetReader executeScanQuery(YdbQuery query, YdbExecutor executor, Params params) throws SQLException; + ResultSetReader executeScanQuery(YdbQuery query, YdbValidator validator, Params params) throws SQLException; /** * Explicitly explain this query * * @param query query to explain - * @param executor executor for logging and warnings + * @param validator handler for logging and warnings * @return list of result set of two string columns: {@link YdbConst#EXPLAIN_COLUMN_AST} * and {@link YdbConst#EXPLAIN_COLUMN_PLAN} * @throws SQLException if query cannot be explained */ - ExplainDataQueryResult executeExplainQuery(YdbQuery query, YdbExecutor executor) throws SQLException; + ExplainDataQueryResult executeExplainQuery(YdbQuery query, YdbValidator validator) throws SQLException; @Override YdbDatabaseMetaData getMetaData() throws SQLException; diff --git a/jdbc/src/main/java/tech/ydb/jdbc/YdbDriver.java b/jdbc/src/main/java/tech/ydb/jdbc/YdbDriver.java index 27c14ff..8058b01 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/YdbDriver.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/YdbDriver.java @@ -12,14 +12,12 @@ import javax.annotation.Nullable; -import tech.ydb.jdbc.context.YdbConfig; -import tech.ydb.jdbc.impl.YdbConnectionImpl; import tech.ydb.jdbc.context.YdbContext; -import tech.ydb.jdbc.settings.YdbJdbcTools; +import tech.ydb.jdbc.impl.YdbConnectionImpl; +import tech.ydb.jdbc.settings.YdbConfig; import tech.ydb.scheme.SchemeClient; import tech.ydb.table.TableClient; -import static tech.ydb.jdbc.YdbConst.JDBC_YDB_PREFIX; /** * YDB JDBC driver, basic implementation supporting {@link TableClient} and {@link SchemeClient} @@ -48,13 +46,13 @@ public YdbConnection connect(String url, Properties info) throws SQLException { return null; } - YdbConfig config = new YdbConfig(url, info); + YdbConfig config = YdbConfig.from(url, info); LOGGER.log(Level.FINE, "About to connect to [{0}] using properties {1}", new Object[] { config.getSafeUrl(), config.getSafeProps() }); - if (config.getOperationProperties().isCacheConnectionsInDriver()) { + if (config.isCacheConnectionsInDriver()) { return new YdbConnectionImpl(getCachedContext(config)); } @@ -74,7 +72,7 @@ public YdbContext getCachedContext(YdbConfig config) throws SQLException { // Was fixed in Java 9+ YdbContext context = cache.get(config); if (context != null) { - LOGGER.log(Level.FINE, "Reusing YDB connection to {0}", config.getConnectionProperties()); + LOGGER.log(Level.FINE, "Reusing YDB connection to {0}", config.getSafeUrl()); return context; } @@ -88,13 +86,13 @@ public YdbContext getCachedContext(YdbConfig config) throws SQLException { @Override public boolean acceptsURL(String url) { - return YdbJdbcTools.isYdb(url); + return YdbConfig.isYdb(url); } @Override public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { - String targetUrl = acceptsURL(url) ? url : JDBC_YDB_PREFIX; - return YdbJdbcTools.from(targetUrl, info).toDriverProperties(); + YdbConfig config = YdbConfig.from(url, info); + return config.toPropertyInfo(); } @Override diff --git a/jdbc/src/main/java/tech/ydb/jdbc/YdbDriverInfo.java b/jdbc/src/main/java/tech/ydb/jdbc/YdbDriverInfo.java index 62901e5..744f90b 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/YdbDriverInfo.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/YdbDriverInfo.java @@ -11,4 +11,6 @@ public final class YdbDriverInfo { public static final String DRIVER_FULL_NAME = DRIVER_NAME + " " + DRIVER_VERSION; public static final int JDBC_MAJOR_VERSION = 4; public static final int JDBC_MINOR_VERSION = 2; + + private YdbDriverInfo() { } } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/common/FixedResultSetFactory.java b/jdbc/src/main/java/tech/ydb/jdbc/common/FixedResultSetFactory.java index 4bccb52..ab228eb 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/common/FixedResultSetFactory.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/common/FixedResultSetFactory.java @@ -127,7 +127,7 @@ private class FixedResultSet implements ResultSetReader { private final List> rows; private int rowIndex = 0; - public FixedResultSet(List> rows) { + FixedResultSet(List> rows) { this.rows = rows; } @@ -190,7 +190,7 @@ private static class Column { private final String name; private final Type type; - public Column(String name, Type type) { + Column(String name, Type type) { this.name = name; this.type = type; } @@ -237,7 +237,7 @@ private static class FixedValueReader implements ValueReader { private final PrimitiveValue value; private final Type type; - public FixedValueReader(PrimitiveValue value, Type type) { + FixedValueReader(PrimitiveValue value, Type type) { this.value = value; this.type = type; } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/common/JdbcDriverVersion.java b/jdbc/src/main/java/tech/ydb/jdbc/common/JdbcDriverVersion.java index c86b851..f3d9c60 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/common/JdbcDriverVersion.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/common/JdbcDriverVersion.java @@ -32,12 +32,12 @@ public String getFullVersion() { } public static JdbcDriverVersion getInstance() { - return Holder.instance; + return Holder.INSTANCE; } private static class Holder { - private final static String PROPERTIES_PATH = "/ydb_jdbc.properties"; - private final static JdbcDriverVersion instance; + private static final String PROPERTIES_PATH = "/ydb_jdbc.properties"; + private static final JdbcDriverVersion INSTANCE; static { int major = -1; @@ -60,7 +60,7 @@ private static class Holder { } catch (RuntimeException e) { } } - instance = new JdbcDriverVersion(version, major, minor); + INSTANCE = new JdbcDriverVersion(version, major, minor); } } } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/common/MappingGetters.java b/jdbc/src/main/java/tech/ydb/jdbc/common/MappingGetters.java index 2040c17..7b4fd7c 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/common/MappingGetters.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/common/MappingGetters.java @@ -16,10 +16,6 @@ import java.util.Objects; import java.util.concurrent.TimeUnit; -import javax.annotation.Nullable; - -import com.google.common.base.Preconditions; - import tech.ydb.jdbc.impl.YdbTypesImpl; import tech.ydb.table.result.PrimitiveReader; import tech.ydb.table.result.ValueReader; @@ -30,159 +26,205 @@ import static tech.ydb.jdbc.YdbConst.UNABLE_TO_CAST; import static tech.ydb.jdbc.YdbConst.UNABLE_TO_CONVERT; +import static tech.ydb.table.values.PrimitiveType.Bool; +import static tech.ydb.table.values.PrimitiveType.Bytes; +import static tech.ydb.table.values.PrimitiveType.Date; +import static tech.ydb.table.values.PrimitiveType.Datetime; +import static tech.ydb.table.values.PrimitiveType.Double; +import static tech.ydb.table.values.PrimitiveType.Float; import static tech.ydb.table.values.PrimitiveType.Int16; import static tech.ydb.table.values.PrimitiveType.Int32; +import static tech.ydb.table.values.PrimitiveType.Int64; +import static tech.ydb.table.values.PrimitiveType.Int8; +import static tech.ydb.table.values.PrimitiveType.Interval; +import static tech.ydb.table.values.PrimitiveType.Json; +import static tech.ydb.table.values.PrimitiveType.JsonDocument; +import static tech.ydb.table.values.PrimitiveType.Text; +import static tech.ydb.table.values.PrimitiveType.Timestamp; +import static tech.ydb.table.values.PrimitiveType.TzDate; +import static tech.ydb.table.values.PrimitiveType.TzDatetime; +import static tech.ydb.table.values.PrimitiveType.TzTimestamp; +import static tech.ydb.table.values.PrimitiveType.Uint16; import static tech.ydb.table.values.PrimitiveType.Uint32; +import static tech.ydb.table.values.PrimitiveType.Uint64; +import static tech.ydb.table.values.PrimitiveType.Uint8; +import static tech.ydb.table.values.PrimitiveType.Uuid; +import static tech.ydb.table.values.PrimitiveType.Yson; +import static tech.ydb.table.values.Type.Kind.PRIMITIVE; public class MappingGetters { + private MappingGetters() { } static Getters buildGetters(Type type) { Type.Kind kind = type.getKind(); - @Nullable PrimitiveType id = type.getKind() == Type.Kind.PRIMITIVE ? ((PrimitiveType) type) : null; - return new Getters( - valueToString(kind, id), - valueToBoolean(kind, id), - valueToByte(kind, id), - valueToShort(kind, id), - valueToInt(kind, id), - valueToLong(kind, id), - valueToFloat(kind, id), - valueToDouble(kind, id), - valueToBytes(kind, id), - valueToObject(kind, id), - valueToDateMillis(kind, id), - valueToNString(kind, id), - valueToURL(kind, id), - valueToBigDecimal(kind, id), - valueToReader(kind, id)); - } - - private static ValueToString valueToString(Type.Kind kind, @Nullable PrimitiveType id) { - Class javaType = String.class; - if (kind == Type.Kind.PRIMITIVE) { - Preconditions.checkState(id != null, "Primitive type must not be null when kind is %s", kind); - switch (id) { - case Bytes: - return value -> new String(value.getBytes()); - case Text: - return PrimitiveReader::getText; - case Json: - return PrimitiveReader::getJson; - case JsonDocument: - return PrimitiveReader::getJsonDocument; - case Yson: - return value -> new String(value.getYson()); - case Uuid: - return value -> String.valueOf(value.getUuid()); - case Bool: - return value -> String.valueOf(value.getBool()); - case Int8: - return value -> String.valueOf(value.getInt8()); - case Uint8: - return value -> String.valueOf(value.getUint8()); - case Int16: - return value -> String.valueOf(value.getInt16()); - case Uint16: - return value -> String.valueOf(value.getUint16()); - case Int32: - return value -> String.valueOf(value.getInt32()); - case Uint32: - return value -> String.valueOf(value.getUint32()); - case Int64: - return value -> String.valueOf(value.getInt64()); - case Uint64: - return value -> String.valueOf(value.getUint64()); - case Float: - return value -> String.valueOf(value.getFloat()); - case Double: - return value -> String.valueOf(value.getDouble()); - case Date: - return value -> String.valueOf(value.getDate()); - case Datetime: - return value -> String.valueOf(value.getDatetime()); - case Timestamp: - return value -> String.valueOf(value.getTimestamp()); - case Interval: - return value -> String.valueOf(value.getInterval()); - case TzDate: - return value -> String.valueOf(value.getTzDate()); - case TzDatetime: - return value -> String.valueOf(value.getTzDatetime()); - case TzTimestamp: - return value -> String.valueOf(value.getTzTimestamp()); - default: - // DyNumber - return value -> { - throw dataTypeNotSupported(id, javaType); - }; - } - } else if (kind == Type.Kind.DECIMAL) { - return value -> String.valueOf(value.getDecimal()); - } else { - return value -> String.valueOf(value.getValue()); - } - } - - private static ValueToBoolean valueToBoolean(Type.Kind kind, @Nullable PrimitiveType id) { - Class javaType = boolean.class; - if (kind == Type.Kind.PRIMITIVE) { - Preconditions.checkState(id != null, "Primitive type must not be null when kind is %s", kind); - switch (id) { - case Bool: - return PrimitiveReader::getBool; - case Int8: - return value -> value.getInt8() != 0; - case Uint8: - return value -> value.getUint8() != 0; - case Int16: - return value -> value.getInt16() != 0; - case Uint16: - return value -> value.getUint16() != 0; - case Int32: - return value -> value.getInt32() != 0; - case Uint32: - return value -> value.getUint32() != 0; - case Int64: - return value -> value.getInt64() != 0; - case Uint64: - return value -> value.getUint64() != 0; - case Bytes: - return value -> { - byte[] stringValue = value.getBytes(); - if (stringValue.length == 0) { + String clazz = kind.toString(); + switch (kind) { + case PRIMITIVE: + PrimitiveType id = (PrimitiveType) type; + return new Getters( + valueToString(id), + valueToBoolean(id), + valueToByte(id), + valueToShort(id), + valueToInt(id), + valueToLong(id), + valueToFloat(id), + valueToDouble(id), + valueToBytes(id), + valueToObject(id), + valueToDateMillis(id), + valueToNString(id), + valueToURL(id), + valueToBigDecimal(id), + valueToReader(id) + ); + case DECIMAL: + return new Getters( + value -> String.valueOf(value.getDecimal()), + castToBooleanNotSupported(clazz), + castToByteNotSupported(clazz), + castToShortNotSupported(clazz), + value -> value.getDecimal().toBigInteger().intValue(), + value -> value.getDecimal().toBigInteger().longValue(), + value -> value.getDecimal().toBigDecimal().floatValue(), + value -> value.getDecimal().toBigDecimal().doubleValue(), + castToBytesNotSupported(clazz), + PrimitiveReader::getDecimal, + castToDateMillisNotSupported(clazz), + castToNStringNotSupported(clazz), + castToUrlNotSupported(clazz), + value -> value.getDecimal().toBigDecimal(), + castToReaderNotSupported(clazz) + ); + default: + return new Getters( + value -> String.valueOf(value.getValue()), + castToBooleanNotSupported(clazz), + castToByteNotSupported(clazz), + castToShortNotSupported(clazz), + castToIntNotSupported(clazz), + castToLongNotSupported(clazz), + castToFloatNotSupported(clazz), + castToDoubleNotSupported(clazz), + castToBytesNotSupported(clazz), + ValueReader::getValue, + castToDateMillisNotSupported(clazz), + castToNStringNotSupported(clazz), + castToUrlNotSupported(clazz), + castToBigDecimalNotSupported(clazz), + castToReaderNotSupported(clazz) + ); + } + } + + private static ValueToString valueToString(PrimitiveType id) { + switch (id) { + case Bytes: + return value -> new String(value.getBytes()); + case Text: + return PrimitiveReader::getText; + case Json: + return PrimitiveReader::getJson; + case JsonDocument: + return PrimitiveReader::getJsonDocument; + case Yson: + return value -> new String(value.getYson()); + case Uuid: + return value -> String.valueOf(value.getUuid()); + case Bool: + return value -> String.valueOf(value.getBool()); + case Int8: + return value -> String.valueOf(value.getInt8()); + case Uint8: + return value -> String.valueOf(value.getUint8()); + case Int16: + return value -> String.valueOf(value.getInt16()); + case Uint16: + return value -> String.valueOf(value.getUint16()); + case Int32: + return value -> String.valueOf(value.getInt32()); + case Uint32: + return value -> String.valueOf(value.getUint32()); + case Int64: + return value -> String.valueOf(value.getInt64()); + case Uint64: + return value -> String.valueOf(value.getUint64()); + case Float: + return value -> String.valueOf(value.getFloat()); + case Double: + return value -> String.valueOf(value.getDouble()); + case Date: + return value -> String.valueOf(value.getDate()); + case Datetime: + return value -> String.valueOf(value.getDatetime()); + case Timestamp: + return value -> String.valueOf(value.getTimestamp()); + case Interval: + return value -> String.valueOf(value.getInterval()); + case TzDate: + return value -> String.valueOf(value.getTzDate()); + case TzDatetime: + return value -> String.valueOf(value.getTzDatetime()); + case TzTimestamp: + return value -> String.valueOf(value.getTzTimestamp()); + default: + // DyNumber + return value -> { + throw dataTypeNotSupported(id, String.class); + }; + } + } + + private static ValueToBoolean valueToBoolean(PrimitiveType id) { + switch (id) { + case Bool: + return PrimitiveReader::getBool; + case Int8: + return value -> value.getInt8() != 0; + case Uint8: + return value -> value.getUint8() != 0; + case Int16: + return value -> value.getInt16() != 0; + case Uint16: + return value -> value.getUint16() != 0; + case Int32: + return value -> value.getInt32() != 0; + case Uint32: + return value -> value.getUint32() != 0; + case Int64: + return value -> value.getInt64() != 0; + case Uint64: + return value -> value.getUint64() != 0; + case Bytes: + return value -> { + byte[] stringValue = value.getBytes(); + if (stringValue.length == 0) { + return false; + } else if (stringValue.length == 1) { + if (stringValue[0] == '0') { return false; - } else if (stringValue.length == 1) { - if (stringValue[0] == '0') { - return false; - } else if (stringValue[0] == '1') { - return true; - } + } else if (stringValue[0] == '1') { + return true; } - throw cannotConvert(id, javaType, new String(stringValue)); - }; - case Text: - return value -> { - String utfValue = value.getText(); - if (utfValue.isEmpty()) { + } + throw cannotConvert(id, boolean.class, new String(stringValue)); + }; + case Text: + return value -> { + String utfValue = value.getText(); + if (utfValue.isEmpty()) { + return false; + } else if (utfValue.length() == 1) { + if (utfValue.charAt(0) == '0') { return false; - } else if (utfValue.length() == 1) { - if (utfValue.charAt(0) == '0') { - return false; - } else if (utfValue.charAt(0) == '1') { - return true; - } + } else if (utfValue.charAt(0) == '1') { + return true; } - throw cannotConvert(id, javaType, utfValue); - }; - default: - return value -> { - throw dataTypeNotSupported(id, javaType); - }; - } - } else { - return value -> { - throw dataTypeNotSupported(kind, javaType); - }; + } + throw cannotConvert(id, boolean.class, utfValue); + }; + default: + return castToBooleanNotSupported(id.name()); } } @@ -191,48 +233,39 @@ private static byte checkByteValue(PrimitiveType id, int value) throws SQLExcept if ((ch & 0x7F) != ch) { throw cannotConvert(id, byte.class, value); } - return (byte)value; + return (byte) value; } private static byte checkByteValue(PrimitiveType id, long value) throws SQLException { long ch = value >= 0 ? value : ~value; - if ((ch & 0x7Fl) != ch) { + if ((ch & 0x7FL) != ch) { throw cannotConvert(id, byte.class, value); } - return (byte)value; - } - - private static ValueToByte valueToByte(Type.Kind kind, PrimitiveType id) { - if (kind == Type.Kind.PRIMITIVE) { - Preconditions.checkState(id != null, "Primitive type must not be null when kind is %s", kind); - switch (id) { - case Bool: - return value -> checkByteValue(id, value.getBool() ? 1 : 0); - case Int8: - return PrimitiveReader::getInt8; - case Int16: - return value -> checkByteValue(id, value.getInt16()); - case Int32: - return value -> checkByteValue(id, value.getInt32()); - case Int64: - return value -> checkByteValue(id, value.getInt64()); - case Uint8: - return value -> checkByteValue(id, value.getUint8()); - case Uint16: - return value -> checkByteValue(id, value.getUint16()); - case Uint32: - return value -> checkByteValue(id, value.getUint32()); - case Uint64: - return value -> checkByteValue(id, value.getUint64()); - default: - return value -> { - throw dataTypeNotSupported(id, byte.class); - }; - } - } else { - return value -> { - throw dataTypeNotSupported(kind, byte.class); - }; + return (byte) value; + } + + private static ValueToByte valueToByte(PrimitiveType id) { + switch (id) { + case Bool: + return value -> checkByteValue(id, value.getBool() ? 1 : 0); + case Int8: + return PrimitiveReader::getInt8; + case Int16: + return value -> checkByteValue(id, value.getInt16()); + case Int32: + return value -> checkByteValue(id, value.getInt32()); + case Int64: + return value -> checkByteValue(id, value.getInt64()); + case Uint8: + return value -> checkByteValue(id, value.getUint8()); + case Uint16: + return value -> checkByteValue(id, value.getUint16()); + case Uint32: + return value -> checkByteValue(id, value.getUint32()); + case Uint64: + return value -> checkByteValue(id, value.getUint64()); + default: + return castToByteNotSupported(id.name()); } } @@ -241,545 +274,405 @@ private static short checkShortValue(PrimitiveType id, int value) throws SQLExce if ((ch & 0x7FFF) != ch) { throw cannotConvert(id, short.class, value); } - return (short)value; + return (short) value; } private static short checkShortValue(PrimitiveType id, long value) throws SQLException { long ch = value >= 0 ? value : ~value; - if ((ch & 0x7FFFl) != ch) { + if ((ch & 0x7FFFL) != ch) { throw cannotConvert(id, short.class, value); } - return (short)value; - } - - private static ValueToShort valueToShort(Type.Kind kind, PrimitiveType id) { - if (kind == Type.Kind.PRIMITIVE) { - Preconditions.checkState(id != null, "Primitive type must not be null when kind is %s", kind); - switch (id) { - case Bool: - return value -> checkShortValue(id, value.getBool() ? 1 : 0); - case Int8: - return PrimitiveReader::getInt8; - case Int16: - return PrimitiveReader::getInt16; - case Int32: - return value -> checkShortValue(id, value.getInt32()); - case Int64: - return value -> checkShortValue(id, value.getInt64()); - case Uint8: - return value -> checkShortValue(id, value.getUint8()); - case Uint16: - return value -> checkShortValue(id, value.getUint16()); - case Uint32: - return value -> checkShortValue(id, value.getUint32()); - case Uint64: - return value -> checkShortValue(id, value.getUint64()); - default: - return value -> { - throw dataTypeNotSupported(id, short.class); - }; - } - } else { - return value -> { - throw dataTypeNotSupported(kind, short.class); - }; + return (short) value; + } + + private static ValueToShort valueToShort(PrimitiveType id) { + switch (id) { + case Bool: + return value -> checkShortValue(id, value.getBool() ? 1 : 0); + case Int8: + return PrimitiveReader::getInt8; + case Int16: + return PrimitiveReader::getInt16; + case Int32: + return value -> checkShortValue(id, value.getInt32()); + case Int64: + return value -> checkShortValue(id, value.getInt64()); + case Uint8: + return value -> checkShortValue(id, value.getUint8()); + case Uint16: + return value -> checkShortValue(id, value.getUint16()); + case Uint32: + return value -> checkShortValue(id, value.getUint32()); + case Uint64: + return value -> checkShortValue(id, value.getUint64()); + default: + return castToShortNotSupported(id.name()); } } private static int checkIntValue(PrimitiveType id, long value) throws SQLException { long ch = value >= 0 ? value : ~value; - if ((ch & 0x7FFFFFFFl) != ch) { + if ((ch & 0x7FFFFFFFL) != ch) { throw cannotConvert(id, int.class, value); } - return (int)value; - } - - private static ValueToInt valueToInt(Type.Kind kind, PrimitiveType id) { - if (kind == Type.Kind.PRIMITIVE) { - Preconditions.checkState(id != null, "Primitive type must not be null when kind is %s", kind); - switch (id) { - case Bool: - return value -> value.getBool() ? 1 : 0; - case Int8: - return PrimitiveReader::getInt8; - case Int16: - return PrimitiveReader::getInt16; - case Int32: - return PrimitiveReader::getInt32; - case Int64: - return value -> checkIntValue(id, value.getInt64()); - case Uint8: - return PrimitiveReader::getUint8; - case Uint16: - return PrimitiveReader::getUint16; - case Uint32: - return value -> checkIntValue(id, value.getUint32()); - case Uint64: - return value -> checkIntValue(id, value.getUint64()); - default: - return value -> { - throw dataTypeNotSupported(id, int.class); - }; - } - } else if (kind == Type.Kind.DECIMAL) { - return value -> value.getDecimal().toBigInteger().intValue(); // TODO: Improve performance - } else { - return value -> { - throw dataTypeNotSupported(kind, int.class); - }; - } - } - - private static ValueToLong valueToLong(Type.Kind kind, @Nullable PrimitiveType id) { - if (kind == Type.Kind.PRIMITIVE) { - Preconditions.checkState(id != null, "Primitive type must not be null when kind is %s", kind); - switch (id) { - case Bool: - return value -> value.getBool() ? 1 : 0; - case Int8: - return PrimitiveReader::getInt8; - case Uint8: - return PrimitiveReader::getUint8; - case Int16: - return PrimitiveReader::getInt16; - case Uint16: - return PrimitiveReader::getUint16; - case Int32: - return PrimitiveReader::getInt32; - case Uint32: - return PrimitiveReader::getUint32; - case Int64: - return PrimitiveReader::getInt64; - case Uint64: - return PrimitiveReader::getUint64; - case Date: - case Datetime: - case TzDate: - case TzDatetime: - case Timestamp: - case TzTimestamp: - ValueToDateMillis delegate = valueToDateMillis(kind, id); - return delegate::fromValue; - case Interval: - return value -> TimeUnit.NANOSECONDS.toMicros(value.getInterval().toNanos()); - default: - return value -> { - throw dataTypeNotSupported(id, long.class); - }; - } - } else if (kind == Type.Kind.DECIMAL) { - return value -> value.getDecimal().toBigInteger().longValue(); // TODO: Improve performance - } else { - return value -> { - throw dataTypeNotSupported(kind, long.class); - }; - } - } - - private static ValueToFloat valueToFloat(Type.Kind kind, @Nullable PrimitiveType id) { - Class javaType = float.class; - if (kind == Type.Kind.PRIMITIVE) { - Preconditions.checkState(id != null, "Primitive type must not be null when kind is %s", kind); - switch (id) { - case Bool: - return value -> value.getBool() ? 1 : 0; - case Int8: - return PrimitiveReader::getInt8; - case Int16: - return PrimitiveReader::getInt16; - case Int32: - return PrimitiveReader::getInt32; - case Int64: - return PrimitiveReader::getInt64; - case Uint8: - return PrimitiveReader::getUint8; - case Uint16: - return PrimitiveReader::getUint16; - case Uint32: - return PrimitiveReader::getUint32; - case Uint64: - return PrimitiveReader::getUint64; - case Float: - return PrimitiveReader::getFloat; - case Double: - return value -> (float) value.getDouble(); - default: - return value -> { - throw dataTypeNotSupported(id, javaType); - }; - } - } else if (kind == Type.Kind.DECIMAL) { - return value -> value.getDecimal().toBigDecimal().floatValue(); - } else { - return value -> { - throw dataTypeNotSupported(kind, javaType); - }; - } - } - - private static ValueToDouble valueToDouble(Type.Kind kind, @Nullable PrimitiveType id) { - Class javaType = double.class; - if (kind == Type.Kind.PRIMITIVE) { - Preconditions.checkState(id != null, "Primitive type must not be null when kind is %s", kind); - switch (id) { - case Bool: - return value -> value.getBool() ? 1 : 0; - case Int8: - return PrimitiveReader::getInt8; - case Uint8: - return PrimitiveReader::getUint8; - case Int16: - return PrimitiveReader::getInt16; - case Uint16: - return PrimitiveReader::getUint16; - case Int32: - return PrimitiveReader::getInt32; - case Uint32: - return PrimitiveReader::getUint32; - case Int64: - return PrimitiveReader::getInt64; - case Uint64: - return PrimitiveReader::getUint64; - case Float: - return PrimitiveReader::getFloat; - case Double: - return PrimitiveReader::getDouble; - default: - return value -> { - throw dataTypeNotSupported(id, javaType); - }; - } - } else if (kind == Type.Kind.DECIMAL) { - return value -> value.getDecimal().toBigDecimal().doubleValue(); - } else { - return value -> { - throw dataTypeNotSupported(kind, javaType); - }; - } - } - - private static ValueToBytes valueToBytes(Type.Kind kind, @Nullable PrimitiveType id) { - Class javaType = byte[].class; - if (kind == Type.Kind.PRIMITIVE) { - Preconditions.checkState(id != null, "Primitive type must not be null when kind is %s", kind); - switch (id) { - case Bytes: - return PrimitiveReader::getBytes; - case Text: - // TODO: pretty ineffective conversion (bytes -> string -> bytes) - return value -> value.getText().getBytes(); - case Json: - return value -> value.getJson().getBytes(); - case JsonDocument: - return value -> value.getJsonDocument().getBytes(); - case Yson: - return PrimitiveReader::getYson; - case Uuid: - return value -> value.getUuid().toString().getBytes(); - default: - return value -> { - throw dataTypeNotSupported(id, javaType); - }; - } - } else { - return value -> { - throw dataTypeNotSupported(kind, javaType); - }; - } - } - - private static ValueToDateMillis valueToDateMillis(Type.Kind kind, @Nullable PrimitiveType id) { - Class javaType = long.class; - if (kind == Type.Kind.PRIMITIVE) { - Preconditions.checkState(id != null, "Primitive type must not be null when kind is %s", kind); - switch (id) { - case Int64: - return PrimitiveReader::getInt64; - case Uint64: - return PrimitiveReader::getUint64; - case Date: - return value -> TimeUnit.DAYS.toMillis(value.getDate().toEpochDay()); - case Datetime: - return value -> TimeUnit.SECONDS.toMillis(value.getDatetime().toEpochSecond(ZoneOffset.UTC)); - case TzDate: - return value -> TimeUnit.SECONDS.toMillis(value.getTzDate().toEpochSecond()); - case TzDatetime: - return value -> TimeUnit.SECONDS.toMillis(value.getTzDatetime().toEpochSecond()); - case Timestamp: - return value -> value.getTimestamp().toEpochMilli(); - case TzTimestamp: - return value -> TimeUnit.SECONDS.toMillis(value.getTzTimestamp().toEpochSecond()); - default: - return value -> { - throw dataTypeNotSupported(id, javaType); - }; - } - } else { - return value -> { - throw dataTypeNotSupported(kind, javaType); - }; - } - } - - private static ValueToNString valueToNString(Type.Kind kind, @Nullable PrimitiveType id) { - Class javaType = String.class; - if (kind == Type.Kind.PRIMITIVE) { - Preconditions.checkState(id != null, "Primitive type must not be null when kind is %s", kind); - switch (id) { - case Bytes: - return value -> new String(value.getBytes()); - case Text: - return PrimitiveReader::getText; - case Json: - return PrimitiveReader::getJson; - case JsonDocument: - return PrimitiveReader::getJsonDocument; - case Yson: - return value -> new String(value.getYson()); - case Uuid: - return value -> String.valueOf(value.getUuid()); - default: - return value -> { - throw dataTypeNotSupported(id, javaType); - }; - } - } else { - return value -> { - throw dataTypeNotSupported(kind, javaType); - }; - } - } - - private static ValueToURL valueToURL(Type.Kind kind, @Nullable PrimitiveType id) { - Class javaType = URL.class; - if (kind == Type.Kind.PRIMITIVE) { - Preconditions.checkState(id != null, "Primitive type must not be null when kind is %s", kind); - switch (id) { - case Bytes: - return value -> new String(value.getBytes()); - case Text: - return PrimitiveReader::getText; - default: - return value -> { - throw dataTypeNotSupported(id, javaType); - }; - } - } else { - return value -> { - throw dataTypeNotSupported(kind, javaType); - }; - } - } - - private static ValueToBigDecimal valueToBigDecimal(Type.Kind kind, @Nullable PrimitiveType id) { - Class javaType = BigDecimal.class; - if (kind == Type.Kind.PRIMITIVE) { - Preconditions.checkState(id != null, "Primitive type must not be null when kind is %s", kind); - switch (id) { - case Bool: - return value -> BigDecimal.valueOf(value.getBool() ? 1 : 0); - case Int8: - return value -> BigDecimal.valueOf(value.getInt8()); - case Uint8: - return value -> BigDecimal.valueOf(value.getUint8()); - case Int16: - return value -> BigDecimal.valueOf(value.getInt16()); - case Uint16: - return value -> BigDecimal.valueOf(value.getUint16()); - case Int32: - return value -> BigDecimal.valueOf(value.getInt32()); - case Uint32: - return value -> BigDecimal.valueOf(value.getUint32()); - case Int64: - return value -> BigDecimal.valueOf(value.getInt64()); - case Uint64: - return value -> BigDecimal.valueOf(value.getUint64()); - case Float: - return value -> BigDecimal.valueOf(value.getFloat()); - case Double: - return value -> BigDecimal.valueOf(value.getDouble()); - default: - return value -> { - throw dataTypeNotSupported(id, javaType); - }; - } - } else if (kind == Type.Kind.DECIMAL) { - return value -> value.getDecimal().toBigDecimal(); - } else { - return value -> { - throw dataTypeNotSupported(kind, javaType); - }; - } - } - - private static ValueToReader valueToReader(Type.Kind kind, @Nullable PrimitiveType id) { - Class javaType = Reader.class; - if (kind == Type.Kind.PRIMITIVE) { - Preconditions.checkState(id != null, "Primitive type must not be null when kind is %s", kind); - switch (id) { - case Bytes: - return value -> new InputStreamReader(new ByteArrayInputStream(value.getBytes())); - case Text: - return value -> new StringReader(value.getText()); - case Json: - return value -> new StringReader(value.getJson()); - case JsonDocument: - return value -> new StringReader(value.getJsonDocument()); - case Yson: - return value -> new InputStreamReader(new ByteArrayInputStream(value.getYson())); - case Uuid: - return value -> new StringReader(value.getUuid().toString()); - default: - return value -> { - throw dataTypeNotSupported(id, javaType); - }; - } - } else { - return value -> { - throw dataTypeNotSupported(kind, javaType); - }; + return (int) value; + } + + private static ValueToInt valueToInt(PrimitiveType id) { + switch (id) { + case Bool: + return value -> value.getBool() ? 1 : 0; + case Int8: + return PrimitiveReader::getInt8; + case Int16: + return PrimitiveReader::getInt16; + case Int32: + return PrimitiveReader::getInt32; + case Int64: + return value -> checkIntValue(id, value.getInt64()); + case Uint8: + return PrimitiveReader::getUint8; + case Uint16: + return PrimitiveReader::getUint16; + case Uint32: + return value -> checkIntValue(id, value.getUint32()); + case Uint64: + return value -> checkIntValue(id, value.getUint64()); + default: + return castToIntNotSupported(id.name()); + } + } + + private static ValueToLong valueToLong(PrimitiveType id) { + switch (id) { + case Bool: + return value -> value.getBool() ? 1 : 0; + case Int8: + return PrimitiveReader::getInt8; + case Uint8: + return PrimitiveReader::getUint8; + case Int16: + return PrimitiveReader::getInt16; + case Uint16: + return PrimitiveReader::getUint16; + case Int32: + return PrimitiveReader::getInt32; + case Uint32: + return PrimitiveReader::getUint32; + case Int64: + return PrimitiveReader::getInt64; + case Uint64: + return PrimitiveReader::getUint64; + case Date: + case Datetime: + case TzDate: + case TzDatetime: + case Timestamp: + case TzTimestamp: + ValueToDateMillis delegate = valueToDateMillis(id); + return delegate::fromValue; + case Interval: + return value -> TimeUnit.NANOSECONDS.toMicros(value.getInterval().toNanos()); + default: + return castToLongNotSupported(id.name()); + } + } + + private static ValueToFloat valueToFloat(PrimitiveType id) { + switch (id) { + case Bool: + return value -> value.getBool() ? 1 : 0; + case Int8: + return PrimitiveReader::getInt8; + case Int16: + return PrimitiveReader::getInt16; + case Int32: + return PrimitiveReader::getInt32; + case Int64: + return PrimitiveReader::getInt64; + case Uint8: + return PrimitiveReader::getUint8; + case Uint16: + return PrimitiveReader::getUint16; + case Uint32: + return PrimitiveReader::getUint32; + case Uint64: + return PrimitiveReader::getUint64; + case Float: + return PrimitiveReader::getFloat; + case Double: + return value -> (float) value.getDouble(); + default: + return castToFloatNotSupported(id.name()); + } + } + + private static ValueToDouble valueToDouble(PrimitiveType id) { + switch (id) { + case Bool: + return value -> value.getBool() ? 1 : 0; + case Int8: + return PrimitiveReader::getInt8; + case Uint8: + return PrimitiveReader::getUint8; + case Int16: + return PrimitiveReader::getInt16; + case Uint16: + return PrimitiveReader::getUint16; + case Int32: + return PrimitiveReader::getInt32; + case Uint32: + return PrimitiveReader::getUint32; + case Int64: + return PrimitiveReader::getInt64; + case Uint64: + return PrimitiveReader::getUint64; + case Float: + return PrimitiveReader::getFloat; + case Double: + return PrimitiveReader::getDouble; + default: + return castToDoubleNotSupported(id.name()); + } + } + + private static ValueToBytes valueToBytes(PrimitiveType id) { + switch (id) { + case Bytes: + return PrimitiveReader::getBytes; + case Text: + // TODO: pretty ineffective conversion (bytes -> string -> bytes) + return value -> value.getText().getBytes(); + case Json: + return value -> value.getJson().getBytes(); + case JsonDocument: + return value -> value.getJsonDocument().getBytes(); + case Yson: + return PrimitiveReader::getYson; + case Uuid: + return value -> value.getUuid().toString().getBytes(); + default: + return castToBytesNotSupported(id.name()); + } + } + + private static ValueToDateMillis valueToDateMillis(PrimitiveType id) { + switch (id) { + case Int64: + return PrimitiveReader::getInt64; + case Uint64: + return PrimitiveReader::getUint64; + case Date: + return value -> TimeUnit.DAYS.toMillis(value.getDate().toEpochDay()); + case Datetime: + return value -> TimeUnit.SECONDS.toMillis(value.getDatetime().toEpochSecond(ZoneOffset.UTC)); + case TzDate: + return value -> TimeUnit.SECONDS.toMillis(value.getTzDate().toEpochSecond()); + case TzDatetime: + return value -> TimeUnit.SECONDS.toMillis(value.getTzDatetime().toEpochSecond()); + case Timestamp: + return value -> value.getTimestamp().toEpochMilli(); + case TzTimestamp: + return value -> TimeUnit.SECONDS.toMillis(value.getTzTimestamp().toEpochSecond()); + default: + return castToDateMillisNotSupported(id.name()); + } + } + + private static ValueToNString valueToNString(PrimitiveType id) { + switch (id) { + case Bytes: + return value -> new String(value.getBytes()); + case Text: + return PrimitiveReader::getText; + case Json: + return PrimitiveReader::getJson; + case JsonDocument: + return PrimitiveReader::getJsonDocument; + case Yson: + return value -> new String(value.getYson()); + case Uuid: + return value -> String.valueOf(value.getUuid()); + default: + return castToNStringNotSupported(id.name()); + } + } + + private static ValueToURL valueToURL(PrimitiveType id) { + switch (id) { + case Bytes: + return value -> new String(value.getBytes()); + case Text: + return PrimitiveReader::getText; + default: + return castToUrlNotSupported(id.name()); + } + } + + private static ValueToBigDecimal valueToBigDecimal(PrimitiveType id) { + switch (id) { + case Bool: + return value -> BigDecimal.valueOf(value.getBool() ? 1 : 0); + case Int8: + return value -> BigDecimal.valueOf(value.getInt8()); + case Uint8: + return value -> BigDecimal.valueOf(value.getUint8()); + case Int16: + return value -> BigDecimal.valueOf(value.getInt16()); + case Uint16: + return value -> BigDecimal.valueOf(value.getUint16()); + case Int32: + return value -> BigDecimal.valueOf(value.getInt32()); + case Uint32: + return value -> BigDecimal.valueOf(value.getUint32()); + case Int64: + return value -> BigDecimal.valueOf(value.getInt64()); + case Uint64: + return value -> BigDecimal.valueOf(value.getUint64()); + case Float: + return value -> BigDecimal.valueOf(value.getFloat()); + case Double: + return value -> BigDecimal.valueOf(value.getDouble()); + default: + return castToBigDecimalNotSupported(id.name()); + } + } + + private static ValueToReader valueToReader(PrimitiveType id) { + switch (id) { + case Bytes: + return value -> new InputStreamReader(new ByteArrayInputStream(value.getBytes())); + case Text: + return value -> new StringReader(value.getText()); + case Json: + return value -> new StringReader(value.getJson()); + case JsonDocument: + return value -> new StringReader(value.getJsonDocument()); + case Yson: + return value -> new InputStreamReader(new ByteArrayInputStream(value.getYson())); + case Uuid: + return value -> new StringReader(value.getUuid().toString()); + default: + return castToReaderNotSupported(id.name()); } } + private static SqlType buildPrimitiveType(int sqlType, PrimitiveType id) { + switch (id) { + case Text: + case Json: + case JsonDocument: + case Uuid: + return new SqlType(sqlType, String.class); + case Bytes: + case Yson: + return new SqlType(sqlType, byte[].class); + case Bool: + return new SqlType(sqlType, Boolean.class); + case Int8: + return new SqlType(sqlType, Byte.class); + case Uint8: + case Int32: + case Uint16: + return new SqlType(sqlType, Integer.class); + case Int16: + return new SqlType(sqlType, Short.class); + case Uint32: + case Int64: + case Uint64: + return new SqlType(sqlType, Long.class); + case Float: + return new SqlType(sqlType, Float.class); + case Double: + return new SqlType(sqlType, Double.class); + case Date: + return new SqlType(sqlType, LocalDate.class); + case Datetime: + return new SqlType(sqlType, LocalDateTime.class); + case Timestamp: + return new SqlType(sqlType, Instant.class); + case Interval: + return new SqlType(sqlType, Duration.class); + case TzDate: + case TzDatetime: + case TzTimestamp: + return new SqlType(sqlType, ZonedDateTime.class); + default: + return new SqlType(sqlType, Value.class); + } + } static SqlType buildDataType(Type type) { - Type.Kind kind = type.getKind(); // All types must be the same as for #valueToObject int sqlType = YdbTypesImpl.getInstance().toSqlType(type); - if (kind == Type.Kind.PRIMITIVE) { - PrimitiveType id = (PrimitiveType) type; - final Class javaType; - switch (id) { - case Text: - case Json: - case JsonDocument: - case Uuid: - javaType = String.class; - break; - case Bytes: - case Yson: - javaType = byte[].class; - break; - case Bool: - javaType = Boolean.class; - break; - case Int8: - javaType = Byte.class; - break; - case Uint8: - case Int32: - case Uint16: - javaType = Integer.class; - break; - case Int16: - javaType = Short.class; - break; - case Uint32: - case Int64: - case Uint64: - javaType = Long.class; - break; - case Float: - javaType = Float.class; - break; - case Double: - javaType = Double.class; - break; - case Date: - javaType = LocalDate.class; - break; - case Datetime: - javaType = LocalDateTime.class; - break; - case Timestamp: - javaType = Instant.class; - break; - case Interval: - javaType = Duration.class; - break; - case TzDate: - case TzDatetime: - case TzTimestamp: - javaType = ZonedDateTime.class; - break; - default: - javaType = Value.class; - } - return new SqlType(sqlType, javaType); - } else if (kind == Type.Kind.DECIMAL) { - return new SqlType(sqlType, DecimalValue.class); - } else { - return new SqlType(sqlType, Value.class); - } - } - - private static ValueToObject valueToObject(Type.Kind kind, @Nullable PrimitiveType id) { - Class javaType = Object.class; - if (kind == Type.Kind.PRIMITIVE) { - Preconditions.checkState(id != null, "Primitive type must not be null when kind is %s", kind); - switch (id) { - case Bytes: - return PrimitiveReader::getBytes; - case Text: - return PrimitiveReader::getText; - case Json: - return PrimitiveReader::getJson; - case JsonDocument: - return PrimitiveReader::getJsonDocument; - case Yson: - return PrimitiveReader::getYson; - case Uuid: - return PrimitiveReader::getUuid; - case Bool: - return PrimitiveReader::getBool; - case Int8: - return PrimitiveReader::getInt8; - case Uint8: - return PrimitiveReader::getUint8; - case Int16: - return PrimitiveReader::getInt16; - case Uint16: - return PrimitiveReader::getUint16; - case Int32: - return PrimitiveReader::getInt32; - case Uint32: - return PrimitiveReader::getUint32; - case Int64: - return PrimitiveReader::getInt64; - case Uint64: - return PrimitiveReader::getUint64; - case Float: - return PrimitiveReader::getFloat; - case Double: - return PrimitiveReader::getDouble; - case Date: - return PrimitiveReader::getDate; - case Datetime: - return PrimitiveReader::getDatetime; - case Timestamp: - return PrimitiveReader::getTimestamp; - case Interval: - return PrimitiveReader::getInterval; - case TzDate: - return PrimitiveReader::getTzDate; - case TzDatetime: - return PrimitiveReader::getTzDatetime; - case TzTimestamp: - return PrimitiveReader::getTzTimestamp; - default: - // DyNumber - return value -> { - throw dataTypeNotSupported(id, javaType); - }; - } - } else if (kind == Type.Kind.DECIMAL) { - return PrimitiveReader::getDecimal; - } else { - return ValueReader::getValue; + switch (type.getKind()) { + case PRIMITIVE: + return buildPrimitiveType(sqlType, (PrimitiveType) type); + case DECIMAL: + return new SqlType(sqlType, DecimalValue.class); + default: + return new SqlType(sqlType, Value.class); } } + private static ValueToObject valueToObject(PrimitiveType id) { + switch (id) { + case Bytes: + return PrimitiveReader::getBytes; + case Text: + return PrimitiveReader::getText; + case Json: + return PrimitiveReader::getJson; + case JsonDocument: + return PrimitiveReader::getJsonDocument; + case Yson: + return PrimitiveReader::getYson; + case Uuid: + return PrimitiveReader::getUuid; + case Bool: + return PrimitiveReader::getBool; + case Int8: + return PrimitiveReader::getInt8; + case Uint8: + return PrimitiveReader::getUint8; + case Int16: + return PrimitiveReader::getInt16; + case Uint16: + return PrimitiveReader::getUint16; + case Int32: + return PrimitiveReader::getInt32; + case Uint32: + return PrimitiveReader::getUint32; + case Int64: + return PrimitiveReader::getInt64; + case Uint64: + return PrimitiveReader::getUint64; + case Float: + return PrimitiveReader::getFloat; + case Double: + return PrimitiveReader::getDouble; + case Date: + return PrimitiveReader::getDate; + case Datetime: + return PrimitiveReader::getDatetime; + case Timestamp: + return PrimitiveReader::getTimestamp; + case Interval: + return PrimitiveReader::getInterval; + case TzDate: + return PrimitiveReader::getTzDate; + case TzDatetime: + return PrimitiveReader::getTzDatetime; + case TzTimestamp: + return PrimitiveReader::getTzTimestamp; + default: + // DyNumber + return value -> { + throw dataTypeNotSupported(id, Object.class); + }; + } + } private static SQLException cannotConvert(PrimitiveType type, Class javaType, Object value) { return new SQLException(String.format(UNABLE_TO_CONVERT, type, value, javaType)); @@ -789,27 +682,102 @@ private static SQLException dataTypeNotSupported(PrimitiveType type, Class ja return new SQLException(String.format(UNABLE_TO_CAST, type, javaType)); } - private static SQLException dataTypeNotSupported(Type.Kind kind, Class javaType) { - return new SQLException(String.format(UNABLE_TO_CAST, kind, javaType)); + private static ValueToBoolean castToBooleanNotSupported(String type) { + return value -> { + throw new SQLException(String.format(UNABLE_TO_CAST, type, boolean.class)); + }; } - public static class Getters { - public final ValueToString toString; - public final ValueToBoolean toBoolean; - public final ValueToByte toByte; - public final ValueToShort toShort; - public final ValueToInt toInt; - public final ValueToLong toLong; - public final ValueToFloat toFloat; - public final ValueToDouble toDouble; - public final ValueToBytes toBytes; - public final ValueToObject toObject; - public final ValueToDateMillis toDateMillis; - public final ValueToNString toNString; - public final ValueToURL toURL; - public final ValueToBigDecimal toBigDecimal; - public final ValueToReader toReader; + private static ValueToByte castToByteNotSupported(String type) { + return value -> { + throw new SQLException(String.format(UNABLE_TO_CAST, type, byte.class)); + }; + } + + private static ValueToShort castToShortNotSupported(String type) { + return value -> { + throw new SQLException(String.format(UNABLE_TO_CAST, type, short.class)); + }; + } + + private static ValueToInt castToIntNotSupported(String type) { + return value -> { + throw new SQLException(String.format(UNABLE_TO_CAST, type, int.class)); + }; + } + + private static ValueToLong castToLongNotSupported(String type) { + return value -> { + throw new SQLException(String.format(UNABLE_TO_CAST, type, long.class)); + }; + } + private static ValueToFloat castToFloatNotSupported(String type) { + return value -> { + throw new SQLException(String.format(UNABLE_TO_CAST, type, float.class)); + }; + } + + private static ValueToDouble castToDoubleNotSupported(String type) { + return value -> { + throw new SQLException(String.format(UNABLE_TO_CAST, type, double.class)); + }; + } + + private static ValueToBytes castToBytesNotSupported(String type) { + return value -> { + throw new SQLException(String.format(UNABLE_TO_CAST, type, byte[].class)); + }; + } + + private static ValueToDateMillis castToDateMillisNotSupported(String type) { + return value -> { + throw new SQLException(String.format(UNABLE_TO_CAST, type, long.class)); + }; + } + + private static ValueToNString castToNStringNotSupported(String type) { + return value -> { + throw new SQLException(String.format(UNABLE_TO_CAST, type, String.class)); + }; + } + + private static ValueToURL castToUrlNotSupported(String type) { + return value -> { + throw new SQLException(String.format(UNABLE_TO_CAST, type, URL.class)); + }; + } + + private static ValueToBigDecimal castToBigDecimalNotSupported(String type) { + return value -> { + throw new SQLException(String.format(UNABLE_TO_CAST, type, BigDecimal.class)); + }; + } + + private static ValueToReader castToReaderNotSupported(String type) { + return value -> { + throw new SQLException(String.format(UNABLE_TO_CAST, type, Reader.class)); + }; + } + + public static class Getters { + private final ValueToString toString; + private final ValueToBoolean toBoolean; + private final ValueToByte toByte; + private final ValueToShort toShort; + private final ValueToInt toInt; + private final ValueToLong toLong; + private final ValueToFloat toFloat; + private final ValueToDouble toDouble; + private final ValueToBytes toBytes; + private final ValueToObject toObject; + private final ValueToDateMillis toDateMillis; + private final ValueToNString toNString; + private final ValueToURL toURL; + private final ValueToBigDecimal toBigDecimal; + private final ValueToReader toReader; + + @SuppressWarnings("ParameterNumber") Getters(ValueToString toString, ValueToBoolean toBoolean, ValueToByte toByte, @@ -841,65 +809,125 @@ public static class Getters { this.toBigDecimal = toBigDecimal; this.toReader = toReader; } + + public String readString(ValueReader reader) throws SQLException { + return toString.fromValue(reader); + } + + public boolean readBoolean(ValueReader reader) throws SQLException { + return toBoolean.fromValue(reader); + } + + public byte readByte(ValueReader reader) throws SQLException { + return toByte.fromValue(reader); + } + + public short readShort(ValueReader reader) throws SQLException { + return toShort.fromValue(reader); + } + + public int readInt(ValueReader reader) throws SQLException { + return toInt.fromValue(reader); + } + + public long readLong(ValueReader reader) throws SQLException { + return toLong.fromValue(reader); + } + + public float readFloat(ValueReader reader) throws SQLException { + return toFloat.fromValue(reader); + } + + public double readDouble(ValueReader reader) throws SQLException { + return toDouble.fromValue(reader); + } + + public byte[] readBytes(ValueReader reader) throws SQLException { + return toBytes.fromValue(reader); + } + + public Object readObject(ValueReader reader) throws SQLException { + return toObject.fromValue(reader); + } + + public long readDateMillis(ValueReader reader) throws SQLException { + return toDateMillis.fromValue(reader); + } + + public String readNString(ValueReader reader) throws SQLException { + return toNString.fromValue(reader); + } + + public String readURL(ValueReader reader) throws SQLException { + return toURL.fromValue(reader); + } + + public BigDecimal readBigDecimal(ValueReader reader) throws SQLException { + return toBigDecimal.fromValue(reader); + } + + public Reader readReader(ValueReader reader) throws SQLException { + return toReader.fromValue(reader); + } } - public interface ValueToString { + private interface ValueToString { String fromValue(ValueReader reader) throws SQLException; } - public interface ValueToBoolean { + private interface ValueToBoolean { boolean fromValue(ValueReader reader) throws SQLException; } - public interface ValueToByte { + private interface ValueToByte { byte fromValue(ValueReader reader) throws SQLException; } - public interface ValueToShort { + private interface ValueToShort { short fromValue(ValueReader reader) throws SQLException; } - public interface ValueToInt { + private interface ValueToInt { int fromValue(ValueReader reader) throws SQLException; } - public interface ValueToLong { + private interface ValueToLong { long fromValue(ValueReader reader) throws SQLException; } - public interface ValueToFloat { + private interface ValueToFloat { float fromValue(ValueReader reader) throws SQLException; } - public interface ValueToDouble { + private interface ValueToDouble { double fromValue(ValueReader reader) throws SQLException; } - public interface ValueToBytes { + private interface ValueToBytes { byte[] fromValue(ValueReader reader) throws SQLException; } - public interface ValueToObject { + private interface ValueToObject { Object fromValue(ValueReader reader) throws SQLException; } - public interface ValueToDateMillis { + private interface ValueToDateMillis { long fromValue(ValueReader reader) throws SQLException; } - public interface ValueToNString { + private interface ValueToNString { String fromValue(ValueReader reader) throws SQLException; } - public interface ValueToURL { + private interface ValueToURL { String fromValue(ValueReader reader) throws SQLException; } - public interface ValueToBigDecimal { + private interface ValueToBigDecimal { BigDecimal fromValue(ValueReader reader) throws SQLException; } - public interface ValueToReader { + private interface ValueToReader { Reader fromValue(ValueReader reader) throws SQLException; } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/common/MappingSetters.java b/jdbc/src/main/java/tech/ydb/jdbc/common/MappingSetters.java index 3a3b152..090f223 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/common/MappingSetters.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/common/MappingSetters.java @@ -37,6 +37,7 @@ import static tech.ydb.jdbc.YdbConst.UNABLE_TO_CAST; public class MappingSetters { + private MappingSetters() { } static Setters buildSetters(Type type) { return buildToValueImpl(type); diff --git a/jdbc/src/main/java/tech/ydb/jdbc/common/TypeDescription.java b/jdbc/src/main/java/tech/ydb/jdbc/common/TypeDescription.java index 5a2f050..15abd19 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/common/TypeDescription.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/common/TypeDescription.java @@ -11,6 +11,15 @@ import tech.ydb.table.values.Type; public class TypeDescription { + private static final Map TYPES = new ConcurrentHashMap<>(); + + static { + ofInternal(DecimalType.of(DecimalType.MAX_PRECISION)); // max + ofInternal(DecimalType.of(22, 9)); // default for database + for (PrimitiveType type : PrimitiveType.values()) { + ofInternal(type); // All primitive values + } + } private final Type type; private final boolean optional; @@ -58,16 +67,6 @@ public Type ydbType() { return type; } - private static final Map TYPES = new ConcurrentHashMap<>(); - - static { - ofInternal(DecimalType.of(DecimalType.MAX_PRECISION)); // max - ofInternal(DecimalType.of(22, 9)); // default for database - for (PrimitiveType type : PrimitiveType.values()) { - ofInternal(type); // All primitive values - } - } - private static void ofInternal(Type type) { of(type); of(type.makeOptional()); // Register both normal and optional types diff --git a/jdbc/src/main/java/tech/ydb/jdbc/common/YdbFunctions.java b/jdbc/src/main/java/tech/ydb/jdbc/common/YdbFunctions.java index de7be2d..ebfe99c 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/common/YdbFunctions.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/common/YdbFunctions.java @@ -8,6 +8,34 @@ import java.util.stream.Stream; public final class YdbFunctions { + public static final String STRING_FUNCTIONS = Stream.of( + Udf.Hyperscans.functions(), + Udf.Pires.functions(), + Udf.Re2s.functions(), + Udf.Strings.functions(), + Udf.Unicodes.functions(), + Udf.Urls.functions(), + Udf.Ips.functions(), + Udf.Digests.functions(), + Udf.Ysons.functions(), + Builtin.Strings.functions() + ).flatMap(Collection::stream).collect(Collectors.joining(",")); + + public static final String NUMERIC_FUNCTIONS = Stream.of( + Udf.Maths.functions(), + Builtin.Numerics.functions() + ).flatMap(Collection::stream).collect(Collectors.joining(",")); + + public static final String SYSTEM_FUNCTIONS = Stream.of( + Builtin.Systems.functions() + ).flatMap(Collection::stream).collect(Collectors.joining(",")); + + public static final String DATETIME_FUNCTIONS = Stream.of( + Udf.DateTimes.functions(), + Builtin.Dates.functions() + ).flatMap(Collection::stream).collect(Collectors.joining(",")); + + private YdbFunctions() { } public static final class Builtin { public static final class Strings { @@ -311,9 +339,6 @@ public static List functions() { } } - private Builtin() { - } - private static final List ALL_FUNCTIONS = Collections.unmodifiableList(Stream.of( Strings.functions(), Systems.functions(), @@ -322,6 +347,8 @@ private Builtin() { .flatMap(Collection::stream) .collect(Collectors.toList())); + private Builtin() { } + public static List allFunctions() { return ALL_FUNCTIONS; } @@ -341,8 +368,7 @@ public static final class Hyperscans { Arrays.asList(GREP, MATCH, BACKTRACKING_GREP, BACKTRACKING_MATCH, MULTI_GREP, MULTI_MATCH, CAPTURE, REPLACE)); - private Hyperscans() { - } + private Hyperscans() { } public static List functions() { return FUNCTIONS; @@ -359,8 +385,7 @@ public static final class Pires { private static final List FUNCTIONS = Collections.unmodifiableList( Arrays.asList(GREP, MATCH, MULTI_GREP, MULTI_MATCH, CAPTURE, REPLACE)); - private Pires() { - } + private Pires() { } public static List functions() { return FUNCTIONS; @@ -378,8 +403,7 @@ public static final class Re2s { private static final List FUNCTIONS = Collections.unmodifiableList( Arrays.asList(GREP, MATCH, CAPTURE, FIND_AND_CONSUME, REPLACE, COUNT, OPTIONS)); - private Re2s() { - } + private Re2s() { } public static List functions() { return FUNCTIONS; @@ -458,8 +482,7 @@ public static final class Strings { LEVENSTEIN_DISTANCE, LEFT_PAD, RIGHT_PAD, HEX, S_HEX, BIN, S_BIN, HEX_TEXT, BIN_TEXT, HUMAN_READABLE_DURATION, HUMAN_READABLE_QUANTITY, HUMAN_READABLE_BYTES, PREC, REVERSE)); - private Strings() { - } + private Strings() { } public static List functions() { return FUNCTIONS; @@ -499,8 +522,7 @@ public static final class Unicodes { FROM_CODE_POINT_LIST, REVERSE, TO_LOWER, TO_UPPER, TO_TITLE, SPLIT_TO_LIST, JOIN_FROM_LIST)); - private Unicodes() { - } + private Unicodes() { } public static List functions() { return FUNCTIONS; @@ -564,19 +586,18 @@ public static final class DateTimes { public static final String PARSE_X509 = "DateTime::ParseX509"; private static final List FUNCTIONS = Collections.unmodifiableList( Arrays.asList(SPLIT, MAKE_DATE, MAKE_DATETIME, MAKE_TIMESTAMP, MAKE_TZ_DATE, MAKE_TZ_DATETIME, - MAKE_TZ_TIMESTAMP, GET_YEAR, GET_DAY_OF_YEAR, GET_MONTH, GET_MONTH_NAME, GET_WEEK_OF_YEAR - , GET_DAY_OF_MONTH, GET_DAY_OF_WEEK, GET_DAY_OF_WEEK_NAME, GET_HOUR, GET_MINUTE, + MAKE_TZ_TIMESTAMP, GET_YEAR, GET_DAY_OF_YEAR, GET_MONTH, GET_MONTH_NAME, GET_WEEK_OF_YEAR, + GET_DAY_OF_MONTH, GET_DAY_OF_WEEK, GET_DAY_OF_WEEK_NAME, GET_HOUR, GET_MINUTE, GET_SECOND, GET_MILLISECOND_OF_SECOND, GET_MICROSECOND_OF_SECOND, GET_TIMEZONE_ID, - GET_TIMEZONE_NAME, UPDATE, FROM_SECONDS, FROM_MILLISECONDS, FROM_MICROSECONDS, TO_SECONDS - , TO_MILLISECONDS, TO_MICROSECONDS, TO_DAYS, TO_HOURS, TO_MINUTES, INTERVAL_FROM_DAYS, + GET_TIMEZONE_NAME, UPDATE, FROM_SECONDS, FROM_MILLISECONDS, FROM_MICROSECONDS, TO_SECONDS, + TO_MILLISECONDS, TO_MICROSECONDS, TO_DAYS, TO_HOURS, TO_MINUTES, INTERVAL_FROM_DAYS, INTERVAL_FROM_HOURS, INTERVAL_FROM_MINUTES, INTERVAL_FROM_SECONDS, INTERVAL_FROM_MILLISECONDS, INTERVAL_FROM_MICROSECONDS, START_OF_YEAR, START_OF_QUARTER, START_OF_MONTH, START_OF_WEEK, START_OF_DAY, START_OF, TIME_OF_DAY, SHIFT_YEARS, SHIFT_QUARTERS, SHIFT_MONTHS, FORMAT, PARSE, PARSE_RFC822, PARSE_ISO8601, PARSE_HTTP, PARSE_X509)); - private DateTimes() { - } + private DateTimes() { } public static List functions() { return FUNCTIONS; @@ -625,8 +646,7 @@ public static final class Urls { FORCE_HOST_NAME_TO_PUNYCODE, PUNYCODE_TO_HOST_NAME, FORCE_PUNYCODE_TO_HOST_NAME, CAN_BE_PUNYCODE_HOST_NAME, IS_ALLOWED_BY_ROBOTS_TXT)); - private Urls() { - } + private Urls() { } public static List functions() { return FUNCTIONS; @@ -645,8 +665,7 @@ public static final class Ips { Arrays.asList(FROM_STRING, TO_STRING, IS_IPV4, IS_IPV6, IS_EMBEDDED_IPV4, CONVERT_TO_IPV6, GET_SUBNET)); - private Ips() { - } + private Ips() { } public static List functions() { return FUNCTIONS; @@ -724,8 +743,7 @@ public static final class Ysons { Y_PATH_STRING, Y_PATH_DICT, Y_PATH_LIST, ATTRIBUTES, SERIALIZE, SERIALIZE_TEXT, SERIALIZE_PRETTY, SERIALIZE_JSON, OPTIONS)); - private Ysons() { - } + private Ysons() { } public static List functions() { return FUNCTIONS; @@ -763,8 +781,7 @@ public static final class Digests { FARM_HASH_FINGERPRINT32, FARM_HASH_FINGERPRINT64, FARM_HASH_FINGERPRINT128, SUPER_FAST_HASH, SHA1, SHA256, INT_HASH64, XXH3, XXH3_128)); - private Digests() { - } + private Digests() { } public static List functions() { return FUNCTIONS; @@ -818,8 +835,7 @@ public static final class Maths { TGAMMA, TRUNC, LOG, LOG2, LOG10, ATAN2, FMOD, HYPOT, POW, REMAINDER, FUZZY_EQUALS, MOD, REM)); - private Maths() { - } + private Maths() { } public static List functions() { return FUNCTIONS; @@ -843,17 +859,13 @@ public static final class Histograms { GET_SUM_BELOW_BOUND, GET_SUM_IN_RANGE, CALC_UPPER_BOUND, CALC_LOWER_BOUND, CALC_UPPER_BOUND_SAFE, CALC_LOWER_BOUND_SAFE)); - private Histograms() { - } + private Histograms() { } public static List functions() { return FUNCTIONS; } } - private Udf() { - } - private static final List ALL_FUNCTIONS = Collections.unmodifiableList(Stream.of( Hyperscans.functions(), Pires.functions(), @@ -870,35 +882,10 @@ private Udf() { .flatMap(Collection::stream) .collect(Collectors.toList())); + private Udf() { } + public static List allFunctions() { return ALL_FUNCTIONS; } } - - public static final String STRING_FUNCTIONS = Stream.of( - Udf.Hyperscans.functions(), - Udf.Pires.functions(), - Udf.Re2s.functions(), - Udf.Strings.functions(), - Udf.Unicodes.functions(), - Udf.Urls.functions(), - Udf.Ips.functions(), - Udf.Digests.functions(), - Udf.Ysons.functions(), - Builtin.Strings.functions() - ).flatMap(Collection::stream).collect(Collectors.joining(",")); - - public static final String NUMERIC_FUNCTIONS = Stream.of( - Udf.Maths.functions(), - Builtin.Numerics.functions() - ).flatMap(Collection::stream).collect(Collectors.joining(",")); - - public static final String SYSTEM_FUNCTIONS = Stream.of( - Builtin.Systems.functions() - ).flatMap(Collection::stream).collect(Collectors.joining(",")); - - public static final String DATETIME_FUNCTIONS = Stream.of( - Udf.DateTimes.functions(), - Builtin.Dates.functions() - ).flatMap(Collection::stream).collect(Collectors.joining(",")); } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/context/BaseYdbExecutor.java b/jdbc/src/main/java/tech/ydb/jdbc/context/BaseYdbExecutor.java new file mode 100644 index 0000000..678414d --- /dev/null +++ b/jdbc/src/main/java/tech/ydb/jdbc/context/BaseYdbExecutor.java @@ -0,0 +1,90 @@ +package tech.ydb.jdbc.context; + +import java.sql.SQLException; +import java.time.Duration; +import java.util.Collection; +import java.util.concurrent.LinkedBlockingQueue; + +import tech.ydb.core.Result; +import tech.ydb.core.UnexpectedResultException; +import tech.ydb.jdbc.exception.ExceptionFactory; +import tech.ydb.jdbc.query.QueryType; +import tech.ydb.jdbc.query.YdbQuery; +import tech.ydb.table.Session; +import tech.ydb.table.TableClient; +import tech.ydb.table.query.ExplainDataQueryResult; +import tech.ydb.table.query.Params; +import tech.ydb.table.result.ResultSetReader; +import tech.ydb.table.result.impl.ProtoValueReaders; +import tech.ydb.table.settings.ExecuteScanQuerySettings; +import tech.ydb.table.settings.ExecuteSchemeQuerySettings; +import tech.ydb.table.settings.ExplainDataQuerySettings; + +/** + * + * @author Aleksandr Gorshenin + */ +public abstract class BaseYdbExecutor implements YdbExecutor { + private final Duration sessionTimeout; + private final TableClient tableClient; + + public BaseYdbExecutor(YdbContext ctx) { + this.sessionTimeout = ctx.getOperationProperties().getSessionTimeout(); + this.tableClient = ctx.getTableClient(); + } + + protected Session createNewTableSession(YdbValidator validator) throws SQLException { + try { + Result session = tableClient.createSession(sessionTimeout).join(); + validator.addStatusIssues(session.getStatus()); + return session.getValue(); + } catch (UnexpectedResultException ex) { + throw ExceptionFactory.createException("Cannot create session with " + ex.getStatus(), ex); + } + } + + @Override + public void executeSchemeQuery(YdbContext ctx, YdbValidator validator, YdbQuery query) throws SQLException { + // Scheme query does not affect transactions or result sets + ExecuteSchemeQuerySettings settings = ctx.withDefaultTimeout(new ExecuteSchemeQuerySettings()); + final String yql = query.getYqlQuery(null); + + try (Session session = createNewTableSession(validator)) { + validator.execute(QueryType.SCHEME_QUERY + " >>\n" + yql, () -> session.executeSchemeQuery(yql, settings)); + } + } + + @Override + public ExplainDataQueryResult executeExplainQuery(YdbContext ctx, YdbValidator validator, YdbQuery query) + throws SQLException { + ensureOpened(); + + String yql = query.getYqlQuery(null); + ExplainDataQuerySettings settings = ctx.withDefaultTimeout(new ExplainDataQuerySettings()); + try (Session session = createNewTableSession(validator)) { + String msg = QueryType.EXPLAIN_QUERY + " >>\n" + yql; + return validator.call(msg, () -> session.explainDataQuery(yql, settings)); + } + } + + @Override + public ResultSetReader executeScanQuery(YdbContext ctx, YdbValidator validator, YdbQuery query, Params params) + throws SQLException { + ensureOpened(); + + String yql = query.getYqlQuery(params); + Collection resultSets = new LinkedBlockingQueue<>(); + Duration scanQueryTimeout = ctx.getOperationProperties().getScanQueryTimeout(); + ExecuteScanQuerySettings settings = ExecuteScanQuerySettings.newBuilder() + .withRequestTimeout(scanQueryTimeout) + .build(); + try (Session session = createNewTableSession(validator)) { + validator.execute(QueryType.SCAN_QUERY + " >>\n" + yql, + () -> session.executeScanQuery(yql, params, settings).start(resultSets::add)); + } + + return ProtoValueReaders.forResultSets(resultSets); + } + + +} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/context/QueryServiceExecutor.java b/jdbc/src/main/java/tech/ydb/jdbc/context/QueryServiceExecutor.java new file mode 100644 index 0000000..6a6c707 --- /dev/null +++ b/jdbc/src/main/java/tech/ydb/jdbc/context/QueryServiceExecutor.java @@ -0,0 +1,266 @@ +package tech.ydb.jdbc.context; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import tech.ydb.common.transaction.TxMode; +import tech.ydb.core.Result; +import tech.ydb.core.UnexpectedResultException; +import tech.ydb.jdbc.YdbConst; +import tech.ydb.jdbc.exception.ExceptionFactory; +import tech.ydb.jdbc.query.QueryType; +import tech.ydb.jdbc.query.YdbQuery; +import tech.ydb.query.QueryClient; +import tech.ydb.query.QuerySession; +import tech.ydb.query.QueryTransaction; +import tech.ydb.query.settings.CommitTransactionSettings; +import tech.ydb.query.settings.ExecuteQuerySettings; +import tech.ydb.query.settings.RollbackTransactionSettings; +import tech.ydb.query.tools.QueryReader; +import tech.ydb.table.query.Params; +import tech.ydb.table.result.ResultSetReader; + +/** + * + * @author Aleksandr Gorshenin + */ +public class QueryServiceExecutor extends BaseYdbExecutor { + private final Duration sessionTimeout; + private final QueryClient queryClient; + + private int transactionLevel; + private boolean isReadOnly; + private boolean isAutoCommit; + private TxMode txMode; + + private QueryTransaction tx; + private boolean isClosed; + + public QueryServiceExecutor(YdbContext ctx, int transactionLevel, boolean autoCommit) throws SQLException { + super(ctx); + this.sessionTimeout = ctx.getOperationProperties().getSessionTimeout(); + this.queryClient = ctx.getQueryClient(); + this.transactionLevel = transactionLevel; + this.isReadOnly = transactionLevel != Connection.TRANSACTION_SERIALIZABLE; + this.isAutoCommit = autoCommit; + this.txMode = txMode(transactionLevel, isReadOnly); + this.tx = null; + this.isClosed = false; + } + + protected QuerySession createNewQuerySession(YdbValidator validator) throws SQLException { + try { + Result session = queryClient.createSession(sessionTimeout).join(); + validator.addStatusIssues(session.getStatus()); + return session.getValue(); + } catch (UnexpectedResultException ex) { + throw ExceptionFactory.createException("Cannot create session with " + ex.getStatus(), ex); + } + } + + @Override + public void close() { + cleanTx(); + isClosed = true; + } + + private void cleanTx() { + if (tx != null) { + tx.getSession().close(); + tx = null; + } + } + + @Override + public void setTransactionLevel(int level) throws SQLException { + if (level == transactionLevel) { + return; + } + + if (tx != null && tx.isActive()) { + throw new SQLFeatureNotSupportedException(YdbConst.CHANGE_ISOLATION_INSIDE_TX); + } + + isReadOnly = isReadOnly || level != Connection.TRANSACTION_SERIALIZABLE; + transactionLevel = level; + txMode = txMode(transactionLevel, isReadOnly); + } + + @Override + public void setReadOnly(boolean readOnly) throws SQLException { + if (readOnly == isReadOnly) { + return; + } + + if (tx != null && tx.isActive()) { + throw new SQLFeatureNotSupportedException(YdbConst.READONLY_INSIDE_TRANSACTION); + } + + isReadOnly = readOnly; + txMode = txMode(transactionLevel, isReadOnly); + } + + @Override + public void setAutoCommit(boolean autoCommit) throws SQLException { + if (autoCommit == isAutoCommit) { + return; + } + + if (tx != null && tx.isActive()) { + throw new SQLFeatureNotSupportedException(YdbConst.CHANGE_ISOLATION_INSIDE_TX); + } + + isAutoCommit = autoCommit; + } + + @Override + public boolean isClosed() { + return isClosed; + } + + @Override + public String txID() { + return tx != null ? tx.getId() : null; + } + + @Override + public boolean isInsideTransaction() throws SQLException { + ensureOpened(); + return tx != null && tx.isActive(); + } + + @Override + public boolean isAutoCommit() throws SQLException { + ensureOpened(); + return isAutoCommit; + } + + @Override + public boolean isReadOnly() throws SQLException { + ensureOpened(); + return isReadOnly; + } + + @Override + public int transactionLevel() throws SQLException { + ensureOpened(); + return transactionLevel; + } + + @Override + public void commit(YdbContext ctx, YdbValidator validator) throws SQLException { + ensureOpened(); + + if (tx == null || !tx.isActive()) { + return; + } + + CommitTransactionSettings settings = ctx.withRequestTimeout(CommitTransactionSettings.newBuilder()).build(); + try { + validator.clearWarnings(); + validator.call("Commit TxId: " + tx.getId(), () -> tx.commit(settings)); + } finally { + cleanTx(); + } + } + + @Override + public void rollback(YdbContext ctx, YdbValidator validator) throws SQLException { + ensureOpened(); + + if (tx == null || !tx.isActive()) { + return; + } + + RollbackTransactionSettings settings = ctx.withRequestTimeout(RollbackTransactionSettings.newBuilder()) + .build(); + + try { + validator.clearWarnings(); + validator.execute("Rollback TxId: " + tx.getId(), () -> tx.rollback(settings)); + } finally { + cleanTx(); + } + } + + @Override + public List executeDataQuery( + YdbContext ctx, YdbValidator validator, YdbQuery query, long timeout, boolean keepInCache, Params params + ) throws SQLException { + ensureOpened(); + + final String yql = query.getYqlQuery(params); + ExecuteQuerySettings.Builder builder = ExecuteQuerySettings.newBuilder(); + if (timeout > 0) { + builder = builder.withRequestTimeout(timeout, TimeUnit.SECONDS); + } + final ExecuteQuerySettings settings = builder.build(); + + if (tx == null) { + tx = createNewQuerySession(validator).createNewTransaction(txMode); + } + + try { + QueryReader result = validator.call(QueryType.DATA_QUERY + " >>\n" + yql, + () -> QueryReader.readFrom(tx.createQuery(yql, isAutoCommit, params, settings)) + ); + + List readers = new ArrayList<>(); + result.forEach(readers::add); + return readers; + } finally { + if (!tx.isActive()) { + cleanTx(); + } + } + } + + @Override + public void executeSchemeQuery(YdbContext ctx, YdbValidator validator, YdbQuery query) throws SQLException { + // Scheme query does not affect transactions or result sets + ExecuteQuerySettings settings = ctx.withRequestTimeout(ExecuteQuerySettings.newBuilder()).build(); + final String yql = query.getYqlQuery(null); + + try (QuerySession session = createNewQuerySession(validator)) { + validator.call(QueryType.SCHEME_QUERY + " >>\n" + yql, + () -> session.createQuery(yql, TxMode.NONE, Params.empty(), settings).execute() + ); + } + } + + @Override + public boolean isValid(YdbValidator validator, int timeout) throws SQLException { + ensureOpened(); + return true; + } + + private static TxMode txMode(int level, boolean isReadOnly) throws SQLException { + if (!isReadOnly) { + // YDB support only one RW mode + if (level != Connection.TRANSACTION_SERIALIZABLE) { + throw new SQLException(YdbConst.UNSUPPORTED_TRANSACTION_LEVEL + level); + } + + return TxMode.SERIALIZABLE_RW; + } + + switch (level) { + case Connection.TRANSACTION_SERIALIZABLE: + return TxMode.SNAPSHOT_RO; + case YdbConst.ONLINE_CONSISTENT_READ_ONLY: + return TxMode.ONLINE_RO; + case YdbConst.ONLINE_INCONSISTENT_READ_ONLY: + return TxMode.ONLINE_INCONSISTENT_RO; + case YdbConst.STALE_CONSISTENT_READ_ONLY: + return TxMode.STALE_RO; + default: + throw new SQLException(YdbConst.UNSUPPORTED_TRANSACTION_LEVEL + level); + } + } + +} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/context/SchemeExecutor.java b/jdbc/src/main/java/tech/ydb/jdbc/context/SchemeExecutor.java new file mode 100644 index 0000000..6631d59 --- /dev/null +++ b/jdbc/src/main/java/tech/ydb/jdbc/context/SchemeExecutor.java @@ -0,0 +1,33 @@ +package tech.ydb.jdbc.context; + + +import java.util.concurrent.CompletableFuture; + +import tech.ydb.core.Result; +import tech.ydb.scheme.SchemeClient; +import tech.ydb.scheme.description.ListDirectoryResult; +import tech.ydb.table.SessionRetryContext; +import tech.ydb.table.description.TableDescription; +import tech.ydb.table.settings.DescribeTableSettings; + +/** + * + * @author Aleksandr Gorshenin + */ +public class SchemeExecutor { + private final SchemeClient schemeClient; + private final SessionRetryContext retryCtx; + + public SchemeExecutor(YdbContext ctx) { + this.schemeClient = ctx.getSchemeClient(); + this.retryCtx = SessionRetryContext.create(ctx.getTableClient()).build(); + } + + public CompletableFuture> listDirectory(String path) { + return schemeClient.listDirectory(path); + } + + public CompletableFuture> describeTable(String tablePath, DescribeTableSettings settings) { + return retryCtx.supplyResult(session -> session.describeTable(tablePath, settings)); + } +} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/context/TableServiceExecutor.java b/jdbc/src/main/java/tech/ydb/jdbc/context/TableServiceExecutor.java new file mode 100644 index 0000000..6638362 --- /dev/null +++ b/jdbc/src/main/java/tech/ydb/jdbc/context/TableServiceExecutor.java @@ -0,0 +1,418 @@ +package tech.ydb.jdbc.context; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import tech.ydb.jdbc.YdbConst; +import tech.ydb.jdbc.query.QueryType; +import tech.ydb.jdbc.query.YdbQuery; +import tech.ydb.table.Session; +import tech.ydb.table.query.DataQueryResult; +import tech.ydb.table.query.Params; +import tech.ydb.table.result.ResultSetReader; +import tech.ydb.table.settings.CommitTxSettings; +import tech.ydb.table.settings.ExecuteDataQuerySettings; +import tech.ydb.table.settings.KeepAliveSessionSettings; +import tech.ydb.table.settings.RollbackTxSettings; +import tech.ydb.table.transaction.TxControl; + +/** + * + * @author Aleksandr Gorshenin + */ +public class TableServiceExecutor extends BaseYdbExecutor { + private volatile TxState tx; + + public TableServiceExecutor(YdbContext ctx, int transactionLevel, boolean autoCommit) throws SQLException { + super(ctx); + this.tx = createTx(transactionLevel, autoCommit); + } + + @Override + public void close() { + tx = null; + } + + private void updateState(TxState newTx) { + if (this.tx == newTx || this.tx == null) { + return; + } + this.tx = newTx; + } + + @Override + public void setTransactionLevel(int level) throws SQLException { + updateState(tx.withTransactionLevel(level)); + } + + @Override + public void setReadOnly(boolean readOnly) throws SQLException { + updateState(tx.withReadOnly(readOnly)); + } + + @Override + public void setAutoCommit(boolean autoCommit) throws SQLException { + updateState(tx.withAutoCommit(autoCommit)); + } + + @Override + public boolean isClosed() { + return tx == null; + } + + @Override + public String txID() { + return tx != null ? tx.txID() : null; + } + + @Override + public boolean isInsideTransaction() throws SQLException { + ensureOpened(); + return tx.isInsideTransaction(); + } + + @Override + public boolean isAutoCommit() throws SQLException { + ensureOpened(); + return tx.isAutoCommit(); + } + + @Override + public boolean isReadOnly() throws SQLException { + ensureOpened(); + return tx.isReadOnly(); + } + + @Override + public int transactionLevel() throws SQLException { + ensureOpened(); + return tx.transactionLevel(); + } + + @Override + public void commit(YdbContext ctx, YdbValidator validator) throws SQLException { + ensureOpened(); + + if (!isInsideTransaction()) { + return; + } + + Session session = tx.getSession(validator); + CommitTxSettings settings = ctx.withDefaultTimeout(new CommitTxSettings()); + + try { + validator.clearWarnings(); + validator.execute( + "Commit TxId: " + tx.txID(), + () -> session.commitTransaction(tx.txID(), settings) + ); + } finally { + updateState(tx.withCommit(session)); + } + } + + @Override + public void rollback(YdbContext ctx, YdbValidator validator) throws SQLException { + ensureOpened(); + + if (!isInsideTransaction()) { + return; + } + + Session session = tx.getSession(validator); + RollbackTxSettings settings = ctx.withDefaultTimeout(new RollbackTxSettings()); + + try { + validator.clearWarnings(); + validator.execute( + "Rollback TxId: " + tx.txID(), + () -> session.rollbackTransaction(tx.txID(), settings) + ); + } finally { + updateState(tx.withRollback(session)); + } + } + + private ExecuteDataQuerySettings dataQuerySettings(long timeout, boolean keepInCache) { + ExecuteDataQuerySettings settings = new ExecuteDataQuerySettings(); + if (timeout > 0) { + settings = settings + .setOperationTimeout(Duration.ofSeconds(timeout)) + .setTimeout(Duration.ofSeconds(timeout + 1)); + } + if (!keepInCache) { + settings = settings.disableQueryCache(); + } + + return settings; + } + + @Override + public List executeDataQuery( + YdbContext ctx, YdbValidator validator, YdbQuery query, long timeout, boolean keepInCache, Params params + ) throws SQLException { + ensureOpened(); + + final String yql = query.getYqlQuery(params); + final Session session = tx.getSession(validator); + try { + DataQueryResult result = validator.call( + QueryType.DATA_QUERY + " >>\n" + yql, + () -> session.executeDataQuery(yql, tx.txControl(), params, dataQuerySettings(timeout, keepInCache)) + ); + updateState(tx.withDataQuery(session, result.getTxId())); + + List readers = new ArrayList<>(); + for (int idx = 0; idx < result.getResultSetCount(); idx += 1) { + readers.add(result.getResultSet(idx)); + } + + return readers; + } catch (SQLException | RuntimeException ex) { + updateState(tx.withRollback(session)); + throw ex; + } + } + + @Override + public boolean isValid(YdbValidator validator, int timeout) throws SQLException { + ensureOpened(); + + Session session = tx.getSession(validator); + try { + KeepAliveSessionSettings settings = new KeepAliveSessionSettings().setTimeout(Duration.ofSeconds(timeout)); + Session.State keepAlive = validator.call( + "Keep alive: " + tx.txID(), + () -> session.keepAlive(settings) + ); + return keepAlive == Session.State.READY; + } finally { + updateState(tx.withKeepAlive(session)); + } + } + + private class TxState { + private final int transactionLevel; + private final boolean isReadOnly; + private final boolean isAutoCommit; + + private final TxControl txControl; + + protected TxState(TxControl txControl, int level, boolean isReadOnly, boolean isAutoCommit) { + this.transactionLevel = level; + this.isReadOnly = isReadOnly; + this.isAutoCommit = isAutoCommit; + this.txControl = txControl; + } + + protected TxState(TxControl txControl, TxState other) { + this.transactionLevel = other.transactionLevel; + this.isReadOnly = other.isReadOnly; + this.isAutoCommit = other.isAutoCommit; + this.txControl = txControl; + } + + @Override + public String toString() { + return "NoTx"; + } + + public String txID() { + return null; + } + + public boolean isInsideTransaction() { + return false; + } + + public TxControl txControl() { + return txControl; + } + + public boolean isAutoCommit() { + return isAutoCommit; + } + + public boolean isReadOnly() { + return isReadOnly; + } + + public int transactionLevel() { + return transactionLevel; + } + + public TxState withAutoCommit(boolean newAutoCommit) throws SQLException { + if (newAutoCommit == isAutoCommit) { + return this; + } + + if (isInsideTransaction()) { + throw new SQLFeatureNotSupportedException(YdbConst.CHANGE_ISOLATION_INSIDE_TX); + } + + return emptyTx(transactionLevel, isReadOnly, newAutoCommit); + } + + public TxState withReadOnly(boolean newReadOnly) throws SQLException { + if (newReadOnly == isReadOnly()) { + return this; + } + + if (isInsideTransaction()) { + throw new SQLFeatureNotSupportedException(YdbConst.READONLY_INSIDE_TRANSACTION); + } + + return emptyTx(transactionLevel, newReadOnly, isAutoCommit); + } + + public TxState withTransactionLevel(int newTransactionLevel) throws SQLException { + if (newTransactionLevel == transactionLevel) { + return this; + } + + if (isInsideTransaction()) { + throw new SQLFeatureNotSupportedException(YdbConst.CHANGE_ISOLATION_INSIDE_TX); + } + + boolean newReadOnly = isReadOnly || newTransactionLevel != Connection.TRANSACTION_SERIALIZABLE; + return emptyTx(newTransactionLevel, newReadOnly, isAutoCommit); + } + + public TxState withCommit(Session session) { + session.close(); + return this; + } + + public TxState withRollback(Session session) { + session.close(); + return this; + } + + public TxState withKeepAlive(Session session) { + session.close(); + return this; + } + + public TxState withDataQuery(Session session, String txID) { + if (txID != null && !txID.isEmpty()) { + return new TransactionInProgress(txID, session, this); + } + + session.close(); + return this; + } + + public Session getSession(YdbValidator validator) throws SQLException { + return createNewTableSession(validator); + } + } + + private class TransactionInProgress extends TxState { + private final String txID; + private final Session session; + private final TxState previos; + + TransactionInProgress(String id, Session session, TxState previosState) { + super(TxControl.id(id).setCommitTx(previosState.isAutoCommit), previosState); + this.txID = id; + this.session = session; + this.previos = previosState; + } + + @Override + public String toString() { + return "InTx" + transactionLevel() + "[" + txID + "]"; + } + + @Override + public String txID() { + return txID; + } + + @Override + public boolean isInsideTransaction() { + return true; + } + + @Override + public Session getSession(YdbValidator validator) throws SQLException { + return session; + } + + @Override + public TxState withCommit(Session session) { + session.close(); + return previos; + } + + @Override + public TxState withRollback(Session session) { + session.close(); + return previos; + } + + @Override + public TxState withKeepAlive(Session session) { + return this; + } + + @Override + public TxState withDataQuery(Session session, String txID) { + if (txID == null || txID.isEmpty()) { + if (this.session != session) { + session.close(); + } + this.session.close(); + return previos; + } + + if (txID.equals(txID())) { + if (this.session == session) { + return this; + } + this.session.close(); + return new TransactionInProgress(txID, session, previos); + } + + session.close(); + return this; + } + } + + private TxState emptyTx(int level, boolean isReadOnly, boolean isAutoCommit) throws SQLException { + TxControl txCtrl = txControl(level, isReadOnly, isAutoCommit); + return new TxState(txCtrl, level, isReadOnly, isAutoCommit); + } + + private TxState createTx(int level, boolean isAutoCommit) throws SQLException { + return emptyTx(level, level != Connection.TRANSACTION_SERIALIZABLE, isAutoCommit); + } + + private static TxControl txControl(int level, boolean isReadOnly, boolean isAutoCommit) throws SQLException { + if (!isReadOnly) { + // YDB support only one RW mode + if (level != Connection.TRANSACTION_SERIALIZABLE) { + throw new SQLException(YdbConst.UNSUPPORTED_TRANSACTION_LEVEL + level); + } + + return TxControl.serializableRw().setCommitTx(isAutoCommit); + } + + switch (level) { + case Connection.TRANSACTION_SERIALIZABLE: + return TxControl.snapshotRo().setCommitTx(isAutoCommit); + case YdbConst.ONLINE_CONSISTENT_READ_ONLY: + return TxControl.onlineRo().setAllowInconsistentReads(false).setCommitTx(isAutoCommit); + case YdbConst.ONLINE_INCONSISTENT_READ_ONLY: + return TxControl.onlineRo().setAllowInconsistentReads(true).setCommitTx(isAutoCommit); + case YdbConst.STALE_CONSISTENT_READ_ONLY: + return TxControl.staleRo().setCommitTx(isAutoCommit); + default: + throw new SQLException(YdbConst.UNSUPPORTED_TRANSACTION_LEVEL + level); + } + } +} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/context/YdbConfig.java b/jdbc/src/main/java/tech/ydb/jdbc/context/YdbConfig.java deleted file mode 100644 index ddb4907..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/context/YdbConfig.java +++ /dev/null @@ -1,83 +0,0 @@ -package tech.ydb.jdbc.context; - -import java.sql.SQLException; -import java.util.Objects; -import java.util.Properties; - -import tech.ydb.jdbc.settings.YdbClientProperties; -import tech.ydb.jdbc.settings.YdbConnectionProperties; -import tech.ydb.jdbc.settings.YdbConnectionProperty; -import tech.ydb.jdbc.settings.YdbJdbcTools; -import tech.ydb.jdbc.settings.YdbOperationProperties; -import tech.ydb.jdbc.settings.YdbProperties; - -/** - * - * @author Aleksandr Gorshenin - */ -// TODO: implement cache based on connection and client properties only (excluding operation properties) -public class YdbConfig { - private final String url; - private final Properties properties; - private final YdbProperties config; - - public YdbConfig(String url, Properties properties) throws SQLException { - this.url = url; - this.properties = properties; - this.config = YdbJdbcTools.from(url, properties); - } - - public Properties getSafeProps() { - Properties safe = new Properties(); - for (String key: properties.stringPropertyNames()) { - if (isSensetive(key)) { - safe.put(key, "***"); - } else { - safe.put(key, properties.get(key)); - } - } - return safe; - } - - private static boolean isSensetive(String key) { - return YdbConnectionProperty.TOKEN.getName().equalsIgnoreCase(key) - || "password".equalsIgnoreCase(key); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof YdbConfig)) { - return false; - } - YdbConfig that = (YdbConfig) o; - return Objects.equals(url, that.url) && Objects.equals(properties, that.properties); - } - - @Override - public int hashCode() { - return Objects.hash(url, properties); - } - - public String getUrl() { - return url; - } - - public String getSafeUrl() { - return config.getConnectionProperties().getSafeUrl(); - } - - public YdbConnectionProperties getConnectionProperties() { - return config.getConnectionProperties(); - } - - public YdbClientProperties getClientProperties() { - return config.getClientProperties(); - } - - public YdbOperationProperties getOperationProperties() { - return config.getOperationProperties(); - } -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/context/YdbContext.java b/jdbc/src/main/java/tech/ydb/jdbc/context/YdbContext.java index dda27ae..e67b4ff 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/context/YdbContext.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/context/YdbContext.java @@ -4,8 +4,6 @@ import java.sql.SQLException; import java.time.Duration; import java.util.Map; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; @@ -14,10 +12,10 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import tech.ydb.core.Result; import tech.ydb.core.UnexpectedResultException; import tech.ydb.core.grpc.GrpcTransport; import tech.ydb.core.grpc.GrpcTransportBuilder; +import tech.ydb.core.settings.BaseRequestSettings; import tech.ydb.jdbc.YdbConst; import tech.ydb.jdbc.YdbPrepareMode; import tech.ydb.jdbc.exception.ExceptionFactory; @@ -25,23 +23,21 @@ import tech.ydb.jdbc.query.JdbcQueryLexer; import tech.ydb.jdbc.query.YdbQuery; import tech.ydb.jdbc.query.YdbQueryBuilder; -import tech.ydb.jdbc.query.YdbQueryOptions; import tech.ydb.jdbc.query.params.BatchedParams; import tech.ydb.jdbc.query.params.InMemoryParams; import tech.ydb.jdbc.query.params.PreparedParams; -import tech.ydb.jdbc.settings.ParsedProperty; import tech.ydb.jdbc.settings.YdbClientProperties; -import tech.ydb.jdbc.settings.YdbClientProperty; +import tech.ydb.jdbc.settings.YdbConfig; import tech.ydb.jdbc.settings.YdbConnectionProperties; -import tech.ydb.jdbc.settings.YdbConnectionProperty; import tech.ydb.jdbc.settings.YdbOperationProperties; +import tech.ydb.jdbc.settings.YdbQueryProperties; +import tech.ydb.query.QueryClient; +import tech.ydb.query.impl.QueryClientImpl; import tech.ydb.scheme.SchemeClient; import tech.ydb.table.SessionRetryContext; import tech.ydb.table.TableClient; -import tech.ydb.table.description.TableDescription; import tech.ydb.table.impl.PooledTableClient; import tech.ydb.table.rpc.grpc.GrpcTableRpc; -import tech.ydb.table.settings.DescribeTableSettings; import tech.ydb.table.settings.PrepareDataQuerySettings; import tech.ydb.table.settings.RequestSettings; import tech.ydb.table.values.Type; @@ -54,17 +50,18 @@ public class YdbContext implements AutoCloseable { private static final Logger LOGGER = Logger.getLogger(YdbContext.class.getName()); - private static final int SESSION_POOL_DEFAULT_MIN_SIZE = 0; - private static final int SESSION_POOL_DEFAULT_MAX_SIZE = 50; private static final int SESSION_POOL_RESIZE_STEP = 50; private static final int SESSION_POOL_RESIZE_THRESHOLD = 10; private final YdbConfig config; + private final YdbOperationProperties operationProps; + private final YdbQueryProperties queryOptions; + private final GrpcTransport grpcTransport; private final PooledTableClient tableClient; + private final QueryClientImpl queryClient; private final SchemeClient schemeClient; - private final YdbQueryOptions queryOptions; private final SessionRetryContext retryCtx; private final Cache queriesCache; @@ -73,16 +70,28 @@ public class YdbContext implements AutoCloseable { private final boolean autoResizeSessionPool; private final AtomicInteger connectionsCount = new AtomicInteger(); - private YdbContext(YdbConfig config, GrpcTransport transport, PooledTableClient tableClient, boolean autoResize) { + private YdbContext( + YdbConfig config, + YdbOperationProperties operationProperties, + YdbQueryProperties queryProperties, + GrpcTransport transport, + PooledTableClient tableClient, + QueryClientImpl queryClient, + boolean autoResize + ) { this.config = config; - this.grpcTransport = Objects.requireNonNull(transport); - this.tableClient = Objects.requireNonNull(tableClient); - this.schemeClient = SchemeClient.newClient(transport).build(); - this.queryOptions = YdbQueryOptions.createFrom(config.getOperationProperties()); + + this.operationProps = operationProperties; + this.queryOptions = queryProperties; this.autoResizeSessionPool = autoResize; + + this.grpcTransport = transport; + this.tableClient = tableClient; + this.queryClient = queryClient; + this.schemeClient = SchemeClient.newClient(transport).build(); this.retryCtx = SessionRetryContext.create(tableClient).build(); - int cacheSize = config.getOperationProperties().getPreparedStatementCacheSize(); + int cacheSize = config.getPreparedStatementsCachecSize(); if (cacheSize > 0) { queriesCache = CacheBuilder.newBuilder().maximumSize(cacheSize).build(); queryParamsCache = CacheBuilder.newBuilder().maximumSize(cacheSize).build(); @@ -113,16 +122,28 @@ public TableClient getTableClient() { return tableClient; } + public QueryClient getQueryClient() { + return queryClient; + } + public String getUrl() { return config.getUrl(); } + public YdbExecutor createExecutor() throws SQLException { + if (config.isUseQueryService()) { + return new QueryServiceExecutor(this, operationProps.getTransactionLevel(), operationProps.isAutoCommit()); + } else { + return new TableServiceExecutor(this, operationProps.getTransactionLevel(), operationProps.isAutoCommit()); + } + } + public int getConnectionsCount() { return connectionsCount.get(); } public YdbOperationProperties getOperationProperties() { - return config.getOperationProperties(); + return operationProps; } @Override @@ -162,80 +183,46 @@ public void deregister() { public static YdbContext createContext(YdbConfig config) throws SQLException { try { - YdbConnectionProperties connProps = config.getConnectionProperties(); - YdbClientProperties clientProps = config.getClientProperties(); + LOGGER.log(Level.INFO, "Creating new YDB connection to {0}", config.getConnectionString()); + + YdbConnectionProperties connProps = new YdbConnectionProperties(config); + YdbClientProperties clientProps = new YdbClientProperties(config); + YdbOperationProperties operationProps = new YdbOperationProperties(config); + YdbQueryProperties queryProps = new YdbQueryProperties(config); + + GrpcTransportBuilder builder = GrpcTransport.forConnectionString(config.getConnectionString()); + connProps.applyToGrpcTransport(builder); + + // Use custom single thread scheduler + // because JDBC driver doesn't need to execute retries except for DISCOVERY + builder.withSchedulerFactory(() -> { + final String namePrefix = "ydb-jdbc-scheduler[" + config.hashCode() + "]-thread-"; + final AtomicInteger threadNumber = new AtomicInteger(1); + return Executors.newScheduledThreadPool(1, (Runnable r) -> { + Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement()); + t.setDaemon(true); + return t; + }); + }); - LOGGER.log(Level.INFO, "Creating new YDB connection to {0}", connProps.getConnectionString()); + GrpcTransport grpcTransport = builder.build(); - GrpcTransport grpcTransport = buildGrpcTransport(connProps); PooledTableClient.Builder tableClient = PooledTableClient.newClient( GrpcTableRpc.useTransport(grpcTransport) ); - boolean autoResize = buildTableClient(tableClient, clientProps); + QueryClientImpl.Builder queryClient = QueryClientImpl.newClient(grpcTransport); - return new YdbContext(config, grpcTransport, tableClient.build(), autoResize); + boolean autoResize = clientProps.applyToTableClient(tableClient, queryClient); + + return new YdbContext(config, operationProps, queryProps, grpcTransport, + tableClient.build(), queryClient.build(), autoResize); } catch (RuntimeException ex) { throw new SQLException("Cannot connect to YDB: " + ex.getMessage(), ex); } } - public static GrpcTransport buildGrpcTransport(YdbConnectionProperties props) { - GrpcTransportBuilder builder = GrpcTransport.forConnectionString(props.getConnectionString()); - for (Map.Entry, ParsedProperty> entry : props.getParams().entrySet()) { - if (entry.getValue() != null) { - entry.getKey().getSetter().accept(builder, entry.getValue().getParsedValue()); - } - } - - if (props.hasStaticCredentials()) { - builder = builder.withAuthProvider(props.getStaticCredentials()); - } - - // Use custom single thread scheduler because JDBC driver doesn't need to execute retries except for DISCOERY - builder.withSchedulerFactory(() -> { - final String namePrefix = "ydb-jdbc-scheduler[" + props.hashCode() +"]-thread-"; - final AtomicInteger threadNumber = new AtomicInteger(1); - return Executors.newScheduledThreadPool(1, (Runnable r) -> { - Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement()); - t.setDaemon(true); - return t; - }); - }); - - return builder.build(); - } - - private static boolean buildTableClient(TableClient.Builder builder, YdbClientProperties props) { - for (Map.Entry, ParsedProperty> entry : props.getParams().entrySet()) { - if (entry.getValue() != null) { - entry.getKey().getSetter().accept(builder, entry.getValue().getParsedValue()); - } - } - - ParsedProperty minSizeConfig = props.getProperty(YdbClientProperty.SESSION_POOL_SIZE_MIN); - ParsedProperty maxSizeConfig = props.getProperty(YdbClientProperty.SESSION_POOL_SIZE_MAX); - - if (minSizeConfig == null && maxSizeConfig == null) { - return true; - } - - int minSize = SESSION_POOL_DEFAULT_MIN_SIZE; - int maxSize = SESSION_POOL_DEFAULT_MAX_SIZE; - - if (minSizeConfig != null) { - minSize = Math.max(0, minSizeConfig.getParsedValue()); - maxSize = Math.max(maxSize, minSize); - } - if (maxSizeConfig != null) { - maxSize = Math.max(minSize + 1, maxSizeConfig.getParsedValue()); - } - - builder.sessionPoolSize(minSize, maxSize); - return false; - } - public > T withDefaultTimeout(T settings) { - Duration operation = config.getOperationProperties().getDeadlineTimeout(); + Duration operation = operationProps.getDeadlineTimeout(); if (!operation.isZero() && !operation.isNegative()) { settings.setOperationTimeout(operation); settings.setTimeout(operation.plusSeconds(1)); @@ -243,8 +230,13 @@ public > T withDefaultTimeout(T settings) { return settings; } - public CompletableFuture> describeTable(String tablePath, DescribeTableSettings settings) { - return retryCtx.supplyResult(session -> session.describeTable(tablePath, settings)); + public > T withRequestTimeout(T builder) { + Duration operation = operationProps.getDeadlineTimeout(); + if (operation.isNegative() || operation.isZero()) { + return builder; + } + + return builder.withRequestTimeout(operation); } public YdbQuery parseYdbQuery(String sql) throws SQLException { diff --git a/jdbc/src/main/java/tech/ydb/jdbc/context/YdbExecutor.java b/jdbc/src/main/java/tech/ydb/jdbc/context/YdbExecutor.java index 3d27988..44d6776 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/context/YdbExecutor.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/context/YdbExecutor.java @@ -1,117 +1,53 @@ package tech.ydb.jdbc.context; import java.sql.SQLException; -import java.sql.SQLWarning; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.logging.Logger; -import com.google.common.base.Stopwatch; - -import tech.ydb.core.Issue; -import tech.ydb.core.Result; -import tech.ydb.core.Status; -import tech.ydb.core.UnexpectedResultException; -import tech.ydb.jdbc.exception.ExceptionFactory; -import tech.ydb.table.Session; +import tech.ydb.jdbc.YdbConst; +import tech.ydb.jdbc.query.YdbQuery; +import tech.ydb.table.query.ExplainDataQueryResult; +import tech.ydb.table.query.Params; +import tech.ydb.table.result.ResultSetReader; /** * * @author Aleksandr Gorshenin */ -public class YdbExecutor { - @SuppressWarnings("NonConstantLogger") - private final Logger logger; - private final boolean isDebug; - private final List issues = new ArrayList<>(); - - public YdbExecutor(Logger logger) { - this.logger = logger; - this.isDebug = logger.isLoggable(Level.FINE); +public interface YdbExecutor { + default void ensureOpened() throws SQLException { + if (isClosed()) { + throw new SQLException(YdbConst.CLOSED_CONNECTION); + } } - public void clearWarnings() { - this.issues.clear(); - } + boolean isClosed(); - public SQLWarning toSQLWarnings() { - SQLWarning firstWarning = null; - SQLWarning warning = null; - for (Issue issue : issues) { - SQLWarning nextWarning = new SQLWarning(issue.toString(), null, issue.getCode()); - if (firstWarning == null) { - firstWarning = nextWarning; - } - if (warning != null) { - warning.setNextWarning(nextWarning); - } - warning = nextWarning; - } - return firstWarning; - } + String txID(); + int transactionLevel() throws SQLException; - public Session createSession(YdbContext ctx) throws SQLException { - Duration timeout = ctx.getOperationProperties().getSessionTimeout(); - return call("create session", () -> ctx.getTableClient().createSession(timeout)); - } + boolean isInsideTransaction() throws SQLException; + boolean isAutoCommit() throws SQLException; + boolean isReadOnly() throws SQLException; - public void execute(String msg, Supplier> runnableSupplier) throws SQLException { - if (!isDebug) { - simpleExecute(msg, runnableSupplier); - return; - } + void setTransactionLevel(int level) throws SQLException; + void setReadOnly(boolean readOnly) throws SQLException; + void setAutoCommit(boolean autoCommit) throws SQLException; - logger.finest(msg); - Stopwatch sw = Stopwatch.createStarted(); + void executeSchemeQuery(YdbContext ctx, YdbValidator validator, YdbQuery query) throws SQLException; - try { - simpleExecute(msg, runnableSupplier); - logger.log(Level.FINEST, "[{0}] OK ", sw.stop()); - } catch (SQLException | RuntimeException ex) { - logger.log(Level.FINE, "[{0}] {1} ", new Object[] { sw.stop(), ex.getMessage() }); - throw ex; - } - } + List executeDataQuery(YdbContext ctx, YdbValidator validator, YdbQuery query, + long timeout, boolean poolable, Params params) throws SQLException; - public T call(String msg, Supplier>> callSupplier) throws SQLException { - if (!isDebug) { - return simpleCall(msg, callSupplier); - } + ResultSetReader executeScanQuery(YdbContext ctx, YdbValidator validator, YdbQuery query, Params params) + throws SQLException; - logger.finest(msg); - Stopwatch sw = Stopwatch.createStarted(); + ExplainDataQueryResult executeExplainQuery(YdbContext ctx, YdbValidator validator, YdbQuery query) + throws SQLException; - try { - T value = simpleCall(msg, callSupplier); - logger.log(Level.FINEST, "[{0}] OK ", sw.stop()); - return value; - } catch (SQLException | RuntimeException ex) { - logger.log(Level.FINE, "[{0}] {1} ", new Object[] { sw.stop(), ex.getMessage() }); - throw ex; - } - } + void commit(YdbContext ctx, YdbValidator validator) throws SQLException; + void rollback(YdbContext ctx, YdbValidator validator) throws SQLException; - private T simpleCall(String msg, Supplier>> supplier) throws SQLException { - try { - Result result = supplier.get().join(); - issues.addAll(Arrays.asList(result.getStatus().getIssues())); - return result.getValue(); - } catch (UnexpectedResultException ex) { - throw ExceptionFactory.createException("Cannot call '" + msg + "' with " + ex.getStatus(), ex); - } - } + boolean isValid(YdbValidator validator, int timeout) throws SQLException; - private void simpleExecute(String msg, Supplier> supplier) throws SQLException { - Status status = supplier.get().join(); - issues.addAll(Arrays.asList(status.getIssues())); - if (!status.isSuccess()) { - throw ExceptionFactory.createException("Cannot execute '" + msg + "' with " + status, - new UnexpectedResultException("Unexpected status", status)); - } - } + void close(); } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/context/YdbTxState.java b/jdbc/src/main/java/tech/ydb/jdbc/context/YdbTxState.java deleted file mode 100644 index 602dbb6..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/context/YdbTxState.java +++ /dev/null @@ -1,234 +0,0 @@ -package tech.ydb.jdbc.context; - -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; - -import tech.ydb.jdbc.YdbConst; -import tech.ydb.table.Session; -import tech.ydb.table.transaction.TxControl; - -/** - * - * @author Aleksandr Gorshenin - */ -public class YdbTxState { - private final int transactionLevel; - private final boolean isReadOnly; - private final boolean isAutoCommit; - - private final TxControl txControl; - - protected YdbTxState(TxControl txControl, int level, boolean isReadOnly, boolean isAutoCommit) { - this.transactionLevel = level; - this.isReadOnly = isReadOnly; - this.isAutoCommit = isAutoCommit; - this.txControl = txControl; - } - - protected YdbTxState(TxControl txControl, YdbTxState other) { - this.transactionLevel = other.transactionLevel; - this.isReadOnly = other.isReadOnly; - this.isAutoCommit = other.isAutoCommit; - this.txControl = txControl; - } - - @Override - public String toString() { - return "NoTx"; - } - - public String txID() { - return null; - } - - public boolean isInsideTransaction() { - return false; - } - - public TxControl txControl() { - return txControl; - } - - public boolean isAutoCommit() { - return isAutoCommit; - } - - public boolean isReadOnly() { - return isReadOnly; - } - - public int transactionLevel() { - return transactionLevel; - } - - public YdbTxState withAutoCommit(boolean newAutoCommit) throws SQLException { - if (newAutoCommit == isAutoCommit) { - return this; - } - - if (isInsideTransaction()) { - throw new SQLFeatureNotSupportedException(YdbConst.CHANGE_ISOLATION_INSIDE_TX); - } - - return emptyTx(transactionLevel, isReadOnly, newAutoCommit); - } - - public YdbTxState withReadOnly(boolean newReadOnly) throws SQLException { - if (newReadOnly == isReadOnly()) { - return this; - } - - if (isInsideTransaction()) { - throw new SQLFeatureNotSupportedException(YdbConst.READONLY_INSIDE_TRANSACTION); - } - - return emptyTx(transactionLevel, newReadOnly, isAutoCommit); - } - - public YdbTxState withTransactionLevel(int newTransactionLevel) throws SQLException { - if (newTransactionLevel == transactionLevel) { - return this; - } - - if (isInsideTransaction()) { - throw new SQLFeatureNotSupportedException(YdbConst.CHANGE_ISOLATION_INSIDE_TX); - } - - boolean newReadOnly = isReadOnly || newTransactionLevel != Connection.TRANSACTION_SERIALIZABLE; - return emptyTx(newTransactionLevel, newReadOnly, isAutoCommit); - } - - public YdbTxState withCommit(Session session) { - session.close(); - return this; - } - - public YdbTxState withRollback(Session session) { - session.close(); - return this; - } - - public YdbTxState withKeepAlive(Session session) { - session.close(); - return this; - } - - public YdbTxState withDataQuery(Session session, String txID) { - if (txID != null && !txID.isEmpty()) { - return new TransactionInProgress(txID, session, this); - } - - session.close(); - return this; - } - - public Session getSession(YdbContext ctx, YdbExecutor executor) throws SQLException { - return executor.createSession(ctx); - } - - private static TxControl txControl(int level, boolean isReadOnly, boolean isAutoCommit) throws SQLException { - if (!isReadOnly) { - // YDB support only one RW mode - if (level != Connection.TRANSACTION_SERIALIZABLE) { - throw new SQLException(YdbConst.UNSUPPORTED_TRANSACTION_LEVEL + level); - } - - return TxControl.serializableRw().setCommitTx(isAutoCommit); - } - - switch (level) { - case Connection.TRANSACTION_SERIALIZABLE: - return TxControl.snapshotRo().setCommitTx(isAutoCommit); - case YdbConst.ONLINE_CONSISTENT_READ_ONLY: - return TxControl.onlineRo().setAllowInconsistentReads(false).setCommitTx(isAutoCommit); - case YdbConst.ONLINE_INCONSISTENT_READ_ONLY: - return TxControl.onlineRo().setAllowInconsistentReads(true).setCommitTx(isAutoCommit); - case YdbConst.STALE_CONSISTENT_READ_ONLY: - return TxControl.staleRo().setCommitTx(isAutoCommit); - default: - throw new SQLException(YdbConst.UNSUPPORTED_TRANSACTION_LEVEL + level); - } - } - - private static YdbTxState emptyTx(int level, boolean isReadOnly, boolean isAutoCommit) throws SQLException { - TxControl tx = txControl(level, isReadOnly, isAutoCommit); - return new YdbTxState(tx, level, isReadOnly, isAutoCommit); - } - - public static YdbTxState create(int level, boolean isAutoCommit) throws SQLException { - return emptyTx(level, level != Connection.TRANSACTION_SERIALIZABLE, isAutoCommit); - } - - private static class TransactionInProgress extends YdbTxState { - private final String txID; - private final Session session; - private final YdbTxState previos; - - TransactionInProgress(String id, Session session, YdbTxState previosState) { - super(TxControl.id(id).setCommitTx(previosState.isAutoCommit), previosState); - this.txID = id; - this.session = session; - this.previos = previosState; - } - - @Override - public String toString() { - return "InTx" + transactionLevel() + "[" + txID + "]"; - } - - @Override - public String txID() { - return txID; - } - - @Override - public boolean isInsideTransaction() { - return true; - } - - @Override - public Session getSession(YdbContext ctx, YdbExecutor executor) throws SQLException { - return session; - } - - @Override - public YdbTxState withCommit(Session session) { - session.close(); - return previos; - } - - @Override - public YdbTxState withRollback(Session session) { - session.close(); - return previos; - } - - @Override - public YdbTxState withKeepAlive(Session session) { - return this; - } - - @Override - public YdbTxState withDataQuery(Session session, String txID) { - if (txID == null || txID.isEmpty()) { - if (this.session != session) { - session.close(); - } - this.session.close(); - return previos; - } - - if (txID.equals(txID())) { - if (this.session == session) { - return this; - } - this.session.close(); - return new TransactionInProgress(txID, session, previos); - } - - session.close(); - return this; - } - } -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/context/YdbValidator.java b/jdbc/src/main/java/tech/ydb/jdbc/context/YdbValidator.java new file mode 100644 index 0000000..2d6fd27 --- /dev/null +++ b/jdbc/src/main/java/tech/ydb/jdbc/context/YdbValidator.java @@ -0,0 +1,116 @@ +package tech.ydb.jdbc.context; + +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.common.base.Stopwatch; + +import tech.ydb.core.Issue; +import tech.ydb.core.Result; +import tech.ydb.core.Status; +import tech.ydb.core.UnexpectedResultException; +import tech.ydb.jdbc.exception.ExceptionFactory; + +/** + * + * @author Aleksandr Gorshenin + */ +public class YdbValidator { + @SuppressWarnings("NonConstantLogger") + private final Logger logger; + private final boolean isDebug; + private final List issues = new ArrayList<>(); + + public YdbValidator(Logger logger) { + this.logger = logger; + this.isDebug = logger.isLoggable(Level.FINE); + } + + public SQLWarning toSQLWarnings() { + SQLWarning firstWarning = null; + SQLWarning warning = null; + for (Issue issue : issues) { + SQLWarning nextWarning = new SQLWarning(issue.toString(), null, issue.getCode()); + if (firstWarning == null) { + firstWarning = nextWarning; + } + if (warning != null) { + warning.setNextWarning(nextWarning); + } + warning = nextWarning; + } + return firstWarning; + } + + public void addStatusIssues(Status status) { + issues.addAll(Arrays.asList(status.getIssues())); + } + + public void clearWarnings() { + this.issues.clear(); + } + + public void execute(String msg, Supplier> fn) throws SQLException { + if (!isDebug) { + runImpl(msg, fn); + return; + } + + logger.finest(msg); + Stopwatch sw = Stopwatch.createStarted(); + + try { + runImpl(msg, fn); + logger.log(Level.FINEST, "[{0}] OK ", sw.stop()); + } catch (SQLException | RuntimeException ex) { + logger.log(Level.FINE, "[{0}] {1} ", new Object[] {sw.stop(), ex.getMessage()}); + throw ex; + } + } + + public R call(String msg, Supplier>> fn) throws SQLException { + if (!isDebug) { + return callImpl(msg, fn); + } + + logger.finest(msg); + Stopwatch sw = Stopwatch.createStarted(); + + try { + R value = callImpl(msg, fn); + logger.log(Level.FINEST, "[{0}] OK ", sw.stop()); + return value; + } catch (SQLException | RuntimeException ex) { + logger.log(Level.FINE, "[{0}] {1} ", new Object[] {sw.stop(), ex.getMessage()}); + throw ex; + } + } + + private void runImpl(String msg, Supplier> fn) throws SQLException { + Status status = fn.get().join(); + addStatusIssues(status); + + if (!status.isSuccess()) { + throw ExceptionFactory.createException("Cannot execute '" + msg + "' with " + status, + new UnexpectedResultException("Unexpected status", status)); + } + } + + private R callImpl(String msg, Supplier>> fn) throws SQLException { + try { + Result result = fn.get().join(); + addStatusIssues(result.getStatus()); + return result.getValue(); + } catch (UnexpectedResultException ex) { + throw ExceptionFactory.createException("Cannot call '" + msg + "' with " + ex.getStatus(), ex); + } + } + +} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/context/YdbWarnings.java b/jdbc/src/main/java/tech/ydb/jdbc/context/YdbWarnings.java deleted file mode 100644 index f7aa349..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/context/YdbWarnings.java +++ /dev/null @@ -1,41 +0,0 @@ -package tech.ydb.jdbc.context; - -import java.sql.SQLWarning; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import tech.ydb.core.Issue; -import tech.ydb.core.Status; - -/** - * - * @author Aleksandr Gorshenin - */ -public class YdbWarnings { - private final List issues = new ArrayList<>(); - - public void addStatusIssues(Status status) { - issues.addAll(Arrays.asList(status.getIssues())); - } - - public void clearWarnings() { - this.issues.clear(); - } - - public SQLWarning toSQLWarnings() { - SQLWarning firstWarning = null; - SQLWarning warning = null; - for (Issue issue : issues) { - SQLWarning nextWarning = new SQLWarning(issue.toString(), null, issue.getCode()); - if (firstWarning == null) { - firstWarning = nextWarning; - } - if (warning != null) { - warning.setNextWarning(nextWarning); - } - warning = nextWarning; - } - return firstWarning; - } -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/exception/ExceptionFactory.java b/jdbc/src/main/java/tech/ydb/jdbc/exception/ExceptionFactory.java index aa8a05c..ced7fa7 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/exception/ExceptionFactory.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/exception/ExceptionFactory.java @@ -10,6 +10,8 @@ * @author Aleksandr Gorshenin */ public class ExceptionFactory { + private ExceptionFactory() { } + static String getSQLState(StatusCode status) { // TODO: Add SQLSTATE message with order with https://en.wikipedia.org/wiki/SQLSTATE return null; diff --git a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbConditionallyRetryableException.java b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbConditionallyRetryableException.java index 2a45d0a..5a725f1 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbConditionallyRetryableException.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbConditionallyRetryableException.java @@ -5,7 +5,7 @@ import tech.ydb.core.Status; import tech.ydb.core.UnexpectedResultException; -public class YdbConditionallyRetryableException extends SQLTransientException { +public class YdbConditionallyRetryableException extends SQLTransientException implements YdbStatusable { private static final long serialVersionUID = 2155728765762467203L; private final Status status; @@ -14,6 +14,7 @@ public class YdbConditionallyRetryableException extends SQLTransientException { this.status = cause.getStatus(); } + @Override public Status getStatus() { return status; } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbRetryableException.java b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbRetryableException.java index ae1c384..2fe9dfc 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbRetryableException.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbRetryableException.java @@ -5,7 +5,7 @@ import tech.ydb.core.Status; import tech.ydb.core.UnexpectedResultException; -public class YdbRetryableException extends SQLRecoverableException { +public class YdbRetryableException extends SQLRecoverableException implements YdbStatusable { private static final long serialVersionUID = -7171306648623023924L; private final Status status; @@ -14,6 +14,7 @@ public class YdbRetryableException extends SQLRecoverableException { this.status = cause.getStatus(); } + @Override public Status getStatus() { return status; } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbSQLException.java b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbSQLException.java index c8af441..da4ca67 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbSQLException.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbSQLException.java @@ -5,7 +5,7 @@ import tech.ydb.core.Status; import tech.ydb.core.UnexpectedResultException; -public class YdbSQLException extends SQLException { +public class YdbSQLException extends SQLException implements YdbStatusable { private static final long serialVersionUID = 6204553083196091739L; private final Status status; @@ -15,6 +15,7 @@ public class YdbSQLException extends SQLException { this.status = cause.getStatus(); } + @Override public Status getStatus() { return status; } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbStatusable.java b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbStatusable.java new file mode 100644 index 0000000..5df860c --- /dev/null +++ b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbStatusable.java @@ -0,0 +1,11 @@ +package tech.ydb.jdbc.exception; + +import tech.ydb.core.Status; + +/** + * + * @author Aleksandr Gorshenin + */ +public interface YdbStatusable { + Status getStatus(); +} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbTimeoutException.java b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbTimeoutException.java index 5c6a283..3a44d7e 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbTimeoutException.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbTimeoutException.java @@ -9,7 +9,7 @@ * * @author Aleksandr Gorshenin */ -public class YdbTimeoutException extends SQLTimeoutException { +public class YdbTimeoutException extends SQLTimeoutException implements YdbStatusable { private static final long serialVersionUID = -6309565506198809222L; private final Status status; @@ -19,6 +19,7 @@ public class YdbTimeoutException extends SQLTimeoutException { this.status = cause.getStatus(); } + @Override public Status getStatus() { return status; } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbUnavailbaleException.java b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbUnavailbaleException.java index 355150c..cd1c913 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbUnavailbaleException.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbUnavailbaleException.java @@ -9,7 +9,7 @@ * * @author Aleksandr Gorshenin */ -public class YdbUnavailbaleException extends SQLTransientConnectionException { +public class YdbUnavailbaleException extends SQLTransientConnectionException implements YdbStatusable { private static final long serialVersionUID = 7162301155514557562L; private final Status status; @@ -19,6 +19,7 @@ public class YdbUnavailbaleException extends SQLTransientConnectionException { this.status = cause.getStatus(); } + @Override public Status getStatus() { return status; } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/impl/BaseYdbStatement.java b/jdbc/src/main/java/tech/ydb/jdbc/impl/BaseYdbStatement.java index f8a310f..53ad958 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/BaseYdbStatement.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/impl/BaseYdbStatement.java @@ -5,7 +5,6 @@ import java.sql.SQLFeatureNotSupportedException; import java.sql.SQLWarning; import java.sql.Statement; -import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -17,15 +16,13 @@ import tech.ydb.jdbc.YdbResultSet; import tech.ydb.jdbc.YdbStatement; import tech.ydb.jdbc.common.FixedResultSetFactory; -import tech.ydb.jdbc.context.YdbExecutor; +import tech.ydb.jdbc.context.YdbValidator; import tech.ydb.jdbc.query.YdbExpression; import tech.ydb.jdbc.query.YdbQuery; import tech.ydb.jdbc.settings.YdbOperationProperties; -import tech.ydb.table.query.DataQueryResult; import tech.ydb.table.query.ExplainDataQueryResult; import tech.ydb.table.query.Params; import tech.ydb.table.result.ResultSetReader; -import tech.ydb.table.settings.ExecuteDataQuerySettings; /** * @@ -38,13 +35,13 @@ public abstract class BaseYdbStatement implements YdbStatement { // TODO: YDB doesn't return the count of affected rows, so we use little hach to return always 1 private static final YdbResult HAS_UPDATED = new YdbResult(1); - private static FixedResultSetFactory EXPLAIN_RS_FACTORY = FixedResultSetFactory.newBuilder() + private static final FixedResultSetFactory EXPLAIN_RS_FACTORY = FixedResultSetFactory.newBuilder() .addTextColumn(YdbConst.EXPLAIN_COLUMN_AST) .addTextColumn(YdbConst.EXPLAIN_COLUMN_PLAN) .build(); private final YdbConnection connection; - private final YdbExecutor executor; + private final YdbValidator validator; private final int resultSetType; private final int maxRows; private final boolean failOnTruncatedResult; @@ -56,7 +53,7 @@ public abstract class BaseYdbStatement implements YdbStatement { public BaseYdbStatement(Logger logger, YdbConnection connection, int resultSetType, boolean isPoolable) { this.connection = Objects.requireNonNull(connection); - this.executor = new YdbExecutor(logger); + this.validator = new YdbValidator(logger); this.resultSetType = resultSetType; this.isPoolable = isPoolable; @@ -88,12 +85,12 @@ public int getResultSetType() { @Override public SQLWarning getWarnings() { - return executor.toSQLWarnings(); + return validator.toSQLWarnings(); } @Override public void clearWarnings() { - executor.clearWarnings(); + validator.clearWarnings(); } @Override @@ -169,7 +166,7 @@ protected boolean updateState(List results) { } protected List executeSchemeQuery(YdbQuery query) throws SQLException { - connection.executeSchemeQuery(query, executor); + connection.executeSchemeQuery(query, validator); int expressionsCount = query.getExpressions().isEmpty() ? 1 : query.getExpressions().size(); List results = new ArrayList<>(); @@ -180,7 +177,7 @@ protected List executeSchemeQuery(YdbQuery query) throws SQLException } protected List executeExplainQuery(YdbQuery query) throws SQLException { - ExplainDataQueryResult explainDataQuery = connection.executeExplainQuery(query, executor); + ExplainDataQueryResult explainDataQuery = connection.executeExplainQuery(query, validator); ResultSetReader result = EXPLAIN_RS_FACTORY.createResultSet() .newRow() @@ -193,25 +190,13 @@ protected List executeExplainQuery(YdbQuery query) throws SQLExceptio } protected List executeScanQuery(YdbQuery query, Params params) throws SQLException { - ResultSetReader result = connection.executeScanQuery(query, executor, params); + ResultSetReader result = connection.executeScanQuery(query, validator, params); return Collections.singletonList(new YdbResult(new YdbResultSetImpl(this, result))); } protected List executeDataQuery(YdbQuery query, Params params) throws SQLException { - ExecuteDataQuerySettings settings = new ExecuteDataQuerySettings(); - - int timeout = getQueryTimeout(); - if (timeout > 0) { - settings = settings - .setOperationTimeout(Duration.ofSeconds(timeout)) - .setTimeout(Duration.ofSeconds(timeout + 1)); - } - - if (!isPoolable()) { - settings = settings.disableQueryCache(); - } - - DataQueryResult result = connection.executeDataQuery(query, executor, settings, params); + List resultSets = connection + .executeDataQuery(query, validator, getQueryTimeout(), isPoolable(), params); List results = new ArrayList<>(); int idx = 0; @@ -225,8 +210,8 @@ protected List executeDataQuery(YdbQuery query, Params params) throws continue; } - if (idx < result.getResultSetCount()) { - ResultSetReader rs = result.getResultSet(idx); + if (idx < resultSets.size()) { + ResultSetReader rs = resultSets.get(idx); if (failOnTruncatedResult && rs.isTruncated()) { String msg = String.format(YdbConst.RESULT_IS_TRUNCATED, idx, rs.getRowCount()); throw new SQLException(msg); @@ -236,8 +221,8 @@ protected List executeDataQuery(YdbQuery query, Params params) throws } } - while (idx < result.getResultSetCount()) { - ResultSetReader rs = result.getResultSet(idx); + while (idx < resultSets.size()) { + ResultSetReader rs = resultSets.get(idx); if (failOnTruncatedResult && rs.isTruncated()) { String msg = String.format(YdbConst.RESULT_IS_TRUNCATED, idx, rs.getRowCount()); throw new SQLException(msg); diff --git a/jdbc/src/main/java/tech/ydb/jdbc/impl/MetaDataTables.java b/jdbc/src/main/java/tech/ydb/jdbc/impl/MetaDataTables.java index b706022..24496c4 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/MetaDataTables.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/impl/MetaDataTables.java @@ -12,7 +12,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getProcedures(java.lang.String, java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory PROCEDURES = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory PROCEDURES = FixedResultSetFactory.newBuilder() .addTextColumn("PROCEDURE_CAT") .addTextColumn("PROCEDURE_SCHEM") .addTextColumn("PROCEDURE_NAME") @@ -27,7 +27,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getProcedureColumns(java.lang.String, java.lang.String, java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory PROCEDURE_COLUMNS = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory PROCEDURE_COLUMNS = FixedResultSetFactory.newBuilder() .addTextColumn("PROCEDURE_CAT") .addTextColumn("PROCEDURE_SCHEM") .addTextColumn("PROCEDURE_NAME") @@ -53,7 +53,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getTables(java.lang.String, java.lang.String, java.lang.String, java.lang.String[]) */ - public final static FixedResultSetFactory TABLES = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory TABLES = FixedResultSetFactory.newBuilder() .addTextColumn("TABLE_CAT") .addTextColumn("TABLE_SCHEM") .addTextColumn("TABLE_NAME") @@ -69,21 +69,21 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getCatalogs() */ - public final static FixedResultSetFactory CATALOGS = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory CATALOGS = FixedResultSetFactory.newBuilder() .addTextColumn("TABLE_CAT") .build(); /** * @see DatabaseMetaData#getTableTypes() */ - public final static FixedResultSetFactory TABLE_TYPES = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory TABLE_TYPES = FixedResultSetFactory.newBuilder() .addTextColumn("TABLE_TYPE") .build(); /** * @see DatabaseMetaData#getColumns(java.lang.String, java.lang.String, java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory COLUMNS = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory COLUMNS = FixedResultSetFactory.newBuilder() .addTextColumn("TABLE_CAT") .addTextColumn("TABLE_SCHEM") .addTextColumn("TABLE_NAME") @@ -113,7 +113,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getColumnPrivileges(java.lang.String, java.lang.String, java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory COLUMN_PRIVILEGES = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory COLUMN_PRIVILEGES = FixedResultSetFactory.newBuilder() .addTextColumn("TABLE_CAT") .addTextColumn("TABLE_SCHEM") .addTextColumn("TABLE_NAME") @@ -127,7 +127,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getTablePrivileges(java.lang.String, java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory TABLE_PRIVILEGES = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory TABLE_PRIVILEGES = FixedResultSetFactory.newBuilder() .addTextColumn("TABLE_CAT") .addTextColumn("TABLE_SCHEM") .addTextColumn("TABLE_NAME") @@ -140,7 +140,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getBestRowIdentifier(java.lang.String, java.lang.String, java.lang.String, int, boolean) */ - public final static FixedResultSetFactory BEST_ROW_IDENTIFIERS = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory BEST_ROW_IDENTIFIERS = FixedResultSetFactory.newBuilder() .addShortColumn("SCOPE") .addTextColumn("COLUMN_NAME") .addIntColumn("DATA_TYPE") @@ -154,7 +154,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getVersionColumns(java.lang.String, java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory VERSION_COLUMNS = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory VERSION_COLUMNS = FixedResultSetFactory.newBuilder() .addShortColumn("SCOPE") .addTextColumn("COLUMN_NAME") .addIntColumn("DATA_TYPE") @@ -168,7 +168,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getPrimaryKeys(java.lang.String, java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory PRIMARY_KEYS = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory PRIMARY_KEYS = FixedResultSetFactory.newBuilder() .addTextColumn("TABLE_CAT") .addTextColumn("TABLE_SCHEM") .addTextColumn("TABLE_NAME") @@ -180,7 +180,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getImportedKeys(java.lang.String, java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory IMPORTED_KEYS = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory IMPORTED_KEYS = FixedResultSetFactory.newBuilder() .addTextColumn("PKTABLE_CAT") .addTextColumn("PKTABLE_SCHEM") .addTextColumn("PKTABLE_NAME") @@ -200,7 +200,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getExportedKeys(java.lang.String, java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory EXPORTED_KEYS = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory EXPORTED_KEYS = FixedResultSetFactory.newBuilder() .addTextColumn("PKTABLE_CAT") .addTextColumn("PKTABLE_SCHEM") .addTextColumn("PKTABLE_NAME") @@ -221,7 +221,7 @@ public class MetaDataTables { * @see DatabaseMetaData#getCrossReference(java.lang.String, java.lang.String, java.lang.String, java.lang.String, * java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory CROSS_REFERENCES = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory CROSS_REFERENCES = FixedResultSetFactory.newBuilder() .addTextColumn("PKTABLE_CAT") .addTextColumn("PKTABLE_SCHEM") .addTextColumn("PKTABLE_NAME") @@ -241,7 +241,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getTypeInfo() */ - public final static FixedResultSetFactory TYPE_INFOS = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory TYPE_INFOS = FixedResultSetFactory.newBuilder() .addTextColumn("TYPE_NAME") .addIntColumn("DATA_TYPE") .addIntColumn("PRECISION") @@ -265,7 +265,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getIndexInfo(java.lang.String, java.lang.String, java.lang.String, boolean, boolean) */ - public final static FixedResultSetFactory INDEX_INFOS = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory INDEX_INFOS = FixedResultSetFactory.newBuilder() .addTextColumn("TABLE_CAT") .addTextColumn("TABLE_SCHEM") .addTextColumn("TABLE_NAME") @@ -284,7 +284,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getUDTs(java.lang.String, java.lang.String, java.lang.String, int[]) */ - public final static FixedResultSetFactory UDTS = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory UDTS = FixedResultSetFactory.newBuilder() .addTextColumn("TABLE_CAT") .addTextColumn("TABLE_SCHEM") .addTextColumn("TABLE_NAME") @@ -297,7 +297,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getSuperTypes(java.lang.String, java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory SUPER_TYPES = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory SUPER_TYPES = FixedResultSetFactory.newBuilder() .addTextColumn("TYPE_CAT") .addTextColumn("TYPE_SCHEM") .addTextColumn("TYPE_NAME") @@ -309,7 +309,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getSuperTables(java.lang.String, java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory SUPER_TABLES = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory SUPER_TABLES = FixedResultSetFactory.newBuilder() .addTextColumn("TABLE_CAT") .addTextColumn("TABLE_SCHEM") .addTextColumn("TABLE_NAME") @@ -319,7 +319,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getAttributes(java.lang.String, java.lang.String, java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory ATTRIBUTES = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory ATTRIBUTES = FixedResultSetFactory.newBuilder() .addTextColumn("TYPE_CAT") .addTextColumn("TYPE_SCHEM") .addTextColumn("TYPE_NAME") @@ -346,7 +346,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getSchemas(java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory SCHEMAS = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory SCHEMAS = FixedResultSetFactory.newBuilder() .addTextColumn("TABLE_SCHEM") .addTextColumn("TABLE_CATALOG") .build(); @@ -354,7 +354,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getClientInfoProperties() */ - public final static FixedResultSetFactory CLIENT_INFO_PROPERTIES = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory CLIENT_INFO_PROPERTIES = FixedResultSetFactory.newBuilder() .addTextColumn("NAME") .addIntColumn("MAX_LEN") .addTextColumn("DEFAULT_VALUE") @@ -364,7 +364,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getFunctions(java.lang.String, java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory FUNCTIONS = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory FUNCTIONS = FixedResultSetFactory.newBuilder() .addTextColumn("FUNCTION_CAT") .addTextColumn("FUNCTION_SCHEM") .addTextColumn("FUNCTION_NAME") @@ -376,7 +376,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getFunctionColumns(java.lang.String, java.lang.String, java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory FUNCTION_COLUMNS = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory FUNCTION_COLUMNS = FixedResultSetFactory.newBuilder() .addTextColumn("FUNCTION_CAT") .addTextColumn("FUNCTION_SCHEM") .addTextColumn("FUNCTION_NAME") @@ -399,7 +399,7 @@ public class MetaDataTables { /** * @see DatabaseMetaData#getPseudoColumns(java.lang.String, java.lang.String, java.lang.String, java.lang.String) */ - public final static FixedResultSetFactory PSEUDO_COLUMNS = FixedResultSetFactory.newBuilder() + public static final FixedResultSetFactory PSEUDO_COLUMNS = FixedResultSetFactory.newBuilder() .addTextColumn("TABLE_CAT") .addTextColumn("TABLE_SCHEM") .addTextColumn("TABLE_NAME") @@ -413,4 +413,6 @@ public class MetaDataTables { .addIntColumn("CHAR_OCTET_LENGTH") .addTextColumn("IS_NULLABLE") .build(); + + private MetaDataTables() { } } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbConnectionImpl.java b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbConnectionImpl.java index 0bcefff..477146e 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbConnectionImpl.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbConnectionImpl.java @@ -1,6 +1,5 @@ package tech.ydb.jdbc.impl; - import java.sql.Array; import java.sql.Blob; import java.sql.CallableStatement; @@ -14,19 +13,14 @@ import java.sql.Savepoint; import java.sql.Statement; import java.sql.Struct; -import java.time.Duration; -import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; -import com.google.common.base.Suppliers; - import tech.ydb.jdbc.YdbConnection; import tech.ydb.jdbc.YdbConst; import tech.ydb.jdbc.YdbDatabaseMetaData; @@ -36,47 +30,34 @@ import tech.ydb.jdbc.YdbTypes; import tech.ydb.jdbc.context.YdbContext; import tech.ydb.jdbc.context.YdbExecutor; -import tech.ydb.jdbc.context.YdbTxState; +import tech.ydb.jdbc.context.YdbValidator; import tech.ydb.jdbc.query.JdbcParams; import tech.ydb.jdbc.query.QueryType; import tech.ydb.jdbc.query.YdbQuery; import tech.ydb.jdbc.settings.FakeTxMode; import tech.ydb.jdbc.settings.YdbOperationProperties; -import tech.ydb.table.Session; -import tech.ydb.table.query.DataQueryResult; import tech.ydb.table.query.ExplainDataQueryResult; import tech.ydb.table.query.Params; import tech.ydb.table.result.ResultSetReader; -import tech.ydb.table.result.impl.ProtoValueReaders; -import tech.ydb.table.settings.CommitTxSettings; -import tech.ydb.table.settings.ExecuteDataQuerySettings; -import tech.ydb.table.settings.ExecuteScanQuerySettings; -import tech.ydb.table.settings.ExecuteSchemeQuerySettings; -import tech.ydb.table.settings.ExplainDataQuerySettings; -import tech.ydb.table.settings.KeepAliveSessionSettings; -import tech.ydb.table.settings.RollbackTxSettings; public class YdbConnectionImpl implements YdbConnection { private static final Logger LOGGER = Logger.getLogger(YdbConnectionImpl.class.getName()); private final YdbContext ctx; + private final YdbValidator validator; private final YdbExecutor executor; - private final Supplier metaDataSupplier; private final FakeTxMode scanQueryTxMode; private final FakeTxMode schemeQueryTxMode; - private volatile YdbTxState state; - public YdbConnectionImpl(YdbContext context) throws SQLException { this.ctx = context; - this.metaDataSupplier = Suppliers.memoize(() -> new YdbDatabaseMetaDataImpl(this))::get; - this.executor = new YdbExecutor(LOGGER); YdbOperationProperties props = ctx.getOperationProperties(); this.scanQueryTxMode = props.getScanQueryTxMode(); this.schemeQueryTxMode = props.getSchemeQueryTxMode(); - this.state = YdbTxState.create(props.getTransactionLevel(), props.isAutoCommit()); + this.validator = new YdbValidator(LOGGER); + this.executor = ctx.createExecutor(); this.ctx.register(); } @@ -101,19 +82,10 @@ public String nativeSQL(String sql) { } } - private void updateState(YdbTxState newState) { - if (this.state == newState) { - return; - } - - LOGGER.log(Level.FINE, "update tx state: {0} -> {1}", new Object[] { state, newState }); - this.state = newState; - } - @Override public void setAutoCommit(boolean autoCommit) throws SQLException { - ensureOpened(); - if (autoCommit == state.isAutoCommit()) { + executor.ensureOpened(); + if (autoCommit == executor.isAutoCommit()) { return; } @@ -121,57 +93,23 @@ public void setAutoCommit(boolean autoCommit) throws SQLException { if (autoCommit) { commit(); } - updateState(state.withAutoCommit(autoCommit)); + + executor.setAutoCommit(autoCommit); } @Override public boolean getAutoCommit() throws SQLException { - ensureOpened(); - return state.isAutoCommit(); + return executor.isAutoCommit(); } @Override public void commit() throws SQLException { - ensureOpened(); - - if (!state.isInsideTransaction()) { - return; - } - - Session session = state.getSession(ctx, executor); - CommitTxSettings settings = ctx.withDefaultTimeout(new CommitTxSettings()); - - try { - executor.clearWarnings(); - executor.execute( - "Commit TxId: " + state.txID(), - () -> session.commitTransaction(state.txID(), settings) - ); - } finally { - updateState(state.withCommit(session)); - } + executor.commit(ctx, validator); } @Override public void rollback() throws SQLException { - ensureOpened(); - - if (!state.isInsideTransaction()) { - return; - } - - Session session = state.getSession(ctx, executor); - RollbackTxSettings settings = ctx.withDefaultTimeout(new RollbackTxSettings()); - - try { - executor.clearWarnings(); - executor.execute( - "Rollback TxId: " + state.txID(), - () -> session.rollbackTransaction(state.txID(), settings) - ); - } finally { - updateState(state.withRollback(session)); - } + executor.rollback(ctx, validator); } @Override @@ -181,30 +119,32 @@ public void close() throws SQLException { } commit(); // like Oracle - executor.clearWarnings(); - state = null; + validator.clearWarnings(); + executor.close(); ctx.deregister(); } @Override public boolean isClosed() { - return state == null; + return executor.isClosed(); } @Override public YdbDatabaseMetaData getMetaData() { - return metaDataSupplier.get(); + return new YdbDatabaseMetaDataImpl(this); } @Override public void setReadOnly(boolean readOnly) throws SQLException { - ensureOpened(); - state = state.withReadOnly(readOnly); + if (executor.isReadOnly() == readOnly) { + return; + } + executor.setReadOnly(readOnly); } @Override public boolean isReadOnly() throws SQLException { - return state.isReadOnly(); + return executor.isReadOnly(); } @Override @@ -219,38 +159,36 @@ public String getCatalog() { @Override public void setTransactionIsolation(int level) throws SQLException { - ensureOpened(); - if (state.transactionLevel() == level) { + if (executor.transactionLevel() == level) { return; } LOGGER.log(Level.FINE, "Set transaction isolation level: {0}", level); - updateState(state.withTransactionLevel(level)); + executor.setTransactionLevel(level); } @Override public int getTransactionIsolation() throws SQLException { - ensureOpened(); - return state.transactionLevel(); + return executor.transactionLevel(); } @Override public SQLWarning getWarnings() throws SQLException { - ensureOpened(); - return executor.toSQLWarnings(); + executor.ensureOpened(); + return validator.toSQLWarnings(); } @Override public void clearWarnings() throws SQLException { - ensureOpened(); - executor.clearWarnings(); + executor.ensureOpened(); + validator.clearWarnings(); } @Override - public void executeSchemeQuery(YdbQuery query, YdbExecutor executor) throws SQLException { - ensureOpened(); + public void executeSchemeQuery(YdbQuery query, YdbValidator validator) throws SQLException { + executor.ensureOpened(); - if (state.isInsideTransaction()) { + if (executor.isInsideTransaction()) { switch (schemeQueryTxMode) { case FAKE_TX: break; @@ -264,40 +202,20 @@ public void executeSchemeQuery(YdbQuery query, YdbExecutor executor) throws SQLE } } - // Scheme query does not affect transactions or result sets - ExecuteSchemeQuerySettings settings = ctx.withDefaultTimeout(new ExecuteSchemeQuerySettings()); - final String yql = query.getYqlQuery(null); - - try (Session session = executor.createSession(ctx)) { - executor.execute(QueryType.SCHEME_QUERY + " >>\n" + yql, () -> session.executeSchemeQuery(yql, settings)); - } + executor.executeSchemeQuery(ctx, validator, query); } @Override - public DataQueryResult executeDataQuery(YdbQuery query, YdbExecutor executor, - ExecuteDataQuerySettings settings, Params params) throws SQLException { - ensureOpened(); - - final String yql = query.getYqlQuery(params); - final Session session = state.getSession(ctx, executor); - try { - DataQueryResult result = executor.call( - QueryType.DATA_QUERY + " >>\n" + yql, - () -> session.executeDataQuery(yql, state.txControl(), params, settings) - ); - updateState(state.withDataQuery(session, result.getTxId())); - return result; - } catch (SQLException | RuntimeException ex) { - updateState(state.withRollback(session)); - throw ex; - } + public List executeDataQuery(YdbQuery query, YdbValidator validator, + int timeout, boolean poolable, Params params) throws SQLException { + return executor.executeDataQuery(ctx, validator, query, timeout, poolable, params); } @Override - public ResultSetReader executeScanQuery(YdbQuery query, YdbExecutor executor, Params params) throws SQLException { - ensureOpened(); + public ResultSetReader executeScanQuery(YdbQuery query, YdbValidator validator, Params params) throws SQLException { + executor.ensureOpened(); - if (state.isInsideTransaction()) { + if (executor.isInsideTransaction()) { switch (scanQueryTxMode) { case FAKE_TX: break; @@ -310,30 +228,12 @@ public ResultSetReader executeScanQuery(YdbQuery query, YdbExecutor executor, Pa } } - String yql = query.getYqlQuery(params); - Collection resultSets = new LinkedBlockingQueue<>(); - Duration scanQueryTimeout = ctx.getOperationProperties().getScanQueryTimeout(); - ExecuteScanQuerySettings settings = ExecuteScanQuerySettings.newBuilder() - .withRequestTimeout(scanQueryTimeout) - .build(); - try (Session session = executor.createSession(ctx)) { - executor.execute(QueryType.SCAN_QUERY + " >>\n" + yql, - () -> session.executeScanQuery(yql, params, settings).start(resultSets::add)); - } - - return ProtoValueReaders.forResultSets(resultSets); + return executor.executeScanQuery(ctx, validator, query, params); } @Override - public ExplainDataQueryResult executeExplainQuery(YdbQuery query, YdbExecutor executor) throws SQLException { - ensureOpened(); - - String yql = query.getYqlQuery(null); - ExplainDataQuerySettings settings = ctx.withDefaultTimeout(new ExplainDataQuerySettings()); - try (Session session = executor.createSession(ctx)) { - String msg = QueryType.EXPLAIN_QUERY + " >>\n" + yql; - return executor.call(msg, () -> session.explainDataQuery(yql, settings)); - } + public ExplainDataQueryResult executeExplainQuery(YdbQuery query, YdbValidator validator) throws SQLException { + return executor.executeExplainQuery(ctx, validator, query); } @Override @@ -342,7 +242,8 @@ public YdbStatement createStatement(int resultSetType, int resultSetConcurrency) } @Override - public YdbPreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { + public YdbPreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException { return prepareStatement(sql, resultSetType, resultSetConcurrency, ResultSet.HOLD_CURSORS_OVER_COMMIT); } @@ -358,8 +259,7 @@ public void setTypeMap(Map> map) { @Override public void setHoldability(int holdability) throws SQLException { - ensureOpened(); - + executor.ensureOpened(); if (holdability != ResultSet.HOLD_CURSORS_OVER_COMMIT) { throw new SQLFeatureNotSupportedException(YdbConst.RESULT_SET_HOLDABILITY_UNSUPPORTED); } @@ -367,15 +267,14 @@ public void setHoldability(int holdability) throws SQLException { @Override public int getHoldability() throws SQLException { - ensureOpened(); - + executor.ensureOpened(); return ResultSet.HOLD_CURSORS_OVER_COMMIT; } @Override public YdbStatement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { - ensureOpened(); + executor.ensureOpened(); checkStatementParams(resultSetType, resultSetConcurrency, resultSetHoldability); return new YdbStatementImpl(this, resultSetType); } @@ -401,9 +300,10 @@ public YdbPreparedStatement prepareStatement(String sql, int autoGeneratedKeys) ResultSet.HOLD_CURSORS_OVER_COMMIT); } - private YdbPreparedStatement prepareStatement(String sql, int resultSetType, YdbPrepareMode mode) throws SQLException { - ensureOpened(); - executor.clearWarnings(); + private YdbPreparedStatement prepareStatement(String sql, int resultSetType, YdbPrepareMode mode) + throws SQLException { + executor.ensureOpened(); + validator.clearWarnings(); YdbQuery query = ctx.findOrParseYdbQuery(sql); @@ -417,19 +317,7 @@ private YdbPreparedStatement prepareStatement(String sql, int resultSetType, Ydb @Override public boolean isValid(int timeout) throws SQLException { - ensureOpened(); - - Session session = state.getSession(ctx, executor); - try { - KeepAliveSessionSettings settings = new KeepAliveSessionSettings().setTimeout(Duration.ofSeconds(timeout)); - Session.State keepAlive = executor.call( - "Keep alive: " + state.txID(), - () -> session.keepAlive(settings) - ); - return keepAlive == Session.State.READY; - } finally { - updateState(state.withKeepAlive(session)); - } + return executor.isValid(validator, timeout); } @Override @@ -464,7 +352,7 @@ public String getSchema() { @Override public int getNetworkTimeout() throws SQLException { - ensureOpened(); + executor.ensureOpened(); return (int) ctx.getOperationProperties().getDeadlineTimeout().toMillis(); } @@ -475,7 +363,7 @@ public YdbTypes getYdbTypes() { @Override public String getYdbTxId() { - return state.txID(); + return executor.txID(); } @Override @@ -483,12 +371,6 @@ public YdbContext getCtx() { return ctx; } - private void ensureOpened() throws SQLException { - if (state == null) { - throw new SQLException(YdbConst.CLOSED_CONNECTION); - } - } - private void checkStatementParams(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { if (resultSetType != ResultSet.TYPE_FORWARD_ONLY && resultSetType != ResultSet.TYPE_SCROLL_INSENSITIVE) { diff --git a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbDatabaseMetaDataImpl.java b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbDatabaseMetaDataImpl.java index d445955..5a71ed0 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbDatabaseMetaDataImpl.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbDatabaseMetaDataImpl.java @@ -31,9 +31,9 @@ import tech.ydb.jdbc.YdbTypes; import tech.ydb.jdbc.common.FixedResultSetFactory; import tech.ydb.jdbc.common.YdbFunctions; -import tech.ydb.jdbc.context.YdbExecutor; +import tech.ydb.jdbc.context.SchemeExecutor; +import tech.ydb.jdbc.context.YdbValidator; import tech.ydb.proto.scheme.SchemeOperationProtos; -import tech.ydb.scheme.SchemeClient; import tech.ydb.scheme.description.ListDirectoryResult; import tech.ydb.table.description.TableColumn; import tech.ydb.table.description.TableDescription; @@ -50,14 +50,16 @@ public class YdbDatabaseMetaDataImpl implements YdbDatabaseMetaData { static final String TABLE = "TABLE"; static final String SYSTEM_TABLE = "SYSTEM TABLE"; - private final YdbConnectionImpl connection; + private final YdbConnection connection; + private final YdbValidator validator; + private final SchemeExecutor executor; private final YdbTypes types; - private final YdbExecutor executor; - public YdbDatabaseMetaDataImpl(YdbConnectionImpl connection) { + public YdbDatabaseMetaDataImpl(YdbConnection connection) { this.connection = Objects.requireNonNull(connection); this.types = connection.getYdbTypes(); - this.executor = new YdbExecutor(LOGGER); + this.executor = new SchemeExecutor(connection.getCtx()); + this.validator = new YdbValidator(LOGGER); } @Override @@ -720,7 +722,7 @@ private class TableRecord implements Comparable { private final boolean isSystem; private final String name; - public TableRecord(String name) { + TableRecord(String name) { this.name = name; this.isSystem = name.startsWith(".sys/") || name.startsWith(".sys_health/") @@ -881,14 +883,14 @@ public ResultSet getBestRowIdentifier(String catalog, String schema, String tabl int decimalDigits = type.getKind() == Type.Kind.DECIMAL ? YdbConst.SQL_DECIMAL_DEFAULT_PRECISION : 0; rs.newRow() - .withShortValue("SCOPE", (short)scope) + .withShortValue("SCOPE", (short) scope) .withTextValue("COLUMN_NAME", key) .withIntValue("DATA_TYPE", types.toSqlType(type)) .withTextValue("TYPE_NAME", type.toString()) .withIntValue("COLUMN_SIZE", 0) .withIntValue("BUFFER_LENGTH", 0) .withShortValue("DECIMAL_DIGITS", (short) decimalDigits) - .withShortValue("PSEUDO_COLUMN", (short)bestRowNotPseudo) + .withShortValue("PSEUDO_COLUMN", (short) bestRowNotPseudo) .build(); } @@ -969,14 +971,14 @@ public ResultSet getTypeInfo() { .withIntValue("PRECISION", types.getSqlPrecision(type)) .withTextValue("LITERAL_PREFIX", literal) .withTextValue("LITERAL_SUFFIX", literal) - .withShortValue("NULLABLE", (short)typeNullable) + .withShortValue("NULLABLE", (short) typeNullable) .withBoolValue("CASE_SENSITIVE", true) .withShortValue("SEARCHABLE", getSearchable(type)) .withBoolValue("UNSIGNED_ATTRIBUTE", getUnsigned(type)) .withBoolValue("FIXED_PREC_SCALE", type.getKind() == Type.Kind.DECIMAL) .withBoolValue("AUTO_INCREMENT", false) // no auto-increments - .withShortValue("MINIMUM_SCALE", (short)scale) - .withShortValue("MAXIMUM_SCALE", (short)scale) + .withShortValue("MINIMUM_SCALE", (short) scale) + .withShortValue("MAXIMUM_SCALE", (short) scale) .withIntValue("SQL_DATA_TYPE", 0) .withIntValue("SQL_DATETIME_SUB", 0) .withIntValue("NUM_PREC_RADIX", 10) @@ -1029,6 +1031,8 @@ private String getLiteral(Type type) { case JsonDocument: case Yson: return "'"; + default: + return null; } } return null; @@ -1318,8 +1322,7 @@ private List listTables(Predicate filter) throws SQLException { } private List tables(String databasePrefix, String path, Predicate filter) throws SQLException { - SchemeClient client = connection.getCtx().getSchemeClient(); - ListDirectoryResult result = executor.call("List tables from " + path, () -> client.listDirectory(path)); + ListDirectoryResult result = validator.call("List tables from " + path, () -> executor.listDirectory(path)); List tables = new ArrayList<>(); String pathPrefix = withSuffix(path); @@ -1351,13 +1354,14 @@ private TableDescription describeTable(String table) throws SQLException { String databaseWithSuffix = withSuffix(connection.getCtx().getDatabase()); - return executor.call("Describe table " + table, () -> connection.getCtx() + return validator.call("Describe table " + table, () -> executor .describeTable(databaseWithSuffix + table, settings) .thenApply(result -> { // ignore scheme errors like path not found if (result.getStatus().getCode() == StatusCode.SCHEME_ERROR) { LOGGER.log(Level.WARNING, "Cannot describe table {0} -> {1}", - new Object[]{ table, result.getStatus() }); + new Object[]{table, result.getStatus()} + ); return Result.success(null); } return result; diff --git a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbParameterMetaDataImpl.java b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbParameterMetaDataImpl.java index 5fd57b2..39ec07d 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbParameterMetaDataImpl.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbParameterMetaDataImpl.java @@ -6,11 +6,9 @@ import java.sql.SQLException; import java.sql.Types; +import tech.ydb.jdbc.YdbConst; import tech.ydb.jdbc.YdbParameterMetaData; import tech.ydb.jdbc.common.TypeDescription; - -import static tech.ydb.jdbc.YdbConst.CANNOT_UNWRAP_TO; - import tech.ydb.jdbc.query.JdbcParams; public class YdbParameterMetaDataImpl implements YdbParameterMetaData { @@ -91,7 +89,7 @@ public T unwrap(Class iface) throws SQLException { if (iface.isAssignableFrom(getClass())) { return iface.cast(this); } - throw new SQLException(CANNOT_UNWRAP_TO + iface); + throw new SQLException(YdbConst.CANNOT_UNWRAP_TO + iface); } @Override diff --git a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbPreparedStatementImpl.java b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbPreparedStatementImpl.java index 806ac0e..3a08960 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbPreparedStatementImpl.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbPreparedStatementImpl.java @@ -33,11 +33,11 @@ import tech.ydb.jdbc.YdbResultSet; import tech.ydb.jdbc.YdbTypes; import tech.ydb.jdbc.common.MappingSetters; +import tech.ydb.jdbc.query.JdbcParams; import tech.ydb.jdbc.query.YdbQuery; import tech.ydb.table.query.Params; import tech.ydb.table.values.Type; import tech.ydb.table.values.VoidType; -import tech.ydb.jdbc.query.JdbcParams; public class YdbPreparedStatementImpl extends BaseYdbStatement implements YdbPreparedStatement { private static final Logger LOGGER = Logger.getLogger(YdbPreparedStatementImpl.class.getName()); diff --git a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbResultSetImpl.java b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbResultSetImpl.java index a8f9cfb..d62e8c9 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbResultSetImpl.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbResultSetImpl.java @@ -76,49 +76,49 @@ public String getString(int columnIndex) throws SQLException { if (state.nullValue) { return null; // getString supports all types, it's safe to check nullability here } - return state.description.getters().toString.fromValue(state.value); + return state.description.getters().readString(state.value); } @Override public boolean getBoolean(int columnIndex) throws SQLException { initValueReader(columnIndex); - return state.description.getters().toBoolean.fromValue(state.value); + return state.description.getters().readBoolean(state.value); } @Override public byte getByte(int columnIndex) throws SQLException { initValueReader(columnIndex); - return state.description.getters().toByte.fromValue(state.value); + return state.description.getters().readByte(state.value); } @Override public short getShort(int columnIndex) throws SQLException { initValueReader(columnIndex); - return state.description.getters().toShort.fromValue(state.value); + return state.description.getters().readShort(state.value); } @Override public int getInt(int columnIndex) throws SQLException { initValueReader(columnIndex); - return state.description.getters().toInt.fromValue(state.value); + return state.description.getters().readInt(state.value); } @Override public long getLong(int columnIndex) throws SQLException { initValueReader(columnIndex); - return state.description.getters().toLong.fromValue(state.value); + return state.description.getters().readLong(state.value); } @Override public float getFloat(int columnIndex) throws SQLException { initValueReader(columnIndex); - return state.description.getters().toFloat.fromValue(state.value); + return state.description.getters().readFloat(state.value); } @Override public double getDouble(int columnIndex) throws SQLException { initValueReader(columnIndex); - return state.description.getters().toDouble.fromValue(state.value); + return state.description.getters().readDouble(state.value); } @Deprecated @@ -135,7 +135,7 @@ public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException @Override public byte[] getBytes(int columnIndex) throws SQLException { initValueReader(columnIndex); - byte[] copy = state.description.getters().toBytes.fromValue(state.value); + byte[] copy = state.description.getters().readBytes(state.value); if (state.nullValue) { // TODO: do not parse empty value when optional and no value present return null; } @@ -271,7 +271,7 @@ public Object getObject(int columnIndex) throws SQLException { if (state.nullValue) { return null; // getObject supports all types, it's safe to check nullability here } - return state.description.getters().toObject.fromValue(state.value); + return state.description.getters().readObject(state.value); } @Override @@ -287,7 +287,7 @@ public int findColumn(String columnLabel) throws SQLException { @Override public Reader getCharacterStream(int columnIndex) throws SQLException { initValueReader(columnIndex); - Reader copy = state.description.getters().toReader.fromValue(state.value); + Reader copy = state.description.getters().readReader(state.value); if (state.nullValue) { return null; } @@ -302,7 +302,7 @@ public Reader getCharacterStream(String columnLabel) throws SQLException { @Override public BigDecimal getBigDecimal(int columnIndex) throws SQLException { initValueReader(columnIndex); - BigDecimal copy = state.description.getters().toBigDecimal.fromValue(state.value); + BigDecimal copy = state.description.getters().readBigDecimal(state.value); if (state.nullValue) { return null; } @@ -466,7 +466,7 @@ public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLExcept @Override public URL getURL(int columnIndex) throws SQLException { initValueReader(columnIndex); - String copy = state.description.getters().toURL.fromValue(state.value); + String copy = state.description.getters().readURL(state.value); if (state.nullValue) { return null; } @@ -495,7 +495,7 @@ public boolean isClosed() { @Override public String getNString(int columnIndex) throws SQLException { initValueReader(columnIndex); - String copy = state.description.getters().toNString.fromValue(state.value); + String copy = state.description.getters().readNString(state.value); if (state.nullValue) { return null; } @@ -548,7 +548,7 @@ public ResultSetReader getYdbResultSetReader() { private T getDateImpl(int columnIndex, LongFunction fromMillis) throws SQLException { initValueReader(columnIndex); - long longValue = state.description.getters().toDateMillis.fromValue(state.value); + long longValue = state.description.getters().readDateMillis(state.value); if (state.nullValue) { return null; } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbTypesImpl.java b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbTypesImpl.java index 4b73728..87818f6 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbTypesImpl.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbTypesImpl.java @@ -48,8 +48,8 @@ private YdbTypesImpl() { typeByTypeName.put(DEFAULT_DECIMAL_TYPE.toString(), DEFAULT_DECIMAL_TYPE); // Add deprecated type names - typeByTypeName.put("String" , PrimitiveType.Bytes); - typeByTypeName.put("Utf8" , PrimitiveType.Text); + typeByTypeName.put("String", PrimitiveType.Bytes); + typeByTypeName.put("Utf8", PrimitiveType.Text); typeBySqlType = new IntObjectHashMap<>(16); diff --git a/jdbc/src/main/java/tech/ydb/jdbc/query/JdbcQueryLexer.java b/jdbc/src/main/java/tech/ydb/jdbc/query/JdbcQueryLexer.java index beeae22..fa3e46e 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/query/JdbcQueryLexer.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/JdbcQueryLexer.java @@ -3,12 +3,16 @@ import java.sql.SQLException; +import tech.ydb.jdbc.settings.YdbQueryProperties; + /** * * @author Aleksandr Gorshenin */ public class JdbcQueryLexer { + private JdbcQueryLexer() { } + /** * Parses JDBC query to replace all ? to YQL parameters. * @@ -16,7 +20,7 @@ public class JdbcQueryLexer { * @param options Options of parsing * @throws java.sql.SQLException if query contains mix of query types */ - public static void buildQuery(YdbQueryBuilder builder, YdbQueryOptions options) throws SQLException { + public static void buildQuery(YdbQueryBuilder builder, YdbQueryProperties options) throws SQLException { int fragmentStart = 0; boolean nextExpression = true; @@ -142,6 +146,7 @@ private static int parseSingleQuotes(final char[] query, int offset) { return query.length; } + @SuppressWarnings("EmptyBlock") private static int parseDoubleQuotes(final char[] query, int offset) { while (++offset < query.length && query[offset] != '"') { // do nothing diff --git a/jdbc/src/main/java/tech/ydb/jdbc/query/YdbExpression.java b/jdbc/src/main/java/tech/ydb/jdbc/query/YdbExpression.java index 96c54ea..4b3e270 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/query/YdbExpression.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/YdbExpression.java @@ -5,9 +5,9 @@ * @author Aleksandr Gorshenin */ public class YdbExpression { - static YdbExpression SELECT = new YdbExpression(false, true); - static YdbExpression DDL = new YdbExpression(true, false); - static YdbExpression OTHER_DML = new YdbExpression(false, false); + static final YdbExpression SELECT = new YdbExpression(false, true); + static final YdbExpression DDL = new YdbExpression(true, false); + static final YdbExpression OTHER_DML = new YdbExpression(false, false); private final boolean isDDL; // CREATE, DROP and ALTER private final boolean isSelect; diff --git a/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQuery.java b/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQuery.java index 888618d..c951e5d 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQuery.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQuery.java @@ -8,6 +8,7 @@ import java.util.Map; import tech.ydb.jdbc.YdbConst; +import tech.ydb.jdbc.settings.YdbQueryProperties; import tech.ydb.table.query.Params; import tech.ydb.table.values.Value; @@ -16,14 +17,14 @@ * @author Aleksandr Gorshenin */ public class YdbQuery { - private final YdbQueryOptions opts; + private final YdbQueryProperties opts; private final String originSQL; private final String yqlQuery; private final QueryType type; private final List indexesArgsNames; private final List expressions; - YdbQuery(YdbQueryOptions opts, YdbQueryBuilder builder) { + YdbQuery(YdbQueryProperties opts, YdbQueryBuilder builder) { this.opts = opts; this.originSQL = builder.getOriginSQL(); this.yqlQuery = builder.buildYQL(); diff --git a/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQueryBuilder.java b/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQueryBuilder.java index 4e08600..9085254 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQueryBuilder.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQueryBuilder.java @@ -6,6 +6,7 @@ import java.util.List; import tech.ydb.jdbc.YdbConst; +import tech.ydb.jdbc.settings.YdbQueryProperties; /** * @@ -91,7 +92,7 @@ public void append(String string) { query.append(string); } - public YdbQuery build(YdbQueryOptions opts) { + public YdbQuery build(YdbQueryProperties opts) { return new YdbQuery(opts, this); } } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQueryOptions.java b/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQueryOptions.java deleted file mode 100644 index 47220f5..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQueryOptions.java +++ /dev/null @@ -1,119 +0,0 @@ -package tech.ydb.jdbc.query; - -import java.util.Map; - -import com.google.common.annotations.VisibleForTesting; - -import tech.ydb.jdbc.settings.ParsedProperty; -import tech.ydb.jdbc.settings.YdbOperationProperties; -import tech.ydb.jdbc.settings.YdbOperationProperty; - - -/** - * - * @author Aleksandr Gorshenin - */ -public class YdbQueryOptions { - private final boolean isDetectQueryType; - private final boolean isDetectJdbcParameters; - private final boolean isDeclareJdbcParameters; - - private final boolean isPrepareDataQueries; - private final boolean isDetectBatchQueries; - - private final QueryType forcedType; - - @VisibleForTesting - YdbQueryOptions( - boolean detectQueryType, - boolean detectJbdcParams, - boolean declareJdbcParams, - boolean prepareDataQuery, - boolean detectBatchQuery, - QueryType forcedType - ) { - this.isDetectQueryType = detectQueryType; - this.isDetectJdbcParameters = detectJbdcParams; - this.isDeclareJdbcParameters = declareJdbcParams; - - this.isPrepareDataQueries = prepareDataQuery; - this.isDetectBatchQueries = detectBatchQuery; - - this.forcedType = forcedType; - } - - public boolean isDetectQueryType() { - return isDetectQueryType; - } - - public boolean isDetectJdbcParameters() { - return isDetectJdbcParameters; - } - - public boolean isDeclareJdbcParameters() { - return isDeclareJdbcParameters; - } - - public boolean iPrepareDataQueries() { - return isPrepareDataQueries; - } - - public boolean isDetectBatchQueries() { - return isDetectBatchQueries; - } - - public QueryType getForcedQueryType() { - return forcedType; - } - - public static YdbQueryOptions createFrom(YdbOperationProperties props) { - boolean declareJdbcParams = true; - boolean detectJbdcParams = true; - boolean detectBatchQuery = true; - boolean prepareDataQuery = true; - boolean detectQueryType = true; - - // forced properies - Map, ParsedProperty> params = props.getParams(); - - if (params.containsKey(YdbOperationProperty.DISABLE_AUTO_PREPARED_BATCHES)) { - boolean v = params.get(YdbOperationProperty.DISABLE_AUTO_PREPARED_BATCHES).getParsedValue(); - detectBatchQuery = !v; - } - - if (params.containsKey(YdbOperationProperty.DISABLE_PREPARE_DATAQUERY)) { - boolean v = params.get(YdbOperationProperty.DISABLE_PREPARE_DATAQUERY).getParsedValue(); - prepareDataQuery = !v; - detectBatchQuery = detectBatchQuery && prepareDataQuery; - } - - if (params.containsKey(YdbOperationProperty.DISABLE_JDBC_PARAMETERS_DECLARE)) { - boolean v = params.get(YdbOperationProperty.DISABLE_JDBC_PARAMETERS_DECLARE).getParsedValue(); - declareJdbcParams = !v; - } - - if (params.containsKey(YdbOperationProperty.DISABLE_JDBC_PARAMETERS)) { - boolean v = params.get(YdbOperationProperty.DISABLE_JDBC_PARAMETERS).getParsedValue(); - detectJbdcParams = !v; - declareJdbcParams = declareJdbcParams && detectJbdcParams; - } - - if (params.containsKey(YdbOperationProperty.DISABLE_DETECT_SQL_OPERATIONS)) { - boolean v = params.get(YdbOperationProperty.DISABLE_DETECT_SQL_OPERATIONS).getParsedValue(); - detectQueryType = !v; - detectJbdcParams = detectJbdcParams && detectQueryType; - declareJdbcParams = declareJdbcParams && detectJbdcParams; - } - - QueryType forcedQueryType = props.getForcedQueryType(); - - return new YdbQueryOptions( - detectQueryType, - detectJbdcParams, - declareJdbcParams, - prepareDataQuery, - detectBatchQuery, - forcedQueryType - ); - } -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/query/params/BatchedParams.java b/jdbc/src/main/java/tech/ydb/jdbc/query/params/BatchedParams.java index 2338697..2432ee4 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/query/params/BatchedParams.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/params/BatchedParams.java @@ -15,6 +15,7 @@ import tech.ydb.jdbc.YdbConst; import tech.ydb.jdbc.common.TypeDescription; +import tech.ydb.jdbc.query.JdbcParams; import tech.ydb.table.query.Params; import tech.ydb.table.values.ListType; import tech.ydb.table.values.ListValue; @@ -22,7 +23,6 @@ import tech.ydb.table.values.StructValue; import tech.ydb.table.values.Type; import tech.ydb.table.values.Value; -import tech.ydb.jdbc.query.JdbcParams; /** * @@ -199,7 +199,7 @@ public static BatchedParams tryCreateBatched(Map types) { return null; } - StructType itemType = (StructType)innerType; + StructType itemType = (StructType) innerType; return new BatchedParams(listName, itemType); } } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/query/params/InMemoryParams.java b/jdbc/src/main/java/tech/ydb/jdbc/query/params/InMemoryParams.java index d8babfa..2793082 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/query/params/InMemoryParams.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/params/InMemoryParams.java @@ -10,10 +10,10 @@ import tech.ydb.jdbc.YdbConst; import tech.ydb.jdbc.common.TypeDescription; +import tech.ydb.jdbc.query.JdbcParams; import tech.ydb.table.query.Params; import tech.ydb.table.values.Type; import tech.ydb.table.values.Value; -import tech.ydb.jdbc.query.JdbcParams; /** @@ -97,7 +97,7 @@ public void setParam(int index, Object obj, Type type) throws SQLException { @Override public void setParam(String name, Object obj, Type type) throws SQLException { if (obj instanceof Value) { - paramValues.put(name, (Value)obj); + paramValues.put(name, (Value) obj); return; } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/query/params/PreparedParams.java b/jdbc/src/main/java/tech/ydb/jdbc/query/params/PreparedParams.java index 597be96..e5b3c7b 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/query/params/PreparedParams.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/params/PreparedParams.java @@ -14,10 +14,10 @@ import tech.ydb.jdbc.YdbConst; import tech.ydb.jdbc.common.TypeDescription; +import tech.ydb.jdbc.query.JdbcParams; import tech.ydb.table.query.Params; import tech.ydb.table.values.Type; import tech.ydb.table.values.Value; -import tech.ydb.jdbc.query.JdbcParams; /** * diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/AbstractYdbProperty.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/AbstractYdbProperty.java deleted file mode 100644 index e353f9e..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/AbstractYdbProperty.java +++ /dev/null @@ -1,81 +0,0 @@ -package tech.ydb.jdbc.settings; - -import java.sql.DriverPropertyInfo; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Objects; -import java.util.function.BiConsumer; - -import javax.annotation.Nullable; - -public abstract class AbstractYdbProperty implements ToDriverPropertyInfo { - - private final String name; - private final String description; - @Nullable - private final String defaultValue; - private final Class type; - private final PropertyConverter converter; - private final BiConsumer setter; - - protected AbstractYdbProperty(String name, - String description, - @Nullable String defaultValue, - Class type, - PropertyConverter converter, - BiConsumer setter) { - this.name = Objects.requireNonNull(name); - this.description = Objects.requireNonNull(description); - this.defaultValue = defaultValue; - this.type = Objects.requireNonNull(type); - this.converter = Objects.requireNonNull(converter); - this.setter = Objects.requireNonNull(setter); - } - - public String getName() { - return name; - } - - @Nullable - public String getDefaultValue() { - return defaultValue; - } - - public PropertyConverter getConverter() { - return converter; - } - - public Class getType() { - return type; - } - - public BiConsumer getSetter() { - return setter; - } - - @Override - public DriverPropertyInfo toDriverPropertyInfo(@Nullable String value) { - DriverPropertyInfo info = new DriverPropertyInfo(name, - value != null ? value : defaultValue != null ? defaultValue : ""); - info.description = description; - info.required = false; - return info; - } - - static class PropertiesCollector> { - private final Map properties = new LinkedHashMap<>(); - - protected void register(T property) { - if (properties.put(property.getName(), property) != null) { - throw new IllegalStateException("Internal error. Unable to register property with name " + - property.getName() + " twice"); - } - } - - protected Collection properties() { - return Collections.unmodifiableCollection(properties.values()); - } - } -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/ParsedProperty.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/ParsedProperty.java deleted file mode 100644 index 1368d00..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/ParsedProperty.java +++ /dev/null @@ -1,22 +0,0 @@ -package tech.ydb.jdbc.settings; - -import java.util.Objects; - -public class ParsedProperty { - private final String rawValue; - private final Object parsedValue; - - public ParsedProperty(String rawValue, Object parsedValue) { - this.rawValue = Objects.requireNonNull(rawValue); - this.parsedValue = Objects.requireNonNull(parsedValue); - } - - public String getRawValue() { - return rawValue; - } - - @SuppressWarnings("unchecked") - public T getParsedValue() { - return (T) parsedValue; - } -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/PropertyConverter.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/PropertyConverter.java deleted file mode 100644 index ccbe86d..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/PropertyConverter.java +++ /dev/null @@ -1,61 +0,0 @@ -package tech.ydb.jdbc.settings; - -import java.sql.SQLException; -import java.time.Duration; -import java.time.format.DateTimeParseException; -import java.util.Locale; - - -interface PropertyConverter { - T convert(String value) throws SQLException; - - static PropertyConverter stringValue() { - return value -> value; - } - - static > PropertyConverter enumValue(Class clazz) { - return value -> { - for (E v: clazz.getEnumConstants()) { - if (value.equalsIgnoreCase(v.name())) { - return v; - } - } - return null; - }; - } - - static PropertyConverter durationValue() { - return value -> { - String targetValue = "PT" + value.replace(" ", "").toUpperCase(Locale.ROOT); - try { - return Duration.parse(targetValue); - } catch (DateTimeParseException e) { - throw new RuntimeException("Unable to parse value [" + value + "] -> [" + - targetValue + "] as Duration: " + e.getMessage(), e); - } - }; - } - - static PropertyConverter integerValue() { - return value -> { - try { - return Integer.valueOf(value); - } catch (NumberFormatException e) { - throw new RuntimeException("Unable to parse value [" + value + "] as Integer: " + - e.getMessage(), e); - } - }; - } - - static PropertyConverter booleanValue() { - return Boolean::valueOf; - } - - static PropertyConverter stringFileReference() { - return YdbLookup::stringFileReference; - } - - static PropertyConverter byteFileReference() { - return YdbLookup::byteFileReference; - } -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/ToDriverPropertyInfo.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/ToDriverPropertyInfo.java deleted file mode 100644 index ed17a88..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/ToDriverPropertyInfo.java +++ /dev/null @@ -1,14 +0,0 @@ -package tech.ydb.jdbc.settings; - -import java.sql.DriverPropertyInfo; - -import javax.annotation.Nullable; - -public interface ToDriverPropertyInfo { - - default DriverPropertyInfo toDriverPropertyInfoFrom(ParsedProperty value) { - return toDriverPropertyInfo(value != null ? value.getRawValue() : null); - } - - DriverPropertyInfo toDriverPropertyInfo(@Nullable String value); -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbClientProperties.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbClientProperties.java index 3bba232..1f81024 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbClientProperties.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbClientProperties.java @@ -1,22 +1,85 @@ package tech.ydb.jdbc.settings; -import java.util.Map; +import java.sql.SQLException; +import java.time.Duration; +import java.util.Properties; + +import tech.ydb.query.QueryClient; +import tech.ydb.table.TableClient; -import javax.annotation.Nullable; public class YdbClientProperties { - private final Map, ParsedProperty> params; + private static final int SESSION_POOL_DEFAULT_MIN_SIZE = 0; + private static final int SESSION_POOL_DEFAULT_MAX_SIZE = 50; - public YdbClientProperties(Map, ParsedProperty> params) { - this.params = params; - } + static final YdbProperty KEEP_QUERY_TEXT = YdbProperty.bool( + "keepQueryText", "Keep Query text" + ); + + static final YdbProperty SESSION_KEEP_ALIVE_TIME = YdbProperty.duration( + "sessionKeepAliveTime", "Session keep-alive timeout" + ); + + static final YdbProperty SESSION_MAX_IDLE_TIME = YdbProperty.duration( + "sessionMaxIdleTime", "Session max idle time" + ); + + static final YdbProperty SESSION_POOL_SIZE_MIN = YdbProperty.integer( + "sessionPoolSizeMin", "Session pool min size (with with sessionPoolSizeMax)" + ); + + static final YdbProperty SESSION_POOL_SIZE_MAX = YdbProperty.integer( + "sessionPoolSizeMax", "Session pool max size (with with sessionPoolSizeMin)" + ); + + private final YdbValue keepQueryText; + private final YdbValue sessionKeepAliveTime; + private final YdbValue sessionMaxIdleTime; + private final YdbValue sessionPoolMinSize; + private final YdbValue sessionPoolMaxSize; + + public YdbClientProperties(YdbConfig config) throws SQLException { + Properties props = config.getProperties(); - @Nullable - public ParsedProperty getProperty(YdbClientProperty property) { - return params.get(property); + this.keepQueryText = KEEP_QUERY_TEXT.readValue(props); + this.sessionKeepAliveTime = SESSION_KEEP_ALIVE_TIME.readValue(props); + this.sessionMaxIdleTime = SESSION_MAX_IDLE_TIME.readValue(props); + this.sessionPoolMinSize = SESSION_POOL_SIZE_MIN.readValue(props); + this.sessionPoolMaxSize = SESSION_POOL_SIZE_MAX.readValue(props); } - public Map, ParsedProperty> getParams() { - return params; + public boolean applyToTableClient(TableClient.Builder table, QueryClient.Builder query) { + if (keepQueryText.hasValue()) { + table.keepQueryText(keepQueryText.getValue()); + } + + if (sessionKeepAliveTime.hasValue()) { + table.sessionKeepAliveTime(sessionKeepAliveTime.getValue()); + } + + if (sessionMaxIdleTime.hasValue()) { + table.sessionMaxIdleTime(sessionMaxIdleTime.getValue()); + query.sessionMaxIdleTime(sessionMaxIdleTime.getValue()); + } + + if (!sessionPoolMinSize.hasValue() && !sessionPoolMaxSize.hasValue()) { + return true; + } + + int minSize = SESSION_POOL_DEFAULT_MIN_SIZE; + int maxSize = SESSION_POOL_DEFAULT_MAX_SIZE; + + if (sessionPoolMinSize.hasValue()) { + minSize = Math.max(0, sessionPoolMinSize.getValue()); + maxSize = Math.max(maxSize, minSize); + } + if (sessionPoolMaxSize.hasValue()) { + maxSize = Math.max(minSize + 1, sessionPoolMaxSize.getValue()); + } + + table.sessionPoolSize(minSize, maxSize); + query.sessionPoolMaxSize(maxSize).sessionPoolMinSize(minSize); + return false; } + } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbClientProperty.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbClientProperty.java deleted file mode 100644 index b446f90..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbClientProperty.java +++ /dev/null @@ -1,69 +0,0 @@ -package tech.ydb.jdbc.settings; - -import java.time.Duration; -import java.util.Collection; -import java.util.function.BiConsumer; - -import javax.annotation.Nullable; - -import tech.ydb.table.TableClient; - -public class YdbClientProperty extends AbstractYdbProperty { - private static final PropertiesCollector> PROPERTIES = new PropertiesCollector<>(); - - public static final YdbClientProperty KEEP_QUERY_TEXT = - new YdbClientProperty<>( - "keepQueryText", - "Keep Query text", - null, - Boolean.class, - PropertyConverter.booleanValue(), - TableClient.Builder::keepQueryText); - public static final YdbClientProperty SESSION_KEEP_ALIVE_TIME = - new YdbClientProperty<>( - "sessionKeepAliveTime", - "Session keep-alive timeout", - null, - Duration.class, - PropertyConverter.durationValue(), - TableClient.Builder::sessionKeepAliveTime); - public static final YdbClientProperty SESSION_MAX_IDLE_TIME = - new YdbClientProperty<>( - "sessionMaxIdleTime", - "Session max idle time", - null, - Duration.class, - PropertyConverter.durationValue(), - TableClient.Builder::sessionMaxIdleTime); - public static final YdbClientProperty SESSION_POOL_SIZE_MIN = - new YdbClientProperty<>("sessionPoolSizeMin", - "Session pool min size (with with sessionPoolSizeMax)", - null, - Integer.class, - PropertyConverter.integerValue(), - (builder, value) -> { - }); - public static final YdbClientProperty SESSION_POOL_SIZE_MAX = - new YdbClientProperty<>( - "sessionPoolSizeMax", - "Session pool max size (with with sessionPoolSizeMin)", - null, - Integer.class, - PropertyConverter.integerValue(), - (builder, value) -> { - }); - - protected YdbClientProperty(String name, - String description, - @Nullable String defaultValue, - Class type, - PropertyConverter converter, - BiConsumer setter) { - super(name, description, defaultValue, type, converter, setter); - PROPERTIES.register(this); - } - - public static Collection> properties() { - return PROPERTIES.properties(); - } -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConfig.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConfig.java new file mode 100644 index 0000000..8f67fa2 --- /dev/null +++ b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConfig.java @@ -0,0 +1,268 @@ +package tech.ydb.jdbc.settings; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; + +import tech.ydb.core.utils.URITools; +import tech.ydb.jdbc.YdbConst; + + + + +/** + * + * @author Aleksandr Gorshenin + */ +// TODO: implement cache based on connection and client properties only (excluding operation properties) +public class YdbConfig { + static final String TOKEN_KEY = "token"; + static final String USERNAME_KEY = "user"; + static final String PASSWORD_KEY = "password"; + + static final YdbProperty CACHE_CONNECTIONS_IN_DRIVER = YdbProperty.bool( + "cacheConnectionsInDriver", + "Cache YDB connections in YdbDriver, cached by combination or url and properties", + true + ); + static final YdbProperty PREPARED_STATEMENT_CACHE_SIZE = YdbProperty.integer( + "preparedStatementCacheQueries", + "Specifies the maximum number of entries in per-transport cache of prepared statements. A value of " + + "{@code 0} disables the cache.", 256 + ); + static final YdbProperty USE_QUERY_SERVICE = YdbProperty.bool("useQueryService", + "Use QueryService intead of TableService", false + ); + + + private final String url; + private final String username; + private final String password; + + private final String safeUrl; + private final String connectionString; + + private final Properties properties; + private final boolean isCacheConnectionsInDriver; + private final int preparedStatementsCacheSize; + private final boolean useQueryService; + + private YdbConfig( + String url, String safeUrl, String connectionString, String username, String password, Properties props + ) throws SQLException { + this.url = url; + this.username = username; + this.password = password; + this.safeUrl = safeUrl; + this.connectionString = connectionString; + this.properties = props; + this.isCacheConnectionsInDriver = CACHE_CONNECTIONS_IN_DRIVER.readValue(props).getValue(); + this.preparedStatementsCacheSize = Math.max(0, PREPARED_STATEMENT_CACHE_SIZE.readValue(props).getValue()); + this.useQueryService = USE_QUERY_SERVICE.readValue(props).getValue(); + } + + public Properties getSafeProps() { + Properties safe = new Properties(); + for (String key: properties.stringPropertyNames()) { + if (isSensetive(key)) { + safe.put(key, "***"); + } else { + safe.put(key, properties.get(key)); + } + } + return safe; + } + + public String getConnectionString() { + return this.connectionString; + } + + public boolean isCacheConnectionsInDriver() { + return this.isCacheConnectionsInDriver; + } + + public int getPreparedStatementsCachecSize() { + return this.preparedStatementsCacheSize; + } + + public boolean isUseQueryService() { + return this.useQueryService; + } + + static boolean isSensetive(String key) { + return TOKEN_KEY.equalsIgnoreCase(key) || PASSWORD_KEY.equalsIgnoreCase(key); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof YdbConfig)) { + return false; + } + YdbConfig that = (YdbConfig) o; + return Objects.equals(url, that.url) && Objects.equals(properties, that.properties); + } + + @Override + public int hashCode() { + return Objects.hash(url, properties); + } + + public String getUrl() { + return url; + } + + public String getSafeUrl() { + return safeUrl; + } + + String getUsername() { + return username; + } + + String getPassword() { + return password; + } + + Properties getProperties() { + return properties; + } + + public DriverPropertyInfo[] toPropertyInfo() throws SQLException { + return new DriverPropertyInfo[] { + YdbConfig.CACHE_CONNECTIONS_IN_DRIVER.toInfo(properties), + YdbConfig.PREPARED_STATEMENT_CACHE_SIZE.toInfo(properties), + YdbConfig.USE_QUERY_SERVICE.toInfo(properties), + + YdbConnectionProperties.LOCAL_DATACENTER.toInfo(properties), + YdbConnectionProperties.USE_SECURE_CONNECTION.toInfo(properties), + YdbConnectionProperties.SECURE_CONNECTION_CERTIFICATE.toInfo(properties), + YdbConnectionProperties.TOKEN.toInfo(properties), + YdbConnectionProperties.SERVICE_ACCOUNT_FILE.toInfo(properties), + YdbConnectionProperties.USE_METADATA.toInfo(properties), + YdbConnectionProperties.IAM_ENDPOINT.toInfo(properties), + YdbConnectionProperties.METADATA_URL.toInfo(properties), + + YdbClientProperties.KEEP_QUERY_TEXT.toInfo(properties), + YdbClientProperties.SESSION_KEEP_ALIVE_TIME.toInfo(properties), + YdbClientProperties.SESSION_MAX_IDLE_TIME.toInfo(properties), + YdbClientProperties.SESSION_POOL_SIZE_MIN.toInfo(properties), + YdbClientProperties.SESSION_POOL_SIZE_MAX.toInfo(properties), + + YdbOperationProperties.JOIN_DURATION.toInfo(properties), + YdbOperationProperties.QUERY_TIMEOUT.toInfo(properties), + YdbOperationProperties.SCAN_QUERY_TIMEOUT.toInfo(properties), + YdbOperationProperties.FAIL_ON_TRUNCATED_RESULT.toInfo(properties), + YdbOperationProperties.SESSION_TIMEOUT.toInfo(properties), + YdbOperationProperties.DEADLINE_TIMEOUT.toInfo(properties), + YdbOperationProperties.AUTOCOMMIT.toInfo(properties), + YdbOperationProperties.TRANSACTION_LEVEL.toInfo(properties), + YdbOperationProperties.SCHEME_QUERY_TX_MODE.toInfo(properties), + YdbOperationProperties.SCAN_QUERY_TX_MODE.toInfo(properties), + + YdbQueryProperties.DISABLE_PREPARE_DATAQUERY.toInfo(properties), + YdbQueryProperties.DISABLE_AUTO_PREPARED_BATCHES.toInfo(properties), + YdbQueryProperties.DISABLE_DETECT_SQL_OPERATIONS.toInfo(properties), + YdbQueryProperties.DISABLE_JDBC_PARAMETERS.toInfo(properties), + YdbQueryProperties.DISABLE_JDBC_PARAMETERS_DECLARE.toInfo(properties), + YdbQueryProperties.FORCE_QUERY_MODE.toInfo(properties), + }; + } + + public static boolean isYdb(String url) { + return url.startsWith(YdbConst.JDBC_YDB_PREFIX); + } + + public static YdbConfig from(String jdbcURL, Properties origin) throws SQLException { + if (!isYdb(jdbcURL)) { + String msg = "[" + jdbcURL + "] is not a YDB URL, must starts from " + YdbConst.JDBC_YDB_PREFIX; + throw new SQLException(msg); + } + + try { + String ydbURL = jdbcURL.substring(YdbConst.JDBC_YDB_PREFIX.length()); + String connectionString = ydbURL; + String safeURL = ydbURL; + + Properties properties = new Properties(); + String username = null; + String password = null; + + if (origin != null) { + properties.putAll(origin); + username = origin.getProperty(YdbConfig.USERNAME_KEY); + password = origin.getProperty(YdbConfig.PASSWORD_KEY); + } + + if (!ydbURL.isEmpty()) { + URI url = new URI(ydbURL.contains("://") ? ydbURL : "grpc://" + ydbURL); + Map> params = URITools.splitQuery(url); + + String userInfo = url.getUserInfo(); + if (username == null && userInfo != null) { + String[] parsed = userInfo.split(":", 2); + if (parsed.length > 0) { + username = parsed[0]; + } + if (parsed.length > 1) { + password = parsed[1]; + } + } + + String database = url.getPath(); + + // merge properties and query params + for (Map.Entry> entry: params.entrySet()) { + String value = entry.getValue().get(entry.getValue().size() - 1); + properties.put(entry.getKey(), value); + if ("database".equalsIgnoreCase(entry.getKey())) { + if (database == null || database.isEmpty()) { + database = value.startsWith("/") ? value : "/" + value; + } + } + } + StringBuilder sb = new StringBuilder(); + sb.append(url.getScheme()).append("://"); + sb.append(url.getHost()); + if (url.getPort() > 0) { + sb.append(":").append(url.getPort()); + } + sb.append(database); + + connectionString = sb.toString(); + + if (!params.isEmpty()) { + String prefix = "?"; + for (Map.Entry> entry: params.entrySet()) { + String value = entry.getValue().get(entry.getValue().size() - 1); + if (YdbConfig.isSensetive(entry.getKey())) { + value = "***"; + } + if (value != null && !value.isEmpty()) { + sb.append(prefix); + sb.append(URLEncoder.encode(entry.getKey(), "UTF-8")); + sb.append("="); + sb.append(URLEncoder.encode(value, "UTF-8")); + prefix = "&"; + } + } + } + + safeURL = sb.toString(); + } + + return new YdbConfig(jdbcURL, safeURL, connectionString, username, password, properties); + } catch (URISyntaxException | RuntimeException | UnsupportedEncodingException ex) { + throw new SQLException(ex.getMessage(), ex); + } + } +} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConnectionProperties.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConnectionProperties.java index 98767f9..ccfcc0f 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConnectionProperties.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConnectionProperties.java @@ -1,51 +1,119 @@ package tech.ydb.jdbc.settings; -import java.util.Map; -import java.util.Objects; - -import javax.annotation.Nullable; +import java.sql.SQLException; +import java.util.Properties; +import tech.ydb.auth.TokenAuthProvider; +import tech.ydb.auth.iam.CloudAuthHelper; import tech.ydb.core.auth.StaticCredentials; +import tech.ydb.core.grpc.BalancingSettings; +import tech.ydb.core.grpc.GrpcTransportBuilder; public class YdbConnectionProperties { - private final String safeURL; - private final String connectionString; + static final YdbProperty TOKEN = YdbProperty.content(YdbConfig.TOKEN_KEY, "Authentication token"); + + static final YdbProperty LOCAL_DATACENTER = YdbProperty.string("localDatacenter", + "Local Datacenter"); + + static final YdbProperty USE_SECURE_CONNECTION = YdbProperty.bool("secureConnection", + "Use TLS connection"); + + static final YdbProperty SECURE_CONNECTION_CERTIFICATE = YdbProperty.bytes("secureConnectionCertificate", + "Use TLS connection with certificate from provided path"); + + static final YdbProperty SERVICE_ACCOUNT_FILE = YdbProperty.content("saFile", + "Service account file based authentication"); + + static final YdbProperty USE_METADATA = YdbProperty.bool("useMetadata", + "Use metadata service for authentication"); + + static final YdbProperty IAM_ENDPOINT = YdbProperty.content("iamEndpoint", + "Custom IAM endpoint for the service account authentication"); + + static final YdbProperty METADATA_URL = YdbProperty.content("metadataURL", + "Custom URL for the metadata service authentication"); + private final String username; private final String password; - private final Map, ParsedProperty> params; - - public YdbConnectionProperties(String safeURL, String connectionString, String username, String password, - Map, ParsedProperty> params) { - this.safeURL = safeURL; - this.connectionString = Objects.requireNonNull(connectionString); - this.username = username; - this.password = password; - this.params = Objects.requireNonNull(params); - } - public String getSafeUrl() { - return safeURL; - } + private final YdbValue localDatacenter; + private final YdbValue useSecureConnection; + private final YdbValue secureConnectionCertificate; + private final YdbValue token; + private final YdbValue serviceAccountFile; + private final YdbValue useMetadata; + private final YdbValue iamEndpoint; + private final YdbValue metadataUrl; + + public YdbConnectionProperties(YdbConfig config) throws SQLException { + this.username = config.getUsername(); + this.password = config.getPassword(); + + Properties props = config.getProperties(); - public String getConnectionString() { - return connectionString; + this.localDatacenter = LOCAL_DATACENTER.readValue(props); + this.useSecureConnection = USE_SECURE_CONNECTION.readValue(props); + this.secureConnectionCertificate = SECURE_CONNECTION_CERTIFICATE.readValue(props); + this.token = TOKEN.readValue(props); + this.serviceAccountFile = SERVICE_ACCOUNT_FILE.readValue(props); + this.useMetadata = USE_METADATA.readValue(props); + this.iamEndpoint = IAM_ENDPOINT.readValue(props); + this.metadataUrl = METADATA_URL.readValue(props); } - @Nullable - public ParsedProperty getProperty(YdbConnectionProperty property) { - return params.get(property); + String getLocalDataCenter() { + return localDatacenter.getValue(); } - public Map, ParsedProperty> getParams() { - return params; + String getToken() { + return token.getValue(); } - public boolean hasStaticCredentials() { - return username != null && !username.isEmpty(); + byte[] getSecureConnectionCert() { + return secureConnectionCertificate.getValue(); } - public StaticCredentials getStaticCredentials() { - return new StaticCredentials(username, password); + public GrpcTransportBuilder applyToGrpcTransport(GrpcTransportBuilder builder) { + if (localDatacenter.hasValue()) { + builder = builder.withBalancingSettings(BalancingSettings.fromLocation(localDatacenter.getValue())); + } + + if (useSecureConnection.hasValue() && useSecureConnection.getValue()) { + builder = builder.withSecureConnection(); + } + + if (secureConnectionCertificate.hasValue()) { + builder = builder.withSecureConnection(secureConnectionCertificate.getValue()); + } + + if (token.hasValue()) { + builder = builder.withAuthProvider(new TokenAuthProvider(token.getValue())); + } + + if (serviceAccountFile.hasValue()) { + String json = serviceAccountFile.getValue(); + if (iamEndpoint.hasValue()) { + String endpoint = iamEndpoint.getValue(); + builder = builder.withAuthProvider(CloudAuthHelper.getServiceAccountJsonAuthProvider(json, endpoint)); + } else { + builder = builder.withAuthProvider(CloudAuthHelper.getServiceAccountJsonAuthProvider(json)); + } + } + + if (useMetadata.hasValue()) { + if (metadataUrl.hasValue()) { + String url = metadataUrl.getValue(); + builder = builder.withAuthProvider(CloudAuthHelper.getMetadataAuthProvider(url)); + } else { + builder = builder.withAuthProvider(CloudAuthHelper.getMetadataAuthProvider()); + } + } + + if (username != null && !username.isEmpty()) { + builder = builder.withAuthProvider(new StaticCredentials(username, password)); + } + + return builder; } } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConnectionProperty.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConnectionProperty.java deleted file mode 100644 index d57e8ab..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConnectionProperty.java +++ /dev/null @@ -1,98 +0,0 @@ -package tech.ydb.jdbc.settings; - -import java.util.Collection; -import java.util.function.BiConsumer; - -import javax.annotation.Nullable; - -import tech.ydb.auth.AuthProvider; -import tech.ydb.auth.TokenAuthProvider; -import tech.ydb.auth.iam.CloudAuthHelper; -import tech.ydb.core.grpc.BalancingSettings; -import tech.ydb.core.grpc.GrpcTransportBuilder; - -public class YdbConnectionProperty extends AbstractYdbProperty { - private static final PropertiesCollector> PROPERTIES = new PropertiesCollector<>(); - - public static final YdbConnectionProperty LOCAL_DATACENTER = - new YdbConnectionProperty<>( - "localDatacenter", - "Local Datacenter", - null, - String.class, - PropertyConverter.stringValue(), - (builder, value) -> { - if (value != null && !value.isEmpty()) { - builder.withBalancingSettings(BalancingSettings.fromLocation(value)); - } - }); - - public static final YdbConnectionProperty SECURE_CONNECTION = - new YdbConnectionProperty<>( - "secureConnection", - "Use TLS connection", - null, - Boolean.class, - PropertyConverter.booleanValue(), - (builder, value) -> { - if (value) { - builder.withSecureConnection(); - } - }); - - public static final YdbConnectionProperty SECURE_CONNECTION_CERTIFICATE = - new YdbConnectionProperty<>( - "secureConnectionCertificate", - "Use TLS connection with certificate from provided path", - null, - byte[].class, - PropertyConverter.byteFileReference(), - GrpcTransportBuilder::withSecureConnection); - - public static final YdbConnectionProperty TOKEN = - new YdbConnectionProperty<>( - "token", - "Token-based authentication", - null, - AuthProvider.class, - value -> new TokenAuthProvider(PropertyConverter.stringFileReference().convert(value)), - GrpcTransportBuilder::withAuthProvider); - - public static final YdbConnectionProperty SERVICE_ACCOUNT_FILE = - new YdbConnectionProperty<>( - "saFile", - "Service account file based authentication", - null, - AuthProvider.class, - value -> CloudAuthHelper.getServiceAccountJsonAuthProvider( - PropertyConverter.stringFileReference().convert(value) - ), - GrpcTransportBuilder::withAuthProvider); - - public static final YdbConnectionProperty USE_METADATA = - new YdbConnectionProperty<>( - "useMetadata", - "Use metadata service for authentication", - null, - Boolean.class, - PropertyConverter.booleanValue(), - (builder, value) -> { - if (value) { - builder.withAuthProvider(CloudAuthHelper.getMetadataAuthProvider()); - } - }); - - private YdbConnectionProperty(String name, - String description, - @Nullable String defaultValue, - Class type, - PropertyConverter converter, - BiConsumer setter) { - super(name, description, defaultValue, type, converter, setter); - PROPERTIES.register(this); - } - - public static Collection> properties() { - return PROPERTIES.properties(); - } -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbJdbcTools.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbJdbcTools.java deleted file mode 100644 index 48515c5..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbJdbcTools.java +++ /dev/null @@ -1,162 +0,0 @@ -package tech.ydb.jdbc.settings; - -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.sql.SQLException; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import tech.ydb.core.utils.URITools; -import tech.ydb.jdbc.YdbConst; - - -/** - * - * @author Aleksandr Gorshenin - */ -public class YdbJdbcTools { - private YdbJdbcTools() { } - - public static boolean isYdb(String url) { - return url.startsWith(YdbConst.JDBC_YDB_PREFIX); - } - - public static YdbProperties from(String jdbcURL, Properties origProperties) throws SQLException { - if (!isYdb(jdbcURL)) { - String msg = "[" + jdbcURL + "] is not a YDB URL, must starts from " + YdbConst.JDBC_YDB_PREFIX; - throw new SQLException(msg); - } - - try { - String ydbURL = jdbcURL.substring(YdbConst.JDBC_YDB_PREFIX.length()); - String connectionString = ydbURL; - String safeURL = ydbURL; - - String username = origProperties.getProperty("user"); - String password = origProperties.getProperty("password"); - - Properties properties = new Properties(); - properties.putAll(origProperties); - - if (!ydbURL.isEmpty()) { - URI url = new URI(ydbURL.contains("://") ? ydbURL : "grpc://" + ydbURL); - Map> params = URITools.splitQuery(url); - - String userInfo = url.getUserInfo(); - if (username == null && userInfo != null) { - String[] parsed = userInfo.split(":", 2); - if (parsed.length > 0) { - username = parsed[0]; - } - if (parsed.length > 1) { - password = parsed[1]; - } - } - - String database = url.getPath(); - - // merge properties and query params - for (Map.Entry> entry: params.entrySet()) { - String value = entry.getValue().get(entry.getValue().size() - 1); - properties.put(entry.getKey(), value); - if ("database".equalsIgnoreCase(entry.getKey())) { - if (database == null || database.isEmpty()) { - database = value.startsWith("/") ? value : "/" + value; - } - } - } - StringBuilder sb = new StringBuilder(); - sb.append(url.getScheme()).append("://"); - sb.append(url.getHost()); - if (url.getPort() > 0) { - sb.append(":").append(url.getPort()); - } - sb.append(database); - - connectionString = sb.toString(); - - if (!params.isEmpty()) { - String prefix = "?"; - for (Map.Entry> entry: params.entrySet()) { - String value = entry.getValue().get(entry.getValue().size() - 1); - if (YdbConnectionProperty.TOKEN.getName().equalsIgnoreCase(entry.getKey())) { - value = "***"; - } - if (value != null && !value.isEmpty()) { - sb.append(prefix); - sb.append(URLEncoder.encode(entry.getKey(), "UTF-8")); - sb.append("="); - sb.append(URLEncoder.encode(value, "UTF-8")); - prefix = "&"; - } - } - } - - safeURL = sb.toString(); - } - - YdbConnectionProperties ydbConnectionProps = new YdbConnectionProperties(safeURL, connectionString, - username, password, parseProperties(properties, YdbConnectionProperty.properties())); - YdbClientProperties ydbClientProperties = new YdbClientProperties( - parseProperties(properties, YdbClientProperty.properties())); - YdbOperationProperties ydbOperationProperties = new YdbOperationProperties( - parseProperties(properties, YdbOperationProperty.properties())); - - return new YdbProperties(ydbConnectionProps, ydbClientProperties, ydbOperationProperties); - } catch (URISyntaxException | RuntimeException | UnsupportedEncodingException ex) { - throw new SQLException(ex.getMessage(), ex); - } - } - - private static > Map parseProperties( - Properties properties, - Collection knownProperties) throws SQLException { - Map result = new LinkedHashMap<>(knownProperties.size()); - for (T property : knownProperties) { - String title = property.getName(); - Object value = properties.get(title); - - PropertyConverter converter = property.getConverter(); - ParsedProperty parsed; - if (value != null) { - if (value instanceof String) { - String stringValue = (String) value; - try { - parsed = new ParsedProperty(stringValue, converter.convert(stringValue)); - } catch (RuntimeException e) { - throw new SQLException("Unable to convert property " + title + ": " + e.getMessage(), e); - } - } else { - if (property.getType().isAssignableFrom(value.getClass())) { - parsed = new ParsedProperty("", value); - } else { - throw new SQLException("Invalid object property " + title +", must be " + property.getType() + - ", got " + value.getClass()); - } - } - } else { - String stringValue = property.getDefaultValue(); - if (stringValue != null) { - try { - parsed = new ParsedProperty(stringValue, converter.convert(stringValue)); - } catch (RuntimeException e) { - throw new SQLException("Unable to convert property " + title + ": " + e.getMessage(), e); - } - } else { - parsed = null; - } - } - - result.put(property, parsed); - } - return Collections.unmodifiableMap(result); - } - - -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbLookup.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbLookup.java index c2496cf..1caf494 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbLookup.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbLookup.java @@ -21,6 +21,8 @@ public class YdbLookup { private static final String HOME_REF = "~"; private static final String FILE_HOME_REF = FILE_REF + HOME_REF; + private YdbLookup() { } + public static String stringFileReference(String ref) { Optional urlOpt = resolvePath(ref); if (urlOpt.isPresent()) { diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbOperationProperties.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbOperationProperties.java index 4c18787..5c201b5 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbOperationProperties.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbOperationProperties.java @@ -1,111 +1,122 @@ package tech.ydb.jdbc.settings; +import java.sql.Connection; +import java.sql.SQLException; import java.time.Duration; -import java.util.Map; -import java.util.Objects; +import java.util.Properties; -import tech.ydb.jdbc.query.QueryType; public class YdbOperationProperties { - public static final int MAX_ROWS = 1000; // TODO: how to figure out the max rows of current connection? - - private final Map, ParsedProperty> params; - private final Duration joinDuration; - private final Duration queryTimeout; - private final Duration scanQueryTimeout; - private final boolean failOnTruncatedResult; - private final Duration sessionTimeout; - private final Duration deadlineTimeout; - private final boolean autoCommit; - private final int transactionLevel; - private final int maxRows; - private final boolean cacheConnectionsInDriver; - private final int preparedStatementCacheSize; - - private final FakeTxMode scanQueryTxMode; - private final FakeTxMode schemeQueryTxMode; - private final QueryType forcedQueryType; - - public YdbOperationProperties(Map, ParsedProperty> params) { - this.params = Objects.requireNonNull(params); - - this.joinDuration = params.get(YdbOperationProperty.JOIN_DURATION).getParsedValue(); - this.queryTimeout = params.get(YdbOperationProperty.QUERY_TIMEOUT).getParsedValue(); - this.scanQueryTimeout = params.get(YdbOperationProperty.SCAN_QUERY_TIMEOUT).getParsedValue(); - this.failOnTruncatedResult = params.get(YdbOperationProperty.FAIL_ON_TRUNCATED_RESULT).getParsedValue(); - this.sessionTimeout = params.get(YdbOperationProperty.SESSION_TIMEOUT).getParsedValue(); - this.deadlineTimeout = params.get(YdbOperationProperty.DEADLINE_TIMEOUT).getParsedValue(); - this.autoCommit = params.get(YdbOperationProperty.AUTOCOMMIT).getParsedValue(); - this.transactionLevel = params.get(YdbOperationProperty.TRANSACTION_LEVEL).getParsedValue(); - this.maxRows = MAX_ROWS; - this.cacheConnectionsInDriver = params.get(YdbOperationProperty.CACHE_CONNECTIONS_IN_DRIVER).getParsedValue(); - this.preparedStatementCacheSize = Math.max(0, - params.get(YdbOperationProperty.PREPARED_STATEMENT_CACHE_SIZE).getParsedValue()); - - this.scanQueryTxMode = params.get(YdbOperationProperty.SCAN_QUERY_TX_MODE).getParsedValue(); - this.schemeQueryTxMode = params.get(YdbOperationProperty.SCHEME_QUERY_TX_MODE).getParsedValue(); - - ParsedProperty forcedType = params.get(YdbOperationProperty.FORCE_QUERY_MODE); - this.forcedQueryType = forcedType != null ? forcedType.getParsedValue() : null; - } + static final YdbProperty JOIN_DURATION = YdbProperty + .duration("joinDuration", "Default timeout for all YDB operations", "5m"); + + static final YdbProperty QUERY_TIMEOUT = YdbProperty + .duration("queryTimeout", "Default timeout for all YDB data queries, scheme and explain operations", "0s"); + + static final YdbProperty SCAN_QUERY_TIMEOUT = YdbProperty + .duration("scanQueryTimeout", "Default timeout for all YDB scan queries", "5m"); + + static final YdbProperty FAIL_ON_TRUNCATED_RESULT = YdbProperty + .bool("failOnTruncatedResult", "Throw an exception when received truncated result", false); + + static final YdbProperty SESSION_TIMEOUT = YdbProperty + .duration("sessionTimeout", "Default timeout to create a session", "5s"); + + static final YdbProperty DEADLINE_TIMEOUT = YdbProperty + .duration("deadlineTimeout", "Deadline timeout for all operations", "0s"); + + static final YdbProperty AUTOCOMMIT = YdbProperty + .bool("autoCommit", "Auto commit all operations", true); + + static final YdbProperty TRANSACTION_LEVEL = YdbProperty + .integer("transactionLevel", "Default transaction isolation level", Connection.TRANSACTION_SERIALIZABLE); + + static final YdbProperty SCAN_QUERY_TX_MODE = YdbProperty.enums( + "scanQueryTxMode", + FakeTxMode.class, + "Mode of execution scan query inside transaction. Possible values - " + + "ERROR(by default), FAKE_TX and SHADOW_COMMIT", + FakeTxMode.ERROR + ); + + static final YdbProperty SCHEME_QUERY_TX_MODE = YdbProperty.enums("schemeQueryTxMode", + FakeTxMode.class, + "Mode of execution scheme query inside transaction. Possible values - " + + "ERROR(by default), FAKE_TX and SHADOW_COMMIT", + FakeTxMode.ERROR + ); + + private static final int MAX_ROWS = 1000; // TODO: how to figure out the max rows of current connection? - public Map, ParsedProperty> getParams() { - return params; + private final YdbValue joinDuration; + private final YdbValue queryTimeout; + private final YdbValue scanQueryTimeout; + private final YdbValue failOnTruncatedResult; + private final YdbValue sessionTimeout; + private final YdbValue deadlineTimeout; + private final YdbValue autoCommit; + private final YdbValue transactionLevel; + + private final YdbValue scanQueryTxMode; + private final YdbValue schemeQueryTxMode; + + public YdbOperationProperties(YdbConfig config) throws SQLException { + Properties props = config.getProperties(); + + this.joinDuration = JOIN_DURATION.readValue(props); + this.queryTimeout = QUERY_TIMEOUT.readValue(props); + this.scanQueryTimeout = SCAN_QUERY_TIMEOUT.readValue(props); + this.failOnTruncatedResult = FAIL_ON_TRUNCATED_RESULT.readValue(props); + this.sessionTimeout = SESSION_TIMEOUT.readValue(props); + this.deadlineTimeout = DEADLINE_TIMEOUT.readValue(props); + this.autoCommit = AUTOCOMMIT.readValue(props); + this.transactionLevel = TRANSACTION_LEVEL.readValue(props); + + this.scanQueryTxMode = SCAN_QUERY_TX_MODE.readValue(props); + this.schemeQueryTxMode = SCHEME_QUERY_TX_MODE.readValue(props); } public Duration getJoinDuration() { - return joinDuration; + return joinDuration.getValue(); } public Duration getQueryTimeout() { - return queryTimeout; + return queryTimeout.getValue(); } public Duration getScanQueryTimeout() { - return scanQueryTimeout; + return scanQueryTimeout.getValue(); } public boolean isFailOnTruncatedResult() { - return failOnTruncatedResult; + return failOnTruncatedResult.getValue(); } public FakeTxMode getScanQueryTxMode() { - return scanQueryTxMode; + return scanQueryTxMode.getValue(); } public FakeTxMode getSchemeQueryTxMode() { - return schemeQueryTxMode; - } - - public QueryType getForcedQueryType() { - return forcedQueryType; + return schemeQueryTxMode.getValue(); } public Duration getSessionTimeout() { - return sessionTimeout; + return sessionTimeout.getValue(); } public Duration getDeadlineTimeout() { - return deadlineTimeout; + return deadlineTimeout.getValue(); } public boolean isAutoCommit() { - return autoCommit; + return autoCommit.getValue(); } public int getTransactionLevel() { - return transactionLevel; + return transactionLevel.getValue(); } public int getMaxRows() { - return maxRows; - } - - public boolean isCacheConnectionsInDriver() { - return cacheConnectionsInDriver; - } - - public int getPreparedStatementCacheSize() { - return preparedStatementCacheSize; + return MAX_ROWS; } } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbOperationProperty.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbOperationProperty.java deleted file mode 100644 index 3d414a9..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbOperationProperty.java +++ /dev/null @@ -1,170 +0,0 @@ -package tech.ydb.jdbc.settings; - -import java.sql.Connection; -import java.time.Duration; -import java.util.Collection; - -import javax.annotation.Nullable; - -import tech.ydb.jdbc.query.QueryType; - - -public class YdbOperationProperty extends AbstractYdbProperty { - private static final PropertiesCollector> PROPERTIES = new PropertiesCollector<>(); - - public static final YdbOperationProperty JOIN_DURATION = - new YdbOperationProperty<>( - "joinDuration", - "Default timeout for all YDB operations", - "5m", - Duration.class, - PropertyConverter.durationValue()); - - public static final YdbOperationProperty QUERY_TIMEOUT = - new YdbOperationProperty<>( - "queryTimeout", - "Default timeout for all YDB data queries, scheme and explain operations", - "0s", - Duration.class, - PropertyConverter.durationValue()); - - public static final YdbOperationProperty SCAN_QUERY_TIMEOUT = - new YdbOperationProperty<>( - "scanQueryTimeout", - "Default timeout for all YDB scan queries", - "5m", - Duration.class, - PropertyConverter.durationValue()); - - public static final YdbOperationProperty FAIL_ON_TRUNCATED_RESULT = - new YdbOperationProperty<>( - "failOnTruncatedResult", - "Throw an exception when received truncated result", - "false", - Boolean.class, - PropertyConverter.booleanValue()); - - public static final YdbOperationProperty SESSION_TIMEOUT = - new YdbOperationProperty<>( - "sessionTimeout", - "Default timeout to create a session", - "5s", - Duration.class, - PropertyConverter.durationValue()); - - public static final YdbOperationProperty DEADLINE_TIMEOUT = - new YdbOperationProperty<>( - "deadlineTimeout", - "Deadline timeout for all operations", - "0s", - Duration.class, - PropertyConverter.durationValue()); - - public static final YdbOperationProperty AUTOCOMMIT = - new YdbOperationProperty<>( - "autoCommit", - "Auto commit all operations", - "true", - Boolean.class, - PropertyConverter.booleanValue()); - - public static final YdbOperationProperty TRANSACTION_LEVEL = - new YdbOperationProperty<>( - "transactionLevel", - "Default transaction isolation level", - String.valueOf(Connection.TRANSACTION_SERIALIZABLE), - Integer.class, - PropertyConverter.integerValue()); - - // - - // Some JDBC driver specific options - - public static final YdbOperationProperty CACHE_CONNECTIONS_IN_DRIVER = - new YdbOperationProperty<>("cacheConnectionsInDriver", - "Cache YDB connections in YdbDriver, cached by combination or url and properties", - "true", - Boolean.class, - PropertyConverter.booleanValue()); - - public static final YdbOperationProperty PREPARED_STATEMENT_CACHE_SIZE = - new YdbOperationProperty<>("preparedStatementCacheQueries", - "Specifies the maximum number of entries in per-transport cache of prepared statements. " - + "A value of {@code 0} disables the cache.", - "256", - Integer.class, - PropertyConverter.integerValue()); - - public static final YdbOperationProperty DISABLE_DETECT_SQL_OPERATIONS = - new YdbOperationProperty<>("disableDetectSqlOperations", - "Disable detecting SQL operation based on SQL keywords", - "false", - Boolean.class, - PropertyConverter.booleanValue()); - - public static final YdbOperationProperty DISABLE_PREPARE_DATAQUERY = - new YdbOperationProperty<>("disablePrepareDataQuery", - "Disable executing #prepareDataQuery when creating PreparedStatements", - "false", - Boolean.class, - PropertyConverter.booleanValue()); - - public static final YdbOperationProperty DISABLE_AUTO_PREPARED_BATCHES = - new YdbOperationProperty<>("disableAutoPreparedBatches", - "Disable automatically detect list of tuples or structs in prepared statement", - "false", - Boolean.class, - PropertyConverter.booleanValue()); - - - public static final YdbOperationProperty DISABLE_JDBC_PARAMETERS = - new YdbOperationProperty<>("disableJdbcParameters", - "Disable auto detect JDBC standart parameters '?'", - "false", - Boolean.class, - PropertyConverter.booleanValue()); - - public static final YdbOperationProperty DISABLE_JDBC_PARAMETERS_DECLARE = - new YdbOperationProperty<>("disableJdbcParameterDeclare", - "Disable enforce DECLARE section for JDBC parameters '?'", - "false", - Boolean.class, - PropertyConverter.booleanValue()); - - public static final YdbOperationProperty SCAN_QUERY_TX_MODE = - new YdbOperationProperty<>("scanQueryTxMode", - "Mode of execution scan query inside transaction. " - + "Possible values - ERROR(by default), FAKE_TX and SHADOW_COMMIT", - "ERROR", - FakeTxMode.class, - PropertyConverter.enumValue(FakeTxMode.class)); - - public static final YdbOperationProperty SCHEME_QUERY_TX_MODE = - new YdbOperationProperty<>("schemeQueryTxMode", - "Mode of execution scheme query inside transaction. " - + "Possible values - ERROR(by default), FAKE_TX and SHADOW_COMMIT", - "ERROR", - FakeTxMode.class, - PropertyConverter.enumValue(FakeTxMode.class)); - - public static final YdbOperationProperty FORCE_QUERY_MODE = - new YdbOperationProperty<>("forceQueryMode", - "Force usage one of query modes (DATA_QUERY, SCAN_QUERY, SCHEME_QUERY or EXPLAIN_QUERYn) " - + "for all statements", - null, - QueryType.class, - PropertyConverter.enumValue(QueryType.class)); - - protected YdbOperationProperty(String name, - String description, - @Nullable String defaultValue, - Class type, - PropertyConverter converter) { - super(name, description, defaultValue, type, converter, (op, value) -> {}); - PROPERTIES.register(this); - } - - public static Collection> properties() { - return PROPERTIES.properties(); - } -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbProperties.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbProperties.java deleted file mode 100644 index 0cce76b..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbProperties.java +++ /dev/null @@ -1,43 +0,0 @@ -package tech.ydb.jdbc.settings; - - -import java.sql.DriverPropertyInfo; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class YdbProperties { - private final YdbConnectionProperties connectionProperties; - private final YdbClientProperties clientProperties; - private final YdbOperationProperties operationProperties; - - public YdbProperties(YdbConnectionProperties connectionProperties, YdbClientProperties clientProperties, - YdbOperationProperties operationProperties) { - this.connectionProperties = Objects.requireNonNull(connectionProperties); - this.clientProperties = Objects.requireNonNull(clientProperties); - this.operationProperties = Objects.requireNonNull(operationProperties); - } - - public YdbConnectionProperties getConnectionProperties() { - return connectionProperties; - } - - public YdbClientProperties getClientProperties() { - return clientProperties; - } - - public YdbOperationProperties getOperationProperties() { - return operationProperties; - } - - public DriverPropertyInfo[] toDriverProperties() { - List properties = new ArrayList<>(); - connectionProperties.getParams().forEach((property, value) -> - properties.add(property.toDriverPropertyInfoFrom(value))); - clientProperties.getParams().forEach((property, value) -> - properties.add(property.toDriverPropertyInfoFrom(value))); - operationProperties.getParams().forEach((property, value) -> - properties.add(property.toDriverPropertyInfoFrom(value))); - return properties.toArray(new DriverPropertyInfo[0]); - } -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbProperty.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbProperty.java new file mode 100644 index 0000000..8c772e3 --- /dev/null +++ b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbProperty.java @@ -0,0 +1,153 @@ +package tech.ydb.jdbc.settings; + +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; +import java.time.Duration; +import java.time.format.DateTimeParseException; +import java.util.Locale; +import java.util.Objects; +import java.util.Properties; + + + +class YdbProperty { + private interface Parser { + T parse(String value) throws SQLException; + } + + private final String name; + private final String description; + private final String defaultValue; + private final Class clazz; + private final Parser parser; + + private YdbProperty(String name, String description, String defaultValue, Class clazz, Parser parser) { + this.name = Objects.requireNonNull(name); + this.description = Objects.requireNonNull(description); + this.defaultValue = defaultValue; + this.clazz = clazz; + this.parser = Objects.requireNonNull(parser); + } + + public String getName() { + return name; + } + + public YdbValue readValue(Properties props) throws SQLException { + Object value = props.get(name); + if (value == null) { + if (defaultValue == null || defaultValue.isEmpty()) { + return new YdbValue<>(false, "", null); + } else { + return new YdbValue<>(false, defaultValue, parser.parse(defaultValue)); + } + } + + if (value instanceof String) { + try { + String stringValue = (String) value; + return new YdbValue<>(true, stringValue, parser.parse(stringValue)); + } catch (RuntimeException e) { + throw new SQLException("Unable to convert property " + name + ": " + e.getMessage(), e); + } + } else { + if (clazz.isAssignableFrom(value.getClass())) { + T typed = clazz.cast(value); + return new YdbValue<>(true, typed.toString(), typed); + } else { + throw new SQLException("Invalid object property " + name + ", must be " + clazz + + ", got " + value.getClass()); + } + } + } + + DriverPropertyInfo toInfo(Properties values) throws SQLException { + YdbValue value = readValue(values); + DriverPropertyInfo info = new DriverPropertyInfo(name, value.rawValue()); + info.description = description; + info.required = false; + return info; + } + + public static YdbProperty string(String name, String description) { + return string(name, description, null); + } + + public static YdbProperty string(String name, String description, String defaultValue) { + return new YdbProperty<>(name, description, defaultValue, String.class, v -> v); + } + + public static YdbProperty bool(String name, String description, boolean defaultValue) { + return bool(name, description, String.valueOf(defaultValue)); + } + + public static YdbProperty bool(String name, String description) { + return bool(name, description, null); + } + + private static YdbProperty bool(String name, String description, String defaultValue) { + return new YdbProperty<>(name, description, defaultValue, Boolean.class, Boolean::valueOf); + } + + public static YdbProperty integer(String name, String description) { + return integer(name, description, null); + } + + public static YdbProperty integer(String name, String description, int defaultValue) { + return integer(name, description, String.valueOf(defaultValue)); + } + + private static YdbProperty integer(String name, String description, String defaultValue) { + return new YdbProperty<>(name, description, defaultValue, Integer.class, value -> { + try { + return Integer.valueOf(value); + } catch (NumberFormatException e) { + throw new RuntimeException("Unable to parse value [" + value + "] as Integer: " + + e.getMessage(), e); + } + }); + } + + public static > YdbProperty enums(String name, Class clazz, String description) { + return enums(name, description, clazz, null); + } + + public static > YdbProperty enums(String name, Class clazz, String description, E def) { + return enums(name, description, clazz, def.toString()); + } + + private static > YdbProperty enums(String name, String desc, Class clazz, String def) { + return new YdbProperty<>(name, desc, def, clazz, value -> { + for (E v: clazz.getEnumConstants()) { + if (v.name().equalsIgnoreCase(value)) { + return v; + } + } + return null; + }); + } + + public static YdbProperty duration(String name, String description) { + return duration(name, description, null); + } + + public static YdbProperty duration(String name, String description, String defaultValue) { + return new YdbProperty<>(name, description, defaultValue, Duration.class, value -> { + String targetValue = "PT" + value.replace(" ", "").toUpperCase(Locale.ROOT); + try { + return Duration.parse(targetValue); + } catch (DateTimeParseException e) { + throw new RuntimeException("Unable to parse value [" + value + "] -> [" + + targetValue + "] as Duration: " + e.getMessage(), e); + } + }); + } + + public static YdbProperty bytes(String name, String description) { + return new YdbProperty<>(name, description, null, byte[].class, YdbLookup::byteFileReference); + } + + public static YdbProperty content(String name, String description) { + return new YdbProperty<>(name, description, null, String.class, YdbLookup::stringFileReference); + } +} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbQueryProperties.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbQueryProperties.java new file mode 100644 index 0000000..3db2dbf --- /dev/null +++ b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbQueryProperties.java @@ -0,0 +1,87 @@ +package tech.ydb.jdbc.settings; + +import java.sql.SQLException; +import java.util.Properties; + +import tech.ydb.jdbc.query.QueryType; + + + +/** + * + * @author Aleksandr Gorshenin + */ +public class YdbQueryProperties { + static final YdbProperty DISABLE_DETECT_SQL_OPERATIONS = YdbProperty.bool("disableDetectSqlOperations", + "Disable detecting SQL operation based on SQL keywords", false); + + static final YdbProperty DISABLE_PREPARE_DATAQUERY = YdbProperty.bool("disablePrepareDataQuery", + "Disable executing #prepareDataQuery when creating PreparedStatements", false); + + static final YdbProperty DISABLE_AUTO_PREPARED_BATCHES = YdbProperty.bool("disableAutoPreparedBatches", + "Disable automatically detect list of tuples or structs in prepared statement", false); + + static final YdbProperty DISABLE_JDBC_PARAMETERS = YdbProperty.bool("disableJdbcParameters", + "Disable auto detect JDBC standart parameters '?'", false); + + static final YdbProperty DISABLE_JDBC_PARAMETERS_DECLARE = YdbProperty.bool("disableJdbcParameterDeclare", + "Disable enforce DECLARE section for JDBC parameters '?'", false); + + static final YdbProperty FORCE_QUERY_MODE = YdbProperty.enums("forceQueryMode", QueryType.class, + "Force usage one of query modes (DATA_QUERY, SCAN_QUERY, SCHEME_QUERY or EXPLAIN_QUERYn) for all statements" + ); + + private final boolean isDetectQueryType; + private final boolean isDetectJdbcParameters; + private final boolean isDeclareJdbcParameters; + + private final boolean isPrepareDataQueries; + private final boolean isDetectBatchQueries; + + private final QueryType forcedType; + + public YdbQueryProperties(YdbConfig config) throws SQLException { + Properties props = config.getProperties(); + + boolean disableAutoPreparedBatches = DISABLE_AUTO_PREPARED_BATCHES.readValue(props).getValue(); + boolean disablePrepareDataQueries = DISABLE_PREPARE_DATAQUERY.readValue(props).getValue(); + + this.isPrepareDataQueries = !disablePrepareDataQueries; + this.isDetectBatchQueries = !disablePrepareDataQueries && !disableAutoPreparedBatches; + + boolean disableJdbcParametersDeclare = DISABLE_JDBC_PARAMETERS_DECLARE.readValue(props).getValue(); + boolean disableJdbcParametersParse = DISABLE_JDBC_PARAMETERS.readValue(props).getValue(); + boolean disableSqlOperationsDetect = DISABLE_DETECT_SQL_OPERATIONS.readValue(props).getValue(); + + this.isDetectQueryType = !disableSqlOperationsDetect; + this.isDetectJdbcParameters = !disableSqlOperationsDetect && !disableJdbcParametersParse; + this.isDeclareJdbcParameters = !disableSqlOperationsDetect && !disableJdbcParametersParse + && !disableJdbcParametersDeclare; + + this.forcedType = FORCE_QUERY_MODE.readValue(props).getValue(); + } + + public boolean isDetectQueryType() { + return isDetectQueryType; + } + + public boolean isDetectJdbcParameters() { + return isDetectJdbcParameters; + } + + public boolean isDeclareJdbcParameters() { + return isDeclareJdbcParameters; + } + + public boolean iPrepareDataQueries() { + return isPrepareDataQueries; + } + + public boolean isDetectBatchQueries() { + return isDetectBatchQueries; + } + + public QueryType getForcedQueryType() { + return forcedType; + } +} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbValue.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbValue.java new file mode 100644 index 0000000..d050853 --- /dev/null +++ b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbValue.java @@ -0,0 +1,27 @@ +package tech.ydb.jdbc.settings; + +import java.util.Objects; + +class YdbValue { + private final boolean isPresent; + private final String rawValue; + private final T value; + + YdbValue(boolean isPresent, String rawValue, T value) { + this.isPresent = isPresent; + this.rawValue = Objects.requireNonNull(rawValue); + this.value = value; + } + + public T getValue() { + return value; + } + + public boolean hasValue() { + return isPresent; + } + + String rawValue() { + return rawValue; + } +} diff --git a/jdbc/src/test/java/tech/ydb/jdbc/YdbDriverProperitesTest.java b/jdbc/src/test/java/tech/ydb/jdbc/YdbDriverProperitesTest.java deleted file mode 100644 index 2633527..0000000 --- a/jdbc/src/test/java/tech/ydb/jdbc/YdbDriverProperitesTest.java +++ /dev/null @@ -1,549 +0,0 @@ -package tech.ydb.jdbc; - -import java.io.File; -import java.io.IOException; -import java.sql.DriverPropertyInfo; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.time.Duration; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.Properties; -import java.util.logging.Logger; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import javax.annotation.Nullable; - -import com.google.common.io.Files; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import tech.ydb.auth.AuthProvider; -import tech.ydb.jdbc.impl.helper.ExceptionAssert; -import tech.ydb.jdbc.settings.ParsedProperty; -import tech.ydb.jdbc.settings.YdbClientProperty; -import tech.ydb.jdbc.settings.YdbConnectionProperties; -import tech.ydb.jdbc.settings.YdbConnectionProperty; -import tech.ydb.jdbc.settings.YdbJdbcTools; -import tech.ydb.jdbc.settings.YdbOperationProperties; -import tech.ydb.jdbc.settings.YdbOperationProperty; -import tech.ydb.jdbc.settings.YdbProperties; - -public class YdbDriverProperitesTest { - private static final Logger logger = Logger.getLogger(YdbDriverProperitesTest.class.getName()); - public static final String TOKEN_FROM_FILE = "token-from-file"; - public static final String CERTIFICATE_FROM_FILE = "certificate-from-file"; - - private static File TOKEN_FILE; - private static File CERTIFICATE_FILE; - - private YdbDriver driver; - - @BeforeAll - public static void beforeAll() throws SQLException, IOException { - TOKEN_FILE = safeCreateFile(TOKEN_FROM_FILE); - CERTIFICATE_FILE = safeCreateFile(CERTIFICATE_FROM_FILE); - } - - @AfterAll - public static void afterAll() throws SQLException { - safeDeleteFile(TOKEN_FILE); - safeDeleteFile(CERTIFICATE_FILE); - } - - @BeforeEach - public void beforeEach() { - driver = new YdbDriver(); - } - - @AfterEach - public void afterEach() { - driver.close(); - } - - @Test - public void connectToUnsupportedUrl() throws SQLException { - Assertions.assertNull(driver.connect("jdbc:clickhouse:localhost:123", new Properties())); - } - - @ParameterizedTest - @MethodSource("urlsToParse") - public void parseURL(String url, @Nullable String connectionString, @Nullable String localDatacenter) - throws SQLException { - YdbProperties props = YdbJdbcTools.from(url, new Properties()); - YdbConnectionProperties connectionProperties = props.getConnectionProperties(); - Assertions.assertEquals(connectionString, connectionProperties.getConnectionString()); - - ParsedProperty dcProperty = connectionProperties.getParams().get(YdbConnectionProperty.LOCAL_DATACENTER); - Assertions.assertEquals(localDatacenter, Optional.ofNullable(dcProperty) - .map(ParsedProperty::getParsedValue) - .orElse(null)); - - } - - @ParameterizedTest - @MethodSource("urlsToCheck") - public void acceptsURL(String url, boolean accept, String connectionString) throws SQLException { - Assertions.assertEquals(accept, driver.acceptsURL(url)); - DriverPropertyInfo[] properties = driver.getPropertyInfo(url, new Properties()); - Assertions.assertNotNull(properties); - - if (accept) { - YdbProperties ydbProperties = YdbJdbcTools.from(url, new Properties()); - Assertions.assertNotNull(ydbProperties); - Assertions.assertEquals(connectionString, ydbProperties.getConnectionProperties().getConnectionString()); - } - } - - @SuppressWarnings("UnstableApiUsage") - @Test - public void getPropertyInfoDefault() throws SQLException { - String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db"; - - Properties properties = new Properties(); - DriverPropertyInfo[] propertyInfo = driver.getPropertyInfo(url, properties); - Assertions.assertEquals(new Properties(), properties); - - List actual = convertPropertyInfo(propertyInfo); - actual.forEach(logger::info); - - List expect = convertPropertyInfo(defaultPropertyInfo()); - Assertions.assertEquals(expect, actual); - - YdbProperties ydbProperties = YdbJdbcTools.from(url, properties); - Assertions.assertEquals("grpc://ydb-demo.testhost.org:2135/test/db", - ydbProperties.getConnectionProperties().getConnectionString()); - } - - @Test - public void getPropertyInfoAllFromUrl() throws SQLException { - String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db?" + - customizedProperties().entrySet().stream() - .map(e -> e.getKey() + "=" + e.getValue()) - .collect(Collectors.joining("&")); - - Properties properties = new Properties(); - DriverPropertyInfo[] propertyInfo = driver.getPropertyInfo(url, properties); - Assertions.assertEquals(new Properties(), properties); - - List actual = convertPropertyInfo(propertyInfo); - actual.forEach(logger::info); - - List expect = convertPropertyInfo(customizedPropertyInfo()); - Assertions.assertEquals(expect.size(), actual.size(), "Wrong size of all properties"); - for (int idx = 0; idx < expect.size(); idx += 1) { - Assertions.assertEquals(expect.get(idx), actual.get(idx), "Wrong parameter " + idx); - } - - YdbProperties ydbProperties = YdbJdbcTools.from(url, properties); - checkCustomizedProperties(ydbProperties); - } - - @Test - public void getPropertyInfoFromProperties() throws SQLException { - String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db"; - - Properties properties = customizedProperties(); - Properties copy = new Properties(); - copy.putAll(properties); - - DriverPropertyInfo[] propertyInfo = driver.getPropertyInfo(url, properties); - Assertions.assertEquals(copy, properties); - - List actual = convertPropertyInfo(propertyInfo); - actual.forEach(logger::info); - - List expect = convertPropertyInfo(customizedPropertyInfo()); - Assertions.assertEquals(expect.size(), actual.size(), "Wrong size of all properties"); - for (int idx = 0; idx < expect.size(); idx += 1) { - Assertions.assertEquals(expect.get(idx), actual.get(idx), "Wrong parameter " + idx); - } - - YdbProperties ydbProperties = YdbJdbcTools.from(url, properties); - checkCustomizedProperties(ydbProperties); - } - - @SuppressWarnings("UnstableApiUsage") - @Test - public void getPropertyInfoOverwrite() throws SQLException { - String url = "jdbc:ydb:ydb-demo.testhost.org:2135/testing/ydb?localDatacenter=sas"; - Properties properties = new Properties(); - properties.put("localDatacenter", "vla"); - - Properties copy = new Properties(); - copy.putAll(properties); - - DriverPropertyInfo[] propertyInfo = driver.getPropertyInfo(url, properties); - Assertions.assertEquals(copy, properties); - - List actual = convertPropertyInfo(propertyInfo); - actual.forEach(logger::info); - - // URL will always overwrite properties - List expect = convertPropertyInfo(defaultPropertyInfo("sas")); - Assertions.assertEquals(expect.size(), actual.size(), "Wrong size of default properties"); - for (int idx = 0; idx < expect.size(); idx += 1) { - Assertions.assertEquals(expect.get(idx), actual.get(idx), "Wrong default parameter " + idx); - } - - YdbProperties ydbProperties = YdbJdbcTools.from(url, properties); - Assertions.assertEquals("grpc://ydb-demo.testhost.org:2135/testing/ydb", - ydbProperties.getConnectionProperties().getConnectionString()); - } - - @ParameterizedTest(name = "[{index}] {1}") - @MethodSource("tokensToCheck") - public void getTokenAs(String token, String expectValue) throws SQLException { - if ("file:".equals(token)) { - token += TOKEN_FILE.getAbsolutePath(); - } - - String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db?token=" + token; - Properties properties = new Properties(); - YdbProperties ydbProperties = YdbJdbcTools.from(url, properties); - - YdbConnectionProperties props = ydbProperties.getConnectionProperties(); - Assertions.assertEquals(expectValue, ((AuthProvider)props.getProperty(YdbConnectionProperty.TOKEN).getParsedValue()) - .createAuthIdentity(null).getToken()); - } - - @ParameterizedTest - @MethodSource("unknownFiles") - public void getTokenAsInvalid(String token, String expectException) { - String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db?token=" + token; - ExceptionAssert.sqlException( - "Unable to convert property token: " + expectException, - () -> YdbJdbcTools.from(url, new Properties()) - ); - } - - @ParameterizedTest(name = "[{index}] {1}") - @MethodSource("certificatesToCheck") - public void getCaCertificateAs(String certificate, String expectValue) throws SQLException { - if ("file:".equals(certificate)) { - certificate += CERTIFICATE_FILE.getAbsolutePath(); - } - String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db" + - "?secureConnectionCertificate=" + certificate; - Properties properties = new Properties(); - YdbProperties ydbProperties = YdbJdbcTools.from(url, properties); - - YdbConnectionProperties props = ydbProperties.getConnectionProperties(); - Assertions.assertArrayEquals(expectValue.getBytes(), - props.getProperty(YdbConnectionProperty.SECURE_CONNECTION_CERTIFICATE).getParsedValue()); - } - - @ParameterizedTest - @MethodSource("unknownFiles") - public void getCaCertificateAsInvalid(String certificate, String expectException) { - String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db" + - "?secureConnectionCertificate=" + certificate; - ExceptionAssert.sqlException( - "Unable to convert property secureConnectionCertificate: " + expectException, - () -> YdbJdbcTools.from(url, new Properties()) - ); - } - - @ParameterizedTest - @MethodSource("invalidDurationParams") - public void invalidDuration(String param) { - String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db?" + param + "=1bc"; - ExceptionAssert.sqlException("Unable to convert property " + param + - ": Unable to parse value [1bc] -> [PT1BC] as Duration: Text cannot be parsed to a Duration", - () -> YdbJdbcTools.from(url, new Properties())); - } - - @ParameterizedTest - @MethodSource("invalidIntegerParams") - public void invalidInteger(String param) { - String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db?" + param + "=1bc"; - ExceptionAssert.sqlException("Unable to convert property " + param + - ": Unable to parse value [1bc] as Integer: For input string: \"1bc\"", - () -> YdbJdbcTools.from(url, new Properties())); - } - - @Test - public void getMajorVersion() { - Assertions.assertEquals(2, driver.getMajorVersion()); - } - - @Test - public void getMinorVersion() { - Assertions.assertTrue(driver.getMinorVersion() >= 0); - } - - @Test - public void jdbcCompliant() { - Assertions.assertFalse(driver.jdbcCompliant()); - } - - @Test - public void getParentLogger() throws SQLFeatureNotSupportedException { - Assertions.assertNotNull(driver.getParentLogger()); - } - - static List convertPropertyInfo(DriverPropertyInfo[] propertyInfo) { - return Stream.of(propertyInfo) - .map(YdbDriverProperitesTest::asString) - .collect(Collectors.toList()); - } - - static DriverPropertyInfo[] defaultPropertyInfo() { - return defaultPropertyInfo(null); - } - - static DriverPropertyInfo[] defaultPropertyInfo(@Nullable String localDatacenter) { - return new DriverPropertyInfo[]{ - YdbConnectionProperty.LOCAL_DATACENTER.toDriverPropertyInfo(localDatacenter), - YdbConnectionProperty.SECURE_CONNECTION.toDriverPropertyInfo(null), - YdbConnectionProperty.SECURE_CONNECTION_CERTIFICATE.toDriverPropertyInfo(null), - YdbConnectionProperty.TOKEN.toDriverPropertyInfo(null), - - YdbConnectionProperty.SERVICE_ACCOUNT_FILE.toDriverPropertyInfo(null), - YdbConnectionProperty.USE_METADATA.toDriverPropertyInfo(null), - - YdbClientProperty.KEEP_QUERY_TEXT.toDriverPropertyInfo(null), - YdbClientProperty.SESSION_KEEP_ALIVE_TIME.toDriverPropertyInfo(null), - YdbClientProperty.SESSION_MAX_IDLE_TIME.toDriverPropertyInfo(null), - YdbClientProperty.SESSION_POOL_SIZE_MIN.toDriverPropertyInfo(null), - YdbClientProperty.SESSION_POOL_SIZE_MAX.toDriverPropertyInfo(null), - - YdbOperationProperty.JOIN_DURATION.toDriverPropertyInfo("5m"), - YdbOperationProperty.QUERY_TIMEOUT.toDriverPropertyInfo("0s"), - YdbOperationProperty.SCAN_QUERY_TIMEOUT.toDriverPropertyInfo("5m"), - YdbOperationProperty.FAIL_ON_TRUNCATED_RESULT.toDriverPropertyInfo("false"), - YdbOperationProperty.SESSION_TIMEOUT.toDriverPropertyInfo("5s"), - YdbOperationProperty.DEADLINE_TIMEOUT.toDriverPropertyInfo("0s"), - YdbOperationProperty.AUTOCOMMIT.toDriverPropertyInfo("true"), - YdbOperationProperty.TRANSACTION_LEVEL.toDriverPropertyInfo("8"), - - YdbOperationProperty.CACHE_CONNECTIONS_IN_DRIVER.toDriverPropertyInfo("true"), - YdbOperationProperty.PREPARED_STATEMENT_CACHE_SIZE.toDriverPropertyInfo("256"), - - YdbOperationProperty.DISABLE_DETECT_SQL_OPERATIONS.toDriverPropertyInfo("false"), - YdbOperationProperty.DISABLE_PREPARE_DATAQUERY.toDriverPropertyInfo("false"), - YdbOperationProperty.DISABLE_AUTO_PREPARED_BATCHES.toDriverPropertyInfo("false"), - YdbOperationProperty.DISABLE_JDBC_PARAMETERS.toDriverPropertyInfo("false"), - YdbOperationProperty.DISABLE_JDBC_PARAMETERS_DECLARE.toDriverPropertyInfo("false"), - - YdbOperationProperty.SCAN_QUERY_TX_MODE.toDriverPropertyInfo("ERROR"), - YdbOperationProperty.SCHEME_QUERY_TX_MODE.toDriverPropertyInfo("ERROR"), - YdbOperationProperty.FORCE_QUERY_MODE.toDriverPropertyInfo(null), - }; - } - - static Properties customizedProperties() { - Properties properties = new Properties(); - properties.setProperty("localDatacenter", "sas"); - properties.setProperty("secureConnection", "true"); - properties.setProperty("readTimeout", "2m"); - properties.setProperty("token", "x-secured-token"); - - properties.setProperty("saFile", "x-secured-file"); - properties.setProperty("useMetadata", "true"); - - properties.setProperty("keepQueryText", "true"); - properties.setProperty("sessionKeepAliveTime", "15m"); - properties.setProperty("sessionMaxIdleTime", "5m"); - properties.setProperty("sessionPoolSizeMin", "3"); - properties.setProperty("sessionPoolSizeMax", "4"); - - properties.setProperty("joinDuration", "6m"); - properties.setProperty("keepInQueryCache", "true"); - properties.setProperty("queryTimeout", "2m"); - properties.setProperty("scanQueryTimeout", "3m"); - properties.setProperty("failOnTruncatedResult", "false"); - properties.setProperty("sessionTimeout", "6s"); - properties.setProperty("deadlineTimeout", "1s"); - properties.setProperty("autoCommit", "true"); - properties.setProperty("transactionLevel", "16"); - - properties.setProperty("cacheConnectionsInDriver", "false"); - properties.setProperty("preparedStatementCacheQueries", "100"); - - properties.setProperty("disablePrepareDataQuery", "true"); - properties.setProperty("disableAutoPreparedBatches", "true"); - properties.setProperty("disableDetectSqlOperations", "true"); - properties.setProperty("disableJdbcParameters", "true"); - properties.setProperty("disableJdbcParameterDeclare", "true"); - - properties.setProperty("scanQueryTxMode", "FAKE_TX"); - properties.setProperty("schemeQueryTxMode", "SHADOW_COMMIT"); - - properties.setProperty("forceQueryMode", "SCAN_QUERY"); - return properties; - } - - static DriverPropertyInfo[] customizedPropertyInfo() { - return new DriverPropertyInfo[]{ - YdbConnectionProperty.LOCAL_DATACENTER.toDriverPropertyInfo("sas"), - YdbConnectionProperty.SECURE_CONNECTION.toDriverPropertyInfo("true"), - YdbConnectionProperty.SECURE_CONNECTION_CERTIFICATE.toDriverPropertyInfo(null), - YdbConnectionProperty.TOKEN.toDriverPropertyInfo("x-secured-token"), - - YdbConnectionProperty.SERVICE_ACCOUNT_FILE.toDriverPropertyInfo("x-secured-file"), - YdbConnectionProperty.USE_METADATA.toDriverPropertyInfo("true"), - - YdbClientProperty.KEEP_QUERY_TEXT.toDriverPropertyInfo("true"), - YdbClientProperty.SESSION_KEEP_ALIVE_TIME.toDriverPropertyInfo("15m"), - YdbClientProperty.SESSION_MAX_IDLE_TIME.toDriverPropertyInfo("5m"), - YdbClientProperty.SESSION_POOL_SIZE_MIN.toDriverPropertyInfo("3"), - YdbClientProperty.SESSION_POOL_SIZE_MAX.toDriverPropertyInfo("4"), - - YdbOperationProperty.JOIN_DURATION.toDriverPropertyInfo("6m"), - YdbOperationProperty.QUERY_TIMEOUT.toDriverPropertyInfo("2m"), - YdbOperationProperty.SCAN_QUERY_TIMEOUT.toDriverPropertyInfo("3m"), - YdbOperationProperty.FAIL_ON_TRUNCATED_RESULT.toDriverPropertyInfo("false"), - YdbOperationProperty.SESSION_TIMEOUT.toDriverPropertyInfo("6s"), - YdbOperationProperty.DEADLINE_TIMEOUT.toDriverPropertyInfo("1s"), - YdbOperationProperty.AUTOCOMMIT.toDriverPropertyInfo("true"), - YdbOperationProperty.TRANSACTION_LEVEL.toDriverPropertyInfo("16"), - - YdbOperationProperty.CACHE_CONNECTIONS_IN_DRIVER.toDriverPropertyInfo("false"), - YdbOperationProperty.PREPARED_STATEMENT_CACHE_SIZE.toDriverPropertyInfo("100"), - - YdbOperationProperty.DISABLE_DETECT_SQL_OPERATIONS.toDriverPropertyInfo("true"), - YdbOperationProperty.DISABLE_PREPARE_DATAQUERY.toDriverPropertyInfo("true"), - YdbOperationProperty.DISABLE_AUTO_PREPARED_BATCHES.toDriverPropertyInfo("true"), - YdbOperationProperty.DISABLE_JDBC_PARAMETERS.toDriverPropertyInfo("true"), - YdbOperationProperty.DISABLE_JDBC_PARAMETERS_DECLARE.toDriverPropertyInfo("true"), - - YdbOperationProperty.SCAN_QUERY_TX_MODE.toDriverPropertyInfo("FAKE_TX"), - YdbOperationProperty.SCHEME_QUERY_TX_MODE.toDriverPropertyInfo("SHADOW_COMMIT"), - YdbOperationProperty.FORCE_QUERY_MODE.toDriverPropertyInfo("SCAN_QUERY"), - }; - } - - static void checkCustomizedProperties(YdbProperties properties) { - YdbConnectionProperties conn = properties.getConnectionProperties(); - Assertions.assertEquals("grpc://ydb-demo.testhost.org:2135/test/db", - conn.getConnectionString()); - - YdbOperationProperties ops = properties.getOperationProperties(); - Assertions.assertEquals(Duration.ofMinutes(6), ops.getJoinDuration()); - Assertions.assertEquals(Duration.ofMinutes(2), ops.getQueryTimeout()); - Assertions.assertEquals(Duration.ofMinutes(3), ops.getScanQueryTimeout()); - Assertions.assertFalse(ops.isFailOnTruncatedResult()); - Assertions.assertEquals(Duration.ofSeconds(6), ops.getSessionTimeout()); - Assertions.assertTrue(ops.isAutoCommit()); - Assertions.assertEquals(YdbConst.ONLINE_CONSISTENT_READ_ONLY, ops.getTransactionLevel()); - Assertions.assertFalse(ops.isCacheConnectionsInDriver()); - } - - static String asString(DriverPropertyInfo info) { - Assertions.assertNull(info.choices); - return String.format("%s=%s (%s, required = %s)", info.name, info.value, info.description, info.required); - } - - @SuppressWarnings("UnstableApiUsage") - public static Collection urlsToParse() { - return Arrays.asList( - Arguments.of("jdbc:ydb:ydb-demo.testhost.org:2135", - "grpc://ydb-demo.testhost.org:2135", - null), - Arguments.of("jdbc:ydb:ydb-demo.testhost.org:2135/demo/ci/testing/ci", - "grpc://ydb-demo.testhost.org:2135/demo/ci/testing/ci", - null), - Arguments.of("jdbc:ydb:grpc://ydb-demo.testhost.org:2135/demo/ci/testing/ci?localDatacenter=man", - "grpc://ydb-demo.testhost.org:2135/demo/ci/testing/ci", - "man"), - Arguments.of("jdbc:ydb:grpcs://ydb-demo.testhost.org:2135?localDatacenter=man", - "grpcs://ydb-demo.testhost.org:2135", - "man") - ); - } - - public static Collection urlsToCheck() { - return Arrays.asList( - Arguments.of("jdbc:ydb:", - true, ""), - Arguments.of("jdbc:ydb:ydb-demo.testhost.org:2135", - true, "grpc://ydb-demo.testhost.org:2135"), - Arguments.of("jdbc:ydb:ydb-demo.testhost.org", - true, "grpc://ydb-demo.testhost.org"), - Arguments.of("jdbc:ydb:ydb-demo.testhost.org:2135?database=test/db", - true, "grpc://ydb-demo.testhost.org:2135/test/db"), - Arguments.of("jdbc:ydb:grpcs://ydb-demo.testhost.org", - true, "grpcs://ydb-demo.testhost.org"), - Arguments.of("jdbc:ydb:ydb-demo.testhost.org:2170?database=/test/db", - true, "grpc://ydb-demo.testhost.org:2170/test/db"), - Arguments.of("jdbc:ydb:ydb-demo.testhost.org:2133/test/db", - true, "grpc://ydb-demo.testhost.org:2133/test/db"), - Arguments.of("jdbc:ydb:grpcs://ydb-demo.testhost.org?database=test/db&dc=man", - true, "grpcs://ydb-demo.testhost.org/test/db"), - Arguments.of("jdbc:ydb:ydb-demo.testhost.org:2135/test/db?dc=man", - true, "grpc://ydb-demo.testhost.org:2135/test/db"), - Arguments.of("ydb:", - false, null), - Arguments.of("jdbc:ydb", - false, null), - Arguments.of("jdbc:clickhouse://man", - false, null) - ); - } - - public static Collection tokensToCheck() { - return Arrays.asList( - Arguments.of("classpath:data/token.txt", "token-from-classpath"), - Arguments.of("file:", TOKEN_FROM_FILE)); - } - - public static Collection certificatesToCheck() { - return Arrays.asList( - Arguments.of("classpath:data/certificate.txt", "certificate-from-classpath"), - Arguments.of("file:", CERTIFICATE_FROM_FILE)); - } - - public static Collection unknownFiles() { - return Arrays.asList( - Arguments.of("classpath:data/unknown-file.txt", - "Unable to find classpath resource: classpath:data/unknown-file.txt"), - Arguments.of("file:data/unknown-file.txt", - "Unable to read resource from file:data/unknown-file.txt")); - } - - public static Collection invalidDurationParams() { - return Arrays.asList( - Arguments.of("sessionKeepAliveTime"), - Arguments.of("sessionMaxIdleTime"), - Arguments.of("joinDuration"), - Arguments.of("queryTimeout"), - Arguments.of("scanQueryTimeout"), - Arguments.of("sessionTimeout"), - Arguments.of("deadlineTimeout") - ); - } - - public static Collection invalidIntegerParams() { - return Arrays.asList( - Arguments.of("sessionPoolSizeMin"), - Arguments.of("sessionPoolSizeMax"), - Arguments.of("transactionLevel") - ); - } - - @SuppressWarnings("UnstableApiUsage") - private static File safeCreateFile(String content) throws IOException { - File file = File.createTempFile("junit", "ydb"); - Files.write(content.getBytes(), file); - return file; - } - - private static void safeDeleteFile(@Nullable File file) { - if (file != null) { - if (!file.delete()) { - file.deleteOnExit(); - } - } - } -} diff --git a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementImplTest.java b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementImplTest.java index 3141948..e721200 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementImplTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementImplTest.java @@ -410,6 +410,10 @@ public void executeScanQueryInTx(SqlQueries.YqlQuery mode) throws SQLException { TextSelectAssert.of(select.executeQuery(), "c_Text", "Text") .nextRow(1, "value-1") .noNextRows(); + + select.setInt("key", 2); + TextSelectAssert.of(select.executeQuery(), "c_Text", "Text") + .noNextRows(); } } finally { jdbc.connection().setAutoCommit(true); diff --git a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbQueryConnectionImplTest.java b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbQueryConnectionImplTest.java new file mode 100644 index 0000000..3e45b16 --- /dev/null +++ b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbQueryConnectionImplTest.java @@ -0,0 +1,772 @@ +package tech.ydb.jdbc.impl; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import tech.ydb.jdbc.YdbConnection; +import tech.ydb.jdbc.YdbConst; +import tech.ydb.jdbc.YdbDatabaseMetaData; +import tech.ydb.jdbc.impl.helper.ExceptionAssert; +import tech.ydb.jdbc.impl.helper.JdbcConnectionExtention; +import tech.ydb.jdbc.impl.helper.SqlQueries; +import tech.ydb.jdbc.impl.helper.TableAssert; +import tech.ydb.table.values.ListType; +import tech.ydb.table.values.ListValue; +import tech.ydb.table.values.PrimitiveType; +import tech.ydb.table.values.PrimitiveValue; +import tech.ydb.test.junit5.YdbHelperExtension; + +public class YdbQueryConnectionImplTest { + @RegisterExtension + private static final YdbHelperExtension ydb = new YdbHelperExtension(); + + @RegisterExtension + private static final JdbcConnectionExtention jdbc = new JdbcConnectionExtention(ydb) + .withArg("useQueryService", "true"); + + private static final SqlQueries QUERIES = new SqlQueries("ydb_connection_test"); + private static final String SELECT_2_2 = "select 2 + 2"; + private static final String SIMPLE_UPSERT = QUERIES + .withTableName("upsert into #tableName (key, c_Text) values (1, '2')"); + + @BeforeAll + public static void createTable() throws SQLException { + try (Statement statement = jdbc.connection().createStatement()) { + // create simple tables + statement.execute(QUERIES.createTableSQL()); + } + } + + @AfterAll + public static void dropTable() throws SQLException { + try (Statement statement = jdbc.connection().createStatement();) { + // create test table + statement.execute(QUERIES.dropTableSQL()); + } + } + + @BeforeEach + public void checkTransactionState() throws SQLException { + Assertions.assertNull(getTxId(jdbc.connection()), "Transaction must be empty before test"); + } + + @AfterEach + public void checkTableIsEmpty() throws SQLException { + if (jdbc.connection().isClosed()) { + return; + } + + try (Statement statement = jdbc.connection().createStatement()) { + try (ResultSet result = statement.executeQuery(QUERIES.selectAllSQL())) { + Assertions.assertFalse(result.next(), "Table must be empty after test"); + } + } + jdbc.connection().close(); + } + + private void cleanTable() throws SQLException { + try (Statement statement = jdbc.connection().createStatement()) { + statement.execute(QUERIES.deleteAllSQL()); + } + } + + private String getTxId(Connection connection) throws SQLException { + return connection.unwrap(YdbConnection.class).getYdbTxId(); + } + + @Test + public void connectionUnwrapTest() throws SQLException { + Assertions.assertTrue(jdbc.connection().isWrapperFor(YdbConnection.class)); + Assertions.assertSame(jdbc.connection(), jdbc.connection().unwrap(YdbConnection.class)); + + Assertions.assertFalse(jdbc.connection().isWrapperFor(YdbDatabaseMetaData.class)); + + SQLException ex = Assertions.assertThrows(SQLException.class, + () -> jdbc.connection().unwrap(YdbDatabaseMetaData.class)); + Assertions.assertEquals("Cannot unwrap to interface tech.ydb.jdbc.YdbDatabaseMetaData", ex.getMessage()); + } + + @Test + public void createStatement() throws SQLException { + try (Statement statement = jdbc.connection().createStatement()) { + TableAssert.assertSelectInt(4, statement.executeQuery("select 1 + 3")); + } + try (Statement statement = jdbc.connection().createStatement( + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) { + TableAssert.assertSelectInt(5, statement.executeQuery("select 2 + 3")); + } + try (Statement statement = jdbc.connection().createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) { + TableAssert.assertSelectInt(3, statement.executeQuery("select 2 + 1")); + } + try (Statement statement = jdbc.connection().createStatement( + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT)) { + TableAssert.assertSelectInt(2, statement.executeQuery("select 1 + 1")); + } + } + + @Test + public void createStatementInvalid() { + ExceptionAssert.sqlFeatureNotSupported( + "resultSetType must be ResultSet.TYPE_FORWARD_ONLY or ResultSet.TYPE_SCROLL_INSENSITIVE", + () -> jdbc.connection().createStatement( + ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT + ) + ); + + ExceptionAssert.sqlFeatureNotSupported( + "resultSetConcurrency must be ResultSet.CONCUR_READ_ONLY", + () -> jdbc.connection().createStatement( + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE, ResultSet.HOLD_CURSORS_OVER_COMMIT + ) + ); + + ExceptionAssert.sqlFeatureNotSupported( + "resultSetHoldability must be ResultSet.HOLD_CURSORS_OVER_COMMIT", + () -> jdbc.connection().createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT + ) + ); + } + + @Test + public void prepareStatement() throws SQLException { + try (PreparedStatement statement = jdbc.connection().prepareStatement("select 1 + 3")) { + TableAssert.assertSelectInt(4, statement.executeQuery()); + } + try (PreparedStatement statement = jdbc.connection().prepareStatement("select 2 + 3", + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) { + TableAssert.assertSelectInt(5, statement.executeQuery()); + } + try (PreparedStatement statement = jdbc.connection().prepareStatement("select 2 + 1", + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) { + TableAssert.assertSelectInt(3, statement.executeQuery()); + } + try (PreparedStatement statement = jdbc.connection().prepareStatement("select 1 + 1", + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT)) { + TableAssert.assertSelectInt(2, statement.executeQuery()); + } + } + + @Test + public void prepareStatementInvalid() throws SQLException { + ExceptionAssert.sqlFeatureNotSupported("Auto-generated keys are not supported", + () -> jdbc.connection().prepareStatement(SELECT_2_2, new int[] {}) + ); + + ExceptionAssert.sqlFeatureNotSupported("Auto-generated keys are not supported", + () -> jdbc.connection().prepareStatement(SELECT_2_2, new String[] {}) + ); + + ExceptionAssert.sqlFeatureNotSupported("Auto-generated keys are not supported", + () -> jdbc.connection().prepareStatement(SELECT_2_2, Statement.RETURN_GENERATED_KEYS) + ); + + ExceptionAssert.sqlFeatureNotSupported( + "resultSetType must be ResultSet.TYPE_FORWARD_ONLY or ResultSet.TYPE_SCROLL_INSENSITIVE", + () -> jdbc.connection().prepareStatement(SELECT_2_2, + ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT + ) + ); + + ExceptionAssert.sqlFeatureNotSupported( + "resultSetConcurrency must be ResultSet.CONCUR_READ_ONLY", + () -> jdbc.connection().prepareStatement(SELECT_2_2, + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE, ResultSet.HOLD_CURSORS_OVER_COMMIT + ) + ); + + ExceptionAssert.sqlFeatureNotSupported( + "resultSetHoldability must be ResultSet.HOLD_CURSORS_OVER_COMMIT", + () -> jdbc.connection().prepareStatement(SELECT_2_2, + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT + ) + ); + } + + @Test + public void prepareCallNotSupported() { + ExceptionAssert.sqlFeatureNotSupported("Prepared calls are not supported", + () -> jdbc.connection().prepareCall(SELECT_2_2) + ); + + ExceptionAssert.sqlFeatureNotSupported("Prepared calls are not supported", + () -> jdbc.connection().prepareCall(SELECT_2_2, + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY) + ); + + ExceptionAssert.sqlFeatureNotSupported("Prepared calls are not supported", + () -> jdbc.connection().prepareCall(SELECT_2_2, + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT) + ); + } + + @Test + public void nativeSQL() throws SQLException { + String nativeSQL = jdbc.connection().nativeSQL("select ? + ?"); + Assertions.assertEquals("-- DECLARE 2 PARAMETERS\nselect $jp1 + $jp2", nativeSQL); + } + + @Test + public void clientInfo() throws SQLException { + Assertions.assertEquals(new Properties(), jdbc.connection().getClientInfo()); + + Properties properties = new Properties(); + properties.setProperty("key", "value"); + jdbc.connection().setClientInfo(properties); + + Assertions.assertEquals(new Properties(), jdbc.connection().getClientInfo()); + jdbc.connection().setClientInfo("key", "value"); + + Assertions.assertEquals(new Properties(), jdbc.connection().getClientInfo()); + Assertions.assertNull(jdbc.connection().getClientInfo("key")); + } + + @Test + public void invalidSQLCancelTransaction() throws SQLException { + jdbc.connection().setAutoCommit(false); + try (Statement statement = jdbc.connection().createStatement()) { + statement.execute(SELECT_2_2); + String txId = getTxId(jdbc.connection()); + Assertions.assertNotNull(txId); + + ExceptionAssert.ydbException("Column reference 'x' (S_ERROR)", () -> statement.execute("select 2 + x")); + + statement.execute(SELECT_2_2); + Assertions.assertNotNull(getTxId(jdbc.connection())); + Assertions.assertNotEquals(txId, getTxId(jdbc.connection())); + } finally { + jdbc.connection().setAutoCommit(true); + } + } + + @Test + public void autoCommit() throws SQLException { + jdbc.connection().setAutoCommit(false); + try (Statement statement = jdbc.connection().createStatement()) { + statement.execute(SELECT_2_2); + String txId = getTxId(jdbc.connection()); + Assertions.assertNotNull(txId); + + Assertions.assertFalse(jdbc.connection().getAutoCommit()); + + jdbc.connection().setAutoCommit(false); + Assertions.assertFalse(jdbc.connection().getAutoCommit()); + Assertions.assertEquals(txId, getTxId(jdbc.connection())); + + jdbc.connection().setAutoCommit(true); + Assertions.assertTrue(jdbc.connection().getAutoCommit()); + Assertions.assertNull(getTxId(jdbc.connection())); + + statement.execute(SELECT_2_2); + Assertions.assertNull(getTxId(jdbc.connection())); + + statement.execute(SELECT_2_2); + Assertions.assertNull(getTxId(jdbc.connection())); + + jdbc.connection().setAutoCommit(false); + Assertions.assertFalse(jdbc.connection().getAutoCommit()); + Assertions.assertNull(getTxId(jdbc.connection())); + } finally { + jdbc.connection().setAutoCommit(true); + } + } + + @Test + public void commit() throws SQLException { + jdbc.connection().setAutoCommit(false); + try (Statement statement = jdbc.connection().createStatement()) { + Assertions.assertTrue(statement.execute(SELECT_2_2)); + String txId = getTxId(jdbc.connection()); + Assertions.assertNotNull(txId); + + Assertions.assertTrue(statement.execute(SELECT_2_2)); + Assertions.assertEquals(txId, getTxId(jdbc.connection())); + + Assertions.assertTrue(statement.execute(QUERIES.selectAllSQL())); + Assertions.assertEquals(txId, getTxId(jdbc.connection())); + + Assertions.assertFalse(statement.execute(SIMPLE_UPSERT)); + Assertions.assertEquals(txId, getTxId(jdbc.connection())); + + Assertions.assertTrue(statement.execute(SELECT_2_2)); + Assertions.assertEquals(txId, getTxId(jdbc.connection())); + + jdbc.connection().commit(); + Assertions.assertNull(getTxId(jdbc.connection())); + + jdbc.connection().commit(); // does nothing + Assertions.assertNull(getTxId(jdbc.connection())); + + try (ResultSet result = statement.executeQuery(QUERIES.selectAllSQL())) { + Assertions.assertTrue(result.next()); + } + } finally { + cleanTable(); + jdbc.connection().setAutoCommit(true); + } + } + + @Test + public void schemeQueryInTx() throws SQLException { + jdbc.connection().setAutoCommit(false); + try (Statement statement = jdbc.connection().createStatement()) { + Assertions.assertTrue(statement.execute(SELECT_2_2)); + String txId = getTxId(jdbc.connection()); + Assertions.assertNotNull(txId); + + Assertions.assertTrue(statement.execute(SELECT_2_2)); + Assertions.assertEquals(txId, getTxId(jdbc.connection())); + + Assertions.assertTrue(statement.execute(QUERIES.selectAllSQL())); + Assertions.assertEquals(txId, getTxId(jdbc.connection())); + + Assertions.assertFalse(statement.execute(SIMPLE_UPSERT)); + Assertions.assertEquals(txId, getTxId(jdbc.connection())); + + Assertions.assertTrue(statement.execute(SELECT_2_2)); + Assertions.assertEquals(txId, getTxId(jdbc.connection())); + + ExceptionAssert.sqlException(YdbConst.SCHEME_QUERY_INSIDE_TRANSACTION, + () -> statement.execute("CREATE TABLE tmp_table (id Int32, primary key (id))")); + Assertions.assertEquals(txId, getTxId(jdbc.connection())); + + ExceptionAssert.sqlException(YdbConst.SCHEME_QUERY_INSIDE_TRANSACTION, + () -> statement.execute("DROP TABLE tmp_table")); + Assertions.assertEquals(txId, getTxId(jdbc.connection())); + + try (ResultSet result = statement.executeQuery(QUERIES.selectAllSQL())) { + Assertions.assertTrue(result.next()); + } + } finally { + cleanTable(); + jdbc.connection().setAutoCommit(true); + } + } + + @Test + public void schemeQueryWithShadowCommit() throws SQLException { + try (Connection connection = jdbc.createCustomConnection("schemeQueryTxMode", "SHADOW_COMMIT")) { + connection.setAutoCommit(false); + + try (Statement statement = connection.createStatement()) { + Assertions.assertTrue(statement.execute(SELECT_2_2)); + String txId = getTxId(connection); + Assertions.assertNotNull(txId); + + Assertions.assertTrue(statement.execute(SELECT_2_2)); + Assertions.assertEquals(txId, getTxId(connection)); + + Assertions.assertTrue(statement.execute(QUERIES.selectAllSQL())); + Assertions.assertEquals(txId, getTxId(connection)); + + Assertions.assertFalse(statement.execute(SIMPLE_UPSERT)); + Assertions.assertEquals(txId, getTxId(connection)); + + Assertions.assertTrue(statement.execute(SELECT_2_2)); + Assertions.assertEquals(txId, getTxId(connection)); + + // DDL - equals to commit + statement.execute("CREATE TABLE tmp_table (id Int32, primary key (id))"); + Assertions.assertNull(getTxId(connection)); + + statement.execute("DROP TABLE tmp_table"); + Assertions.assertNull(getTxId(connection)); + + try (ResultSet result = statement.executeQuery(QUERIES.selectAllSQL())) { + Assertions.assertTrue(result.next()); + } + } + } finally { + cleanTable(); + } + } + + @Test + public void schemeQueryInFakeTx() throws SQLException { + try (Connection connection = jdbc.createCustomConnection("schemeQueryTxMode", "FAKE_TX")) { + connection.setAutoCommit(false); + + try (Statement statement = connection.createStatement()) { + Assertions.assertTrue(statement.execute(SELECT_2_2)); + String txId = getTxId(connection); + Assertions.assertNotNull(txId); + + Assertions.assertTrue(statement.execute(SELECT_2_2)); + Assertions.assertEquals(txId, getTxId(connection)); + + Assertions.assertTrue(statement.execute(QUERIES.selectAllSQL())); + Assertions.assertEquals(txId, getTxId(connection)); + + Assertions.assertFalse(statement.execute(SIMPLE_UPSERT)); + Assertions.assertEquals(txId, getTxId(connection)); + + Assertions.assertTrue(statement.execute(SELECT_2_2)); + Assertions.assertEquals(txId, getTxId(connection)); + + // FAKE TX - DDL will be executed outside current transaction + statement.execute("CREATE TABLE tmp_table (id Int32, primary key (id))"); + Assertions.assertEquals(txId, getTxId(connection)); + + statement.execute("DROP TABLE tmp_table"); + Assertions.assertEquals(txId, getTxId(connection)); + + try (ResultSet result = statement.executeQuery(QUERIES.selectAllSQL())) { + Assertions.assertTrue(result.next()); + } + } + } finally { + cleanTable(); + } + } + + @Test + public void rollback() throws SQLException { + jdbc.connection().setAutoCommit(false); + try (Statement statement = jdbc.connection().createStatement()) { + Assertions.assertTrue(statement.execute(SELECT_2_2)); + String txId = getTxId(jdbc.connection()); + Assertions.assertNotNull(txId); + + Assertions.assertTrue(statement.execute(SELECT_2_2)); + Assertions.assertEquals(txId, getTxId(jdbc.connection())); + + Assertions.assertTrue(statement.execute(QUERIES.selectAllSQL())); + Assertions.assertEquals(txId, getTxId(jdbc.connection())); + + Assertions.assertFalse(statement.execute(SIMPLE_UPSERT)); + Assertions.assertEquals(txId, getTxId(jdbc.connection())); + + Assertions.assertTrue(statement.execute(SELECT_2_2)); + Assertions.assertEquals(txId, getTxId(jdbc.connection())); + + jdbc.connection().rollback(); + Assertions.assertNull(getTxId(jdbc.connection())); + + jdbc.connection().rollback(); // does nothing + Assertions.assertNull(getTxId(jdbc.connection())); + + try (ResultSet result = statement.executeQuery(QUERIES.selectAllSQL())) { + Assertions.assertFalse(result.next()); + } + } finally { + jdbc.connection().setAutoCommit(true); + } + } + + @Test + public void commitInvalidTx() throws SQLException { + jdbc.connection().setAutoCommit(false); + try (Statement statement = jdbc.connection().createStatement()) { + statement.execute(SIMPLE_UPSERT); + statement.execute(SIMPLE_UPSERT); + + ExceptionAssert.ydbException("Member not found: key2. Did you mean key?", + () -> statement.executeQuery(QUERIES.wrongSelectSQL())); + + Assertions.assertNull(getTxId(jdbc.connection())); + + jdbc.connection().commit(); // Nothing to commit, transaction was rolled back already + Assertions.assertNull(getTxId(jdbc.connection())); + } finally { + cleanTable(); + jdbc.connection().setAutoCommit(true); + } + } + + @Test + public void rollbackInvalidTx() throws SQLException { + jdbc.connection().setAutoCommit(false); + try (Statement statement = jdbc.connection().createStatement()) { + ResultSet result = statement.executeQuery(QUERIES.selectAllSQL()); + Assertions.assertFalse(result.next()); + + statement.execute(SIMPLE_UPSERT); + + ExceptionAssert.ydbException("Member not found: key2. Did you mean key?", + () -> statement.executeQuery(QUERIES.wrongSelectSQL())); + + Assertions.assertNull(getTxId(jdbc.connection())); + jdbc.connection().rollback(); + Assertions.assertNull(getTxId(jdbc.connection())); + } finally { + cleanTable(); + jdbc.connection().setAutoCommit(true); + } + } + + @Test + public void closeTest() throws SQLException { + Assertions.assertFalse(jdbc.connection().isClosed()); + jdbc.connection().close(); + Assertions.assertTrue(jdbc.connection().isClosed()); + jdbc.connection().close(); // no effect + } + + @Test + public void getMetaData() throws SQLException { + DatabaseMetaData metaData = jdbc.connection().getMetaData(); + Assertions.assertNotNull(metaData); + } + + @Test + public void readOnly() throws SQLException { + Assertions.assertFalse(jdbc.connection().isReadOnly()); + Assertions.assertEquals(Connection.TRANSACTION_SERIALIZABLE, jdbc.connection().getTransactionIsolation()); + + jdbc.connection().setReadOnly(true); + + Assertions.assertTrue(jdbc.connection().isReadOnly()); + Assertions.assertEquals(Connection.TRANSACTION_SERIALIZABLE, jdbc.connection().getTransactionIsolation()); + + jdbc.connection().setReadOnly(false); + Assertions.assertFalse(jdbc.connection().isReadOnly()); + Assertions.assertEquals(Connection.TRANSACTION_SERIALIZABLE, jdbc.connection().getTransactionIsolation()); + } + + @Test + public void catalog() throws SQLException { + Assertions.assertNull(jdbc.connection().getCatalog()); + jdbc.connection().setCatalog("any"); // catalogs are not supported + Assertions.assertNull(jdbc.connection().getCatalog()); + } + + @Test + public void schema() throws SQLException { + Assertions.assertNull(jdbc.connection().getSchema()); + jdbc.connection().setSchema("test"); // schemas are not supported + Assertions.assertNull(jdbc.connection().getSchema()); + } + + @ParameterizedTest(name = "Check supported isolation level {0}") + @ValueSource(ints = { + Connection.TRANSACTION_SERIALIZABLE, // 8 + YdbConst.ONLINE_CONSISTENT_READ_ONLY, // 16 + YdbConst.ONLINE_INCONSISTENT_READ_ONLY, // 17 + YdbConst.STALE_CONSISTENT_READ_ONLY // 32 + }) + public void supportedTransactionIsolations(int level) throws SQLException { + jdbc.connection().setTransactionIsolation(level); + Assertions.assertEquals(level, jdbc.connection().getTransactionIsolation()); + + try (Statement statement = jdbc.connection().createStatement()) { + TableAssert.assertSelectInt(4, statement.executeQuery(SELECT_2_2)); + } + } + + @ParameterizedTest(name = "Check supported isolation level {0}") + @ValueSource(ints = { 0, 1, 2, 3, 4, 5, 6, 7, /*8,*/ 9, 10 }) + public void unsupportedTransactionIsolations(int level) throws SQLException { + ExceptionAssert.sqlException("Unsupported transaction level: " + level, + () -> jdbc.connection().setTransactionIsolation(level) + ); + } + + @Test + public void clearWarnings() throws SQLException { + // TODO: generate warnings + Assertions.assertNull(jdbc.connection().getWarnings()); + jdbc.connection().clearWarnings(); + Assertions.assertNull(jdbc.connection().getWarnings()); + } + + @Test + public void typeMap() throws SQLException { + Assertions.assertTrue(jdbc.connection().getTypeMap().isEmpty()); + + Map> newMap = new HashMap<>(); + newMap.put("type1", String.class); + jdbc.connection().setTypeMap(newMap); + + // not implemented + Assertions.assertTrue(jdbc.connection().getTypeMap().isEmpty()); + } + + @Test + public void holdability() throws SQLException { + Assertions.assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, jdbc.connection().getHoldability()); + jdbc.connection().setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT); + + ExceptionAssert.sqlFeatureNotSupported("resultSetHoldability must be ResultSet.HOLD_CURSORS_OVER_COMMIT", + () -> jdbc.connection().setHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT)); + } + + @Test + public void networkTimeout() throws SQLException { + Assertions.assertEquals(0, jdbc.connection().getNetworkTimeout()); + ExceptionAssert.sqlFeatureNotSupported("Set network timeout is not supported yet", + () -> jdbc.connection().setNetworkTimeout(null, 1)); + } + + @Test + public void savepoints() { + ExceptionAssert.sqlFeatureNotSupported("Savepoints are not supported", () -> jdbc.connection().setSavepoint()); + ExceptionAssert.sqlFeatureNotSupported("Savepoints are not supported", () -> jdbc.connection().setSavepoint("name")); + ExceptionAssert.sqlFeatureNotSupported("Savepoints are not supported", () -> jdbc.connection().releaseSavepoint(null)); + } + + @Test + public void unsupportedTypes() { + ExceptionAssert.sqlFeatureNotSupported("Clobs are not supported", () -> jdbc.connection().createClob()); + ExceptionAssert.sqlFeatureNotSupported("Blobs are not supported", () -> jdbc.connection().createBlob()); + ExceptionAssert.sqlFeatureNotSupported("NClobs are not supported", () -> jdbc.connection().createNClob()); + ExceptionAssert.sqlFeatureNotSupported("SQLXMLs are not supported", () -> jdbc.connection().createSQLXML()); + ExceptionAssert.sqlFeatureNotSupported("Arrays are not supported", + () -> jdbc.connection().createArrayOf("type", new Object[] { }) + ); + ExceptionAssert.sqlFeatureNotSupported("Structs are not supported", + () -> jdbc.connection().createStruct("type", new Object[] { }) + ); + } + + @Test + public void abort() { + ExceptionAssert.sqlFeatureNotSupported("Abort operation is not supported yet", () -> jdbc.connection().abort(null)); + } + + @Test + public void testDDLInsideTransaction() throws SQLException { + String createTempTable = QUERIES.withTableName( + "create table temp_#tableName(id Int32, value Int32, primary key(id))" + ); + String dropTempTable = QUERIES.withTableName("drop table temp_#tableName"); + + try (Statement statement = jdbc.connection().createStatement()) { + statement.execute(SIMPLE_UPSERT); + + // call autocommit + statement.execute(createTempTable); + + statement.executeQuery(QUERIES.selectAllSQL()); + statement.execute(QUERIES.deleteAllSQL()); + + // call autocommit + statement.execute(dropTempTable); + } + } + + @Test + @Disabled + public void testWarningInIndexUsage() throws SQLException { + try (Statement statement = jdbc.connection().createStatement()) { + statement.execute("" + + "create table unit_0_indexed (" + + "id Int32, value Int32, " + + "primary key (id), " + + "index idx_value global on (value))"); + + String query = "--!syntax_v1\n" + + "declare $list as List;\n" + + "select * from unit_0_indexed view idx_value where value in $list;"; + + ListValue value = ListType.of(PrimitiveType.Int32).newValue( + Arrays.asList(PrimitiveValue.newInt32(1), PrimitiveValue.newInt32(2))); + try (PreparedStatement ps = jdbc.connection().prepareStatement(query)) { + ps.setObject(1, value); + + ResultSet rs = ps.executeQuery(); + Assertions.assertFalse(rs.next()); + + SQLWarning warnings = ps.getWarnings(); + Assertions.assertNotNull(warnings); + + Assertions.assertEquals("#1030 Type annotation (S_WARNING)\n" + + " 1:3 - 1:3: At function: RemovePrefixMembers, At function: RemoveSystemMembers, " + + "At function: PersistableRepr, At function: SqlProject (S_WARNING)\n" + + " 35:3 - 35:3: At function: Filter, At function: Coalesce (S_WARNING)\n" + + " 51:3 - 51:3: At function: SqlIn (S_WARNING)\n" + + " 51:3 - 51:3: #1108 IN may produce unexpected result when used with nullable arguments. " + + "Consider adding 'PRAGMA AnsiInForEmptyOrNullableItemsCollections;' (S_WARNING)", + warnings.getMessage()); + Assertions.assertNull(warnings.getNextWarning()); + } + } + } + + @Test + public void testAnsiLexer() throws SQLException { + try (Statement statement = jdbc.connection().createStatement()) { + ResultSet rs = statement.executeQuery("--!ansi_lexer\n" + + "select 'string value' as \"name with space\""); + Assertions.assertTrue(rs.next()); + Assertions.assertEquals("string value", rs.getString("name with space")); + } + } + + @Test + public void testAnsiLexerForIdea() throws SQLException { + try (Statement statement = jdbc.connection().createStatement()) { + statement.execute(SIMPLE_UPSERT); + jdbc.connection().commit(); + + // TODO: Must be "unit_1" t, see YQL-12618 + try (ResultSet rs = statement.executeQuery("--!ansi_lexer\n" + + QUERIES.withTableName("select t.c_Text from #tableName as t where t.key = 1"))) { + Assertions.assertTrue(rs.next()); + Assertions.assertEquals("2", rs.getString("c_Text")); + } + + try (ResultSet rs = statement.executeQuery("--!ansi_lexer\n" + + QUERIES.withTableName("select t.c_Text from #tableName t where t.key = 1"))) { + Assertions.assertTrue(rs.next()); + Assertions.assertEquals("2", rs.getString("c_Text")); + } + } finally { + cleanTable(); + } + } + + @DisplayName("Check unsupported by storage type {arguments}") + @ParameterizedTest() + @ValueSource(strings = { + "TzDate", + "TzDatetime", + "TzTimestamp", + }) + public void testUnsupportedByStorageTableTypes(String type) throws SQLException { + String tableName = "unsupported_" + type; + String sql = "create table " + tableName + " (key Int32, payload " + type + ", primary key(key))"; + + try (Statement statement = jdbc.connection().createStatement()) { + ExceptionAssert.ydbException("is not supported by storage", () -> statement.execute(sql)); + } + } + + @ParameterizedTest(name = "Check unsupported complex type {0}") + @ValueSource(strings = { + "List", + "Struct", + "Tuple", + "Dict", + }) + public void testUnsupportedComplexTypes(String type) throws SQLException { + String tableName = "unsupported_" + type.replaceAll("[^a-zA-Z0-9]", ""); + String sql = "create table " + tableName + " (key Int32, payload " + type + ", primary key(key))"; + + try (Statement statement = jdbc.connection().createStatement()) { + ExceptionAssert.ydbException("Invalid type for column: payload.", + () -> statement.execute(sql)); + } + } +} diff --git a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbQueryPreparedStatementImplTest.java b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbQueryPreparedStatementImplTest.java new file mode 100644 index 0000000..b3d9f5b --- /dev/null +++ b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbQueryPreparedStatementImplTest.java @@ -0,0 +1,2347 @@ +package tech.ydb.jdbc.impl; + +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import tech.ydb.jdbc.YdbConnection; +import tech.ydb.jdbc.YdbConst; +import tech.ydb.jdbc.YdbParameterMetaData; +import tech.ydb.jdbc.YdbPrepareMode; +import tech.ydb.jdbc.YdbPreparedStatement; +import tech.ydb.jdbc.YdbTypes; +import tech.ydb.jdbc.impl.helper.ExceptionAssert; +import tech.ydb.jdbc.impl.helper.JdbcConnectionExtention; +import tech.ydb.jdbc.impl.helper.SqlQueries; +import tech.ydb.jdbc.impl.helper.TextSelectAssert; +import tech.ydb.table.values.DecimalValue; +import tech.ydb.table.values.PrimitiveType; +import tech.ydb.test.junit5.YdbHelperExtension; + + +public class YdbQueryPreparedStatementImplTest { + private static final Logger LOGGER = Logger.getLogger(YdbQueryPreparedStatementImplTest.class.getName()); + + @RegisterExtension + private static final YdbHelperExtension ydb = new YdbHelperExtension(); + + @RegisterExtension + private static final JdbcConnectionExtention jdbc = new JdbcConnectionExtention(ydb) + .withArg("useQueryService", "true"); + + private static final String TEST_TABLE_NAME = "ydb_prepared_statement_test"; + private static final SqlQueries TEST_TABLE = new SqlQueries(TEST_TABLE_NAME); + + private static final String UPSERT_SQL = "" + + "declare $key as Int32;\n" + + "declare $#column as #type;\n" + + "upsert into #tableName (key, #column) values ($key, $#column)"; + + private static final String SIMPLE_SELECT_SQL = "select key, #column from #tableName"; + private static final String SELECT_BY_KEY_SQL = "" + + "declare $key as Optional;\n" + + "select key, #column from #tableName where key=$key"; + + @BeforeAll + public static void initTable() throws SQLException { + try (Statement statement = jdbc.connection().createStatement();) { + // create test table + statement.execute(TEST_TABLE.createTableSQL()); + } + } + + @AfterAll + public static void dropTable() throws SQLException { + try (Statement statement = jdbc.connection().createStatement();) { + statement.execute(TEST_TABLE.dropTableSQL()); + } + } + + @AfterEach + public void afterEach() throws SQLException { + if (jdbc.connection().isClosed()) { + return; + } + + try (Statement statement = jdbc.connection().createStatement()) { + statement.execute(TEST_TABLE.deleteAllSQL()); + } + + jdbc.connection().close(); + } + + private String upsertSql(String column, String type) { + return UPSERT_SQL + .replaceAll("#column", column) + .replaceAll("#type", type) + .replaceAll("#tableName", TEST_TABLE_NAME); + } + + private YdbPreparedStatement prepareUpsert(YdbPrepareMode mode,String column, String type) + throws SQLException { + return jdbc.connection().unwrap(YdbConnection.class).prepareStatement(upsertSql(column, type), mode); + } + + private PreparedStatement prepareSimpleSelect(String column) throws SQLException { + String sql = SIMPLE_SELECT_SQL + .replaceAll("#column", column) + .replaceAll("#tableName", TEST_TABLE_NAME); + return jdbc.connection().prepareStatement(sql); + } + + private YdbPreparedStatement prepareSelectByKey(String column) throws SQLException { + String sql = SELECT_BY_KEY_SQL + .replaceAll("#column", column) + .replaceAll("#tableName", TEST_TABLE_NAME); + return jdbc.connection().prepareStatement(sql).unwrap(YdbPreparedStatement.class); + } + + private YdbPreparedStatement prepareSelectAll() throws SQLException { + return jdbc.connection().prepareStatement(TEST_TABLE.selectSQL()) + .unwrap(YdbPreparedStatement.class); + } + + @ParameterizedTest(name = "with {0}") + @EnumSource(SqlQueries.YqlQuery.class) + public void unknownColumns(SqlQueries.YqlQuery mode) throws SQLException { + String yql = TEST_TABLE.upsertOne(mode, "c_Text", "Optional"); + try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(yql)) { + statement.setInt("key", 1); + + ExceptionAssert.sqlException("Parameter not found: column0", () -> statement.setObject("column0", "value")); + } + } + + @ParameterizedTest(name = "with {0}") + @EnumSource(SqlQueries.YqlQuery.class) + public void executeWithoutBatch(SqlQueries.YqlQuery mode) throws SQLException { + String yql = TEST_TABLE.upsertOne(mode, "c_Text", "Text"); + try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(yql)) { + statement.setInt("key", 1); + statement.setString("c_Text", "value-1"); + statement.addBatch(); + + statement.setInt("key", 2); + statement.setString("c_Text", "value-2"); + statement.execute(); + + // clear will be called automatically + statement.setInt("key", 3); + statement.setString("c_Text", "value-3"); + statement.execute(); + } + + try (PreparedStatement select = prepareSimpleSelect("c_Text")) { + TextSelectAssert.of(select.executeQuery(), "c_Text", "Text") + .nextRow(2, "value-2") + .nextRow(3, "value-3") + .noNextRows(); + } + }; + + @ParameterizedTest(name = "with {0}") + @EnumSource(SqlQueries.YqlQuery.class) + public void addBatchClearParameters(SqlQueries.YqlQuery mode) throws SQLException { + String yql = TEST_TABLE.upsertOne(mode, "c_Text", "Text"); + try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(yql)) { + statement.setInt("key", 1); + statement.setString("c_Text", "value-1"); + statement.addBatch(); + + statement.setInt("key", 10); + statement.setString("c_Text", "value-11"); + statement.clearParameters(); + + statement.setInt("key", 2); + statement.setString("c_Text", "value-2"); + statement.addBatch(); + + statement.executeBatch(); + } + + try (PreparedStatement select = prepareSimpleSelect("c_Text")) { + TextSelectAssert.of(select.executeQuery(), "c_Text", "Text") + .nextRow(1, "value-1") + .nextRow(2, "value-2") + .noNextRows(); + } + } + + @ParameterizedTest(name = "with {0}") + @EnumSource(SqlQueries.YqlQuery.class) + public void addBatch(SqlQueries.YqlQuery mode) throws SQLException { + String yql = TEST_TABLE.upsertOne(mode, "c_Text", "Text"); + try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(yql)) { + statement.setInt("key", 1); + statement.setString("c_Text", "value-1"); + statement.addBatch(); + + statement.setInt("key", 2); + statement.setString("c_Text", "value-2"); + statement.addBatch(); + + // No add batch, must be skipped + statement.setInt("key", 3); + statement.setString("c_Text", "value-3"); + + Assertions.assertArrayEquals(new int[]{ Statement.SUCCESS_NO_INFO, Statement.SUCCESS_NO_INFO }, + statement.executeBatch()); + + // does nothing + Assertions.assertArrayEquals(new int[0], statement.executeBatch()); + } + + try (PreparedStatement select = prepareSimpleSelect("c_Text")) { + TextSelectAssert.of(select.executeQuery(), "c_Text", "Text") + .nextRow(1, "value-1") + .nextRow(2, "value-2") + .noNextRows(); + } + } + + @ParameterizedTest(name = "with {0}") + @EnumSource(SqlQueries.YqlQuery.class) + public void addAndClearBatch(SqlQueries.YqlQuery mode) throws SQLException { + String yql = TEST_TABLE.upsertOne(mode, "c_Text", "Text"); + try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(yql)) { + statement.setInt("key", 1); + statement.setString("c_Text", "value-1"); + statement.addBatch(); + statement.executeBatch(); + + statement.setInt("key", 11); + statement.setString("c_Text", "value-11"); + statement.addBatch(); + statement.clearBatch(); + + statement.setInt("key", 2); + statement.setString("c_Text", "value-2"); + statement.addBatch(); + statement.executeBatch(); + } + + try (PreparedStatement select = prepareSimpleSelect("c_Text")) { + TextSelectAssert.of(select.executeQuery(), "c_Text", "Text") + .nextRow(1, "value-1") + .nextRow(2, "value-2") + .noNextRows(); + } + } + + @ParameterizedTest(name = "with {0}") + @EnumSource(SqlQueries.YqlQuery.class) + public void executeEmptyBatch(SqlQueries.YqlQuery mode) throws SQLException { + String yql = TEST_TABLE.upsertOne(mode, "c_Text", "Text"); + try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(yql)) { + ExceptionAssert.sqlDataException("Missing value for parameter", () -> statement.execute()); + ExceptionAssert.sqlDataException("Missing value for parameter", () -> statement.executeUpdate()); + statement.executeBatch(); + } + + try (PreparedStatement select = prepareSimpleSelect("c_Text")) { + TextSelectAssert.of(select.executeQuery(), "c_Text", "Text") + .noNextRows(); + } + } + + @Test + public void executeQueryBatchWithBigRead() throws SQLException { + int valuesCount = 5000; + String[] values = new String[valuesCount]; + for (int idx = 1; idx <= valuesCount; idx += 1) { + values[idx - 1] = "Row#" + idx; + } + + String yql = TEST_TABLE.upsertOne(SqlQueries.YqlQuery.BATCHED, "c_Text", "Text"); + try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(yql)) { + for (int idx = 1; idx <= valuesCount; idx += 1) { + statement.setInt("key", idx); + statement.setString("c_Text", values[idx - 1]); + statement.addBatch(); + } + + int[] results = statement.executeBatch(); + Assertions.assertEquals(values.length, results.length); + + for (int idx = 0; idx < results.length; idx += 1) { + Assertions.assertEquals(Statement.SUCCESS_NO_INFO, results[idx], "Wrong batch " + idx); + } + } + + try (PreparedStatement select = prepareSimpleSelect("c_Text")) { + TextSelectAssert check = TextSelectAssert.of(select.executeQuery(), "c_Text", "Text"); + + for (int idx = 1; idx <= valuesCount; idx += 1) { + check.nextRow(idx, values[idx - 1]); + } + + check.noNextRows(); + } + } + + @ParameterizedTest(name = "with {0}") + @EnumSource(SqlQueries.YqlQuery.class) + public void executeDataQuery(SqlQueries.YqlQuery mode) throws SQLException { + String yql = TEST_TABLE.upsertOne(mode, "c_Text", "Text"); + try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(yql)) { + statement.setInt("key", 1); + statement.setString("c_Text", "value-1"); + statement.execute(); + } + + try (YdbPreparedStatement statement = prepareSelectByKey("c_Text")) { + statement.setInt("key", 2); + + TextSelectAssert.of(statement.executeQuery(), "c_Text", "Text") + .noNextRows(); + + statement.setInt("key", 1); + + TextSelectAssert.of(statement.executeQuery(), "c_Text", "Text") + .nextRow(1, "value-1") + .noNextRows(); + } + + try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(yql)) { + statement.setInt("key", 1); + statement.setString("c_Text", "value-11"); + statement.execute(); + + statement.setInt("key", 2); + statement.setString("c_Text", "value-2"); + statement.execute(); + } + + try (PreparedStatement select = prepareSimpleSelect("c_Text")) { + TextSelectAssert.of(select.executeQuery(), "c_Text", "Text") + .nextRow(1, "value-11") + .nextRow(2, "value-2") + .noNextRows(); + } + } + + @ParameterizedTest(name = "with {0}") + @EnumSource(SqlQueries.YqlQuery.class) + public void executeQueryInTx(SqlQueries.YqlQuery mode) throws SQLException { + String yql = TEST_TABLE.upsertOne(mode, "c_Text", "Text"); + jdbc.connection().setAutoCommit(false); + YdbConnection conn = jdbc.connection().unwrap(YdbConnection.class); + try { + try (YdbPreparedStatement statement = conn.prepareStatement(yql)) { + statement.setInt("key", 1); + statement.setString("c_Text", "value-1"); + statement.execute(); + } + + // without jdbc.connection().commit() driver continues to use single transaction; + + try (PreparedStatement select = prepareSimpleSelect("c_Text")) { + select.executeQuery(); + } + } finally { + jdbc.connection().setAutoCommit(true); + } + } + + @Test + public void executeScanQueryAsUpdate() throws SQLException { + String sql = upsertSql("c_Text", "Optional"); + + try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class) + .prepareStatement(sql, YdbPrepareMode.IN_MEMORY) + .unwrap(YdbPreparedStatement.class)) { + statement.setInt("key", 1); + statement.setString("c_Text", "value-1"); + + ExceptionAssert.ydbException("Scan query should have a single result set", + statement::executeScanQuery); + } + } + + @Test + public void executeUnsupportedModes() throws SQLException { + ExceptionAssert.sqlException("Query type in prepared statement not supported: SCHEME_QUERY", () -> { + String sql = "DROP TABLE test_table;"; + jdbc.connection().prepareStatement(sql); + }); + + ExceptionAssert.sqlException("Query type in prepared statement not supported: EXPLAIN_QUERY", () -> { + String sql = "EXPLAIN " + prepareSelectAll(); + jdbc.connection().prepareStatement(sql); + }); + } + + @ParameterizedTest(name = "with {0}") + @EnumSource(value = SqlQueries.YqlQuery.class) + public void executeExplainQueryExplicitly(SqlQueries.YqlQuery mode) throws SQLException { + String yql = TEST_TABLE.upsertOne(mode, "c_Text", "Optional"); + try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(yql)) { + statement.setInt("key", 1); + statement.setString("c_Text", "value-1"); + + ResultSet rs = statement.executeExplainQuery(); + + Assertions.assertTrue(rs.next()); + String ast = rs.getString(YdbConst.EXPLAIN_COLUMN_AST); + String plan = rs.getString(YdbConst.EXPLAIN_COLUMN_AST); + + Assertions.assertNotNull(ast); + Assertions.assertNotNull(plan); + + LOGGER.log(Level.INFO, "AST: {0}", ast); + LOGGER.log(Level.INFO, "PLAN: {0}", plan); + + Assertions.assertFalse(rs.next()); + } + + try (YdbPreparedStatement statement = prepareSelectByKey("c_Text")) { + ResultSet rs = statement.executeExplainQuery(); + Assertions.assertTrue(rs.next()); + + String ast = rs.getString(YdbConst.EXPLAIN_COLUMN_AST); + String plan = rs.getString(YdbConst.EXPLAIN_COLUMN_AST); + + Assertions.assertNotNull(ast); + Assertions.assertNotNull(plan); + + LOGGER.log(Level.INFO, "AST: {0}", ast); + LOGGER.log(Level.INFO, "PLAN: {0}", plan); + + Assertions.assertFalse(rs.next()); + } + } + + @ParameterizedTest(name = "with {0}") + @EnumSource(value = SqlQueries.YqlQuery.class) + public void testSetNull(SqlQueries.YqlQuery mode) throws SQLException { + String yql = TEST_TABLE.namedUpsertAll(mode); + try (YdbPreparedStatement ps = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(yql)) { + ps.setInt("key", 1); + YdbTypes types = ps.getConnection().getYdbTypes(); + ps.setNull("c_Bool", types.wrapYdbJdbcType(PrimitiveType.Bool)); + ps.setNull("c_Int8", types.wrapYdbJdbcType(PrimitiveType.Int8)); + ps.setNull("c_Int16", types.wrapYdbJdbcType(PrimitiveType.Int16)); + ps.setNull("c_Int32", types.wrapYdbJdbcType(PrimitiveType.Int32)); + ps.setNull("c_Int64", types.wrapYdbJdbcType(PrimitiveType.Int64)); + ps.setNull("c_Uint8", types.wrapYdbJdbcType(PrimitiveType.Uint8)); + ps.setNull("c_Uint16", types.wrapYdbJdbcType(PrimitiveType.Uint16)); + ps.setNull("c_Uint32", types.wrapYdbJdbcType(PrimitiveType.Uint32)); + ps.setNull("c_Uint64", types.wrapYdbJdbcType(PrimitiveType.Uint64)); + ps.setNull("c_Float", types.wrapYdbJdbcType(PrimitiveType.Float)); + ps.setNull("c_Double", types.wrapYdbJdbcType(PrimitiveType.Double)); + ps.setNull("c_Bytes", types.wrapYdbJdbcType(PrimitiveType.Bytes)); + ps.setNull("c_Text", types.wrapYdbJdbcType(PrimitiveType.Text)); + ps.setNull("c_Json", types.wrapYdbJdbcType(PrimitiveType.Json)); + ps.setNull("c_JsonDocument", types.wrapYdbJdbcType(PrimitiveType.JsonDocument)); + ps.setNull("c_Yson", types.wrapYdbJdbcType(PrimitiveType.Yson)); + ps.setNull("c_Date", types.wrapYdbJdbcType(PrimitiveType.Date)); + ps.setNull("c_Datetime", types.wrapYdbJdbcType(PrimitiveType.Datetime)); + ps.setNull("c_Timestamp", types.wrapYdbJdbcType(PrimitiveType.Timestamp)); + ps.setNull("c_Interval", types.wrapYdbJdbcType(PrimitiveType.Interval)); + ps.setNull("c_Decimal", types.wrapYdbJdbcType(YdbTypes.DEFAULT_DECIMAL_TYPE)); + + ps.executeUpdate(); + } + + try (YdbPreparedStatement ps = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(yql)) { + ps.setInt("key", 2); + ps.setNull("c_Bool", -1, "Bool"); + ps.setNull("c_Int8", -1, "Int8"); + ps.setNull("c_Int16", -1, "Int16"); + ps.setNull("c_Int32", -1, "Int32"); + ps.setNull("c_Int64", -1, "Int64"); + ps.setNull("c_Uint8", -1, "Uint8"); + ps.setNull("c_Uint16", -1, "Uint16"); + ps.setNull("c_Uint32", -1, "Uint32"); + ps.setNull("c_Uint64", -1, "Uint64"); + ps.setNull("c_Float", -1, "Float"); + ps.setNull("c_Double", -1, "Double"); + ps.setNull("c_Bytes", -1, "String"); + ps.setNull("c_Text", -1, "Text"); + ps.setNull("c_Json", -1, "Json"); + ps.setNull("c_JsonDocument", -1, "JsonDocument"); + ps.setNull("c_Yson", -1, "Yson"); + ps.setNull("c_Date", -1, "Date"); + ps.setNull("c_Datetime", -1, "Datetime"); + ps.setNull("c_Timestamp", -1, "Timestamp"); + ps.setNull("c_Interval", -1, "Interval"); + ps.setNull("c_Decimal", -1, "Decimal(22, 9)"); + + ps.executeUpdate(); + } + + try (YdbPreparedStatement ps = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(yql)) { + ps.setInt("key", 3); + ps.setNull("c_Bool", -1); + ps.setNull("c_Int8", -1); + ps.setNull("c_Int16", -1); + ps.setNull("c_Int32", -1); + ps.setNull("c_Int64", -1); + ps.setNull("c_Uint8", -1); + ps.setNull("c_Uint16", -1); + ps.setNull("c_Uint32", -1); + ps.setNull("c_Uint64", -1); + ps.setNull("c_Float", -1); + ps.setNull("c_Double", -1); + ps.setNull("c_Bytes", -1); + ps.setNull("c_Text", -1); + ps.setNull("c_Json", -1); + ps.setNull("c_JsonDocument", -1); + ps.setNull("c_Yson", -1); + ps.setNull("c_Date", -1); + ps.setNull("c_Datetime", -1); + ps.setNull("c_Timestamp", -1); + ps.setNull("c_Interval", -1); + ps.setNull("c_Decimal", -1); + + ps.executeUpdate(); + } + + try (YdbPreparedStatement ps = prepareSelectAll()) { + ResultSet rs = ps.executeQuery(); + + for (int key = 1; key <= 3; key += 1) { + Assertions.assertTrue(rs.next()); + + ResultSetMetaData metaData = rs.getMetaData(); + Assertions.assertEquals(22, metaData.getColumnCount()); + Assertions.assertEquals(key, rs.getInt("key")); // key + + for (int i = 2; i <= metaData.getColumnCount(); i++) { + Assertions.assertNull(rs.getObject(i)); // everything else + } + } + + Assertions.assertFalse(rs.next()); + } + } + + @ParameterizedTest(name = "with {0}") + @EnumSource(value = SqlQueries.YqlQuery.class) + public void testParametersMeta(SqlQueries.YqlQuery mode) throws SQLException { + String yql = TEST_TABLE.namedUpsertAll(mode); + try (YdbPreparedStatement ps = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(yql)) { + final ParameterMetaData meta = ps.getParameterMetaData(); + final YdbParameterMetaData ydbMeta = meta.unwrap(YdbParameterMetaData.class); + + ExceptionAssert.sqlException("Parameter is out of range: 335", + () -> meta.getParameterType(335) + ); + + Assertions.assertEquals(22, meta.getParameterCount()); + for (int param = 1; param <= meta.getParameterCount(); param++) { + String name = ydbMeta.getParameterName(param); + boolean isKey = "key".equals(name); + + Assertions.assertFalse(meta.isSigned(param), "All params are not isSigned"); + Assertions.assertEquals(0, meta.getPrecision(param), "No precision available"); + Assertions.assertEquals(0, meta.getScale(param), "No scale available"); + Assertions.assertEquals(ParameterMetaData.parameterModeIn, meta.getParameterMode(param), + "All params are in"); + + int type = meta.getParameterType(param); + Assertions.assertTrue(type != 0, "All params have sql type, including " + name); + + if (isKey) { + Assertions.assertEquals(ParameterMetaData.parameterNoNulls, meta.isNullable(param), + "Primary key defined as not nullable"); + } else { + Assertions.assertEquals(ParameterMetaData.parameterNullable, meta.isNullable(param), + "All parameters expect primary key defined as nullable, including " + name); + } + + String expectType = isKey ? "int32" : name.substring("c_".length()).toLowerCase(); + if (expectType.equals("decimal")) { + expectType += "(22, 9)"; + } + + String actualType = meta.getParameterTypeName(param); + Assertions.assertNotNull(actualType, "All parameters have database types"); + Assertions.assertEquals(expectType, actualType.toLowerCase(), + "All parameter names are similar to types"); + + String expectClassName; + switch (name) { + case "c_Bool": + expectClassName = Boolean.class.getName(); + break; + case "c_Int8": + expectClassName = Byte.class.getName(); + break; + case "c_Int16": + expectClassName = Short.class.getName(); + break; + case "key": + case "c_Int32": + case "c_Uint8": + case "c_Uint16": + expectClassName = Integer.class.getName(); + break; + case "c_Int64": + case "c_Uint64": + case "c_Uint32": + expectClassName = Long.class.getName(); + break; + case "c_Float": + expectClassName = Float.class.getName(); + break; + case "c_Double": + expectClassName = Double.class.getName(); + break; + case "c_Text": + case "c_Json": + case "c_JsonDocument": + expectClassName = String.class.getName(); + break; + case "c_Bytes": + case "c_Yson": + expectClassName = byte[].class.getName(); + break; + case "c_Date": + expectClassName = LocalDate.class.getName(); + break; + case "c_Datetime": + expectClassName = LocalDateTime.class.getName(); + break; + case "c_Timestamp": + expectClassName = Instant.class.getName(); + break; + case "c_Interval": + expectClassName = Duration.class.getName(); + break; + case "c_Decimal": + expectClassName = DecimalValue.class.getName(); + break; + default: + throw new IllegalStateException("Unknown param: " + name); + } + Assertions.assertEquals(expectClassName, meta.getParameterClassName(param), + "Check class name for parameter: " + name); + } + } + } + +/* + @Test + void setBoolean() throws SQLException { + checkInsert("c_Bool", "Bool", + YdbPreparedStatement::setBoolean, + YdbPreparedStatement::setBoolean, + ResultSet::getBoolean, + Arrays.asList( + pair(true, true), + pair(false, false) + ), + Arrays.asList( + pair(true, true), + pair(false, false), + pair(1, true), + pair(0, false), + pair(-1, false), + pair(2, true), + pair(0.1, false), // round to 0 + pair(1.1, true), + pair(-0.1, false), + pair((byte) 1, true), + pair((byte) 0, false), + pair((short) 1, true), + pair((short) 0, false), + pair(1, true), + pair(0, false), + pair(1L, true), + pair(0L, false), + pair(PrimitiveValue.newBool(true), true), + pair(PrimitiveValue.newBool(false), false) + ), + Arrays.asList( + "", + "".getBytes(), + PrimitiveValue.newInt32(1), + PrimitiveValue.newInt32(1).makeOptional() + ) + ); + } + + + @ParameterizedTest + @ValueSource(strings = {"Uint8"}) + void setByte(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setByte, + YdbPreparedStatement::setByte, + ResultSet::getByte, + Arrays.asList( + pair((byte) 1, (byte) 1), + pair((byte) 0, (byte) 0), + pair((byte) -1, (byte) -1), + pair((byte) 127, (byte) 127), + pair((byte) 4, (byte) 4) + ), + Arrays.asList( + pair(true, (byte) 1), + pair(false, (byte) 0), + pair(PrimitiveValue.newUint8((byte) 1), (byte) 1), + pair(PrimitiveValue.newUint8((byte) 0), (byte) 0) + ), + Arrays.asList( + "", + "".getBytes(), + (short) 5, + 6, + 7L, + 8f, + 9d, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional() + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Int32", "Uint32"}) + void setByteToInt(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setByte, + YdbPreparedStatement::setByte, + ResultSet::getInt, + Arrays.asList( + pair((byte) 1, 1), + pair((byte) 0, 0), + pair((byte) -1, -1), + pair((byte) 127, 127), + pair((byte) 4, 4) + ), + Arrays.asList( + pair((short) 5, 5), + pair(6, 6), + pair(true, 1), + pair(false, 0) + ), + Arrays.asList( + "", + "".getBytes(), + 7L, + 8f, + 9d, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newUint8((byte) 1) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Int64", "Uint64"}) + void setByteToLong(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setByte, + YdbPreparedStatement::setByte, + ResultSet::getLong, + Arrays.asList( + pair((byte) 1, 1L), + pair((byte) 0, 0L), + pair((byte) -1, -1L), + pair((byte) 127, 127L), + pair((byte) 4, 4L) + ), + Arrays.asList( + pair((short) 5, 5L), + pair(6, 6L), + pair(7L, 7L), + pair(true, 1L), + pair(false, 0L), + pair(new BigInteger("10"), 10L) + ), + Arrays.asList( + "", + "".getBytes(), + 8f, + 9d, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newUint8((byte) 1), + new BigDecimal("10") + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Int32", "Uint32"}) + void setShortToInt(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setShort, + YdbPreparedStatement::setShort, + ResultSet::getInt, + Arrays.asList( + pair((short) 1, 1), + pair((short) 0, 0), + pair((short) -1, -1), + pair((short) 127, 127), + pair((short) 5, 5) + ), + Arrays.asList( + pair((byte) 4, 4), + pair(6, 6), + pair(true, 1), + pair(false, 0) + ), + Arrays.asList( + "", + "".getBytes(), + 7L, + 8f, + 9d, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newInt16((short) 1) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Int64", "Uint64"}) + void setShortToLong(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setShort, + YdbPreparedStatement::setShort, + ResultSet::getLong, + Arrays.asList( + pair((short) 1, 1L), + pair((short) 0, 0L), + pair((short) -1, -1L), + pair((short) 127, 127L), + pair((short) 5, 5L) + ), + Arrays.asList( + pair((byte) 4, 4L), + pair(6, 6L), + pair(7L, 7L), + pair(true, 1L), + pair(false, 0L) + ), + Arrays.asList( + "", + "".getBytes(), + 8f, + 9d, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newInt16((short) 1) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Int32", "Uint32"}) + void setInt(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setInt, + YdbPreparedStatement::setInt, + ResultSet::getInt, + Arrays.asList( + pair(1, 1), + pair(0, 0), + pair(-1, -1), + pair(127, 127), + pair(6, 6) + ), + Arrays.asList( + pair((byte) 4, 4), + pair((short) 5, 5), + pair(true, 1), + pair(false, 0) + ), + Arrays.asList( + "", + "".getBytes(), + 7L, + 8f, + 9d, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newInt16((short) 1) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Int64", "Uint64"}) + void setIntToLong(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setInt, + YdbPreparedStatement::setInt, + ResultSet::getLong, + Arrays.asList( + pair(1, 1L), + pair(0, 0L), + pair(-1, -1L), + pair(127, 127L), + pair(6, 6L) + ), + Arrays.asList( + pair((byte) 4, 4L), + pair((short) 5, 5L), + pair(7L, 7L), + pair(true, 1L), + pair(false, 0L) + ), + Arrays.asList( + "", + "".getBytes(), + 8f, + 9d, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newInt32(1) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Int64", "Uint64"}) + void setLong(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setLong, + YdbPreparedStatement::setLong, + ResultSet::getLong, + Arrays.asList( + pair(1L, 1L), + pair(0L, 0L), + pair(-1L, -1L), + pair(127L, 127L), + pair(7L, 7L) + ), + Arrays.asList( + pair((byte) 4, 4L), + pair((short) 5, 5L), + pair(6, 6L), + pair(true, 1L), + pair(false, 0L), + pair(new BigInteger("1234567890123"), 1234567890123L) + ), + Arrays.asList( + "", + "".getBytes(), + 8f, + 9d, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newInt32(1), + new BigDecimal("123") + ) + ); + } + + @Test + void setFloat() throws SQLException { + checkInsert("c_Float", "Float", + YdbPreparedStatement::setFloat, + YdbPreparedStatement::setFloat, + ResultSet::getFloat, + Arrays.asList( + pair(1f, 1f), + pair(0f, 0f), + pair(-1f, -1f), + pair(127f, 127f), + pair(8f, 8f) + ), + Arrays.asList( + pair((byte) 4, 4f), + pair((short) 5, 5f), + pair(6, 6f), + pair(true, 1f), + pair(false, 0f), + pair(PrimitiveValue.newFloat(1.1f), 1.1f) + ), + Arrays.asList( + "", + "".getBytes(), + 7L, + 9d, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1) + ) + ); + } + + @Test + void setFloatToDouble() throws SQLException { + checkInsert("c_Double", "Double", + YdbPreparedStatement::setFloat, + YdbPreparedStatement::setFloat, + ResultSet::getDouble, + Arrays.asList( + pair(1f, 1d), + pair(0f, 0d), + pair(-1f, -1d), + pair(127f, 127d), + pair(8f, 8d) + ), + Arrays.asList( + pair((byte) 4, 4d), + pair((short) 5, 5d), + pair(6, 6d), + pair(7L, 7d), + pair(9d, 9d), + pair(true, 1d), + pair(false, 0d), + pair(PrimitiveValue.newDouble(1.1f), (double) 1.1f) // lost double precision + ), + Arrays.asList( + "", + "".getBytes(), + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newFloat(1) + ) + ); + } + + @Test + void setDouble() throws SQLException { + checkInsert("c_Double", "Double", + YdbPreparedStatement::setDouble, + YdbPreparedStatement::setDouble, + ResultSet::getDouble, + Arrays.asList( + pair(1d, 1d), + pair(0d, 0d), + pair(-1d, -1d), + pair(127d, 127d), + pair(9d, 9d) + ), + Arrays.asList( + pair((byte) 4, 4d), + pair((short) 5, 5d), + pair(6, 6d), + pair(7L, 7d), + pair(8f, 8d), + pair(true, 1d), + pair(false, 0d), + pair(PrimitiveValue.newDouble(1.1d), 1.1d) + ), + Arrays.asList( + "", + "".getBytes(), + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newFloat(1) + ) + ); + } + + @Test + void setBigDecimalDirect() throws SQLException { + retry(connection -> { + YdbPreparedStatement insert = getTestStatement(connection, "c_Decimal", "Decimal(22,9)?"); + insert.setInt("key", 1); + insert.setObject("c_Decimal", new BigDecimal(0)); // Make sure this type is converted to Decimal(22,9) type + insert.executeUpdate(); + }); + } + + @Test + void setBigDecimal() throws SQLException { + checkInsert("c_Decimal", "Decimal(22,9)", + YdbPreparedStatement::setBigDecimal, + YdbPreparedStatement::setBigDecimal, + ResultSet::getBigDecimal, + Arrays.asList( + pair(new BigDecimal("0.0"), new BigDecimal("0.000000000")), + pair(new BigDecimal("1.3"), new BigDecimal("1.300000000")) + ), + Arrays.asList( + pair(1, new BigDecimal("1.000000000")), + pair(0, new BigDecimal("0.000000000")), + pair(-1, new BigDecimal("-1.000000000")), + pair(127, new BigDecimal("127.000000000")), + pair((byte) 4, new BigDecimal("4.000000000")), + pair((short) 5, new BigDecimal("5.000000000")), + pair(6, new BigDecimal("6.000000000")), + pair(7L, new BigDecimal("7.000000000")), + pair("1", new BigDecimal("1.000000000")), + pair("1.1", new BigDecimal("1.100000000")), + pair(DecimalType.of(22, 9).newValue("1.2"), new BigDecimal("1.200000000")), + pair(new BigInteger("2"), new BigDecimal("2.000000000")) + ), + Arrays.asList( + true, + false, + 8f, + 9d, + "".getBytes(), + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1) + ) + ); + } + + @ParameterizedTest + @MethodSource("bytesAndText") + void setString(String type, List> callSetObject, List unsupported) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setString, + YdbPreparedStatement::setString, + ResultSet::getString, + Arrays.asList( + pair("", ""), + pair("test1", "test1") + ), + merge(callSetObject, + Arrays.asList( + pair(1d, "1.0"), + pair(0d, "0.0"), + pair(-1d, "-1.0"), + pair(127d, "127.0"), + pair((byte) 4, "4"), + pair((short) 5, "5"), + pair(6, "6"), + pair(7L, "7"), + pair(8f, "8.0"), + pair(9d, "9.0"), + pair(true, "true"), + pair(false, "false"), + pair("".getBytes(), ""), + pair("test2".getBytes(), "test2"), + pair(stream("test3"), "test3"), + pair(reader("test4"), "test4") + )), + merge(unsupported, + Arrays.asList( + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d), + PrimitiveValue.newJson("test") + )) + ); + } + + @ParameterizedTest + @MethodSource("jsonAndJsonDocumentAndYson") + void setStringJson(String type, List> callSetObject, List unsupported) + throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setString, + YdbPreparedStatement::setString, + ResultSet::getString, + Arrays.asList( + pair("[1]", "[1]") + ), + merge(callSetObject, + Arrays.asList( + pair("[2]".getBytes(), "[2]"), + pair(stream("[3]"), "[3]"), + pair(reader("[4]"), "[4]") + // No empty values supported + )), + merge(unsupported, + Arrays.asList( + 6, + 7L, + 8f, + 9d, + true, + false, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d), + PrimitiveValue.newText("test") + )) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text"}) + void setBytes(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setBytes, + YdbPreparedStatement::setBytes, + ResultSet::getBytes, + Arrays.asList( + pair("".getBytes(), "".getBytes()), + pair("test2".getBytes(), "test2".getBytes()) + ), + Arrays.asList( + pair(1d, "1.0".getBytes()), + pair(0d, "0.0".getBytes()), + pair(-1d, "-1.0".getBytes()), + pair(127d, "127.0".getBytes()), + pair((byte) 4, "4".getBytes()), + pair((short) 5, "5".getBytes()), + pair(6, "6".getBytes()), + pair(7L, "7".getBytes()), + pair(8f, "8.0".getBytes()), + pair(9d, "9.0".getBytes()), + pair(true, "true".getBytes()), + pair(false, "false".getBytes()), + pair("", "".getBytes()), + pair("test1", "test1".getBytes()), + pair(stream("test3"), "test3".getBytes()), + pair(reader("test4"), "test4".getBytes()) + ), + Arrays.asList( + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d), + PrimitiveValue.newJson("test") + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Json", "JsonDocument", "Yson"}) + void setBytesJson(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setBytes, + YdbPreparedStatement::setBytes, + ResultSet::getBytes, + Arrays.asList( + pair("[2]".getBytes(), "[2]".getBytes()) + ), + Arrays.asList( + pair("[1]", "[1]".getBytes()), + pair(stream("[3]"), "[3]".getBytes()), + pair(reader("[4]"), "[4]".getBytes()) + // No empty values supported + ), + Arrays.asList( + 6, + 7L, + 8f, + 9d, + true, + false, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d), + PrimitiveValue.newText("test") + ) + ); + } + + @Test + void setDateToDate() throws SQLException { + checkInsert("c_Date", "Date", + YdbPreparedStatement::setDate, + YdbPreparedStatement::setDate, + ResultSet::getDate, + Arrays.asList( + pair(new Date(1), new Date(0)), + pair(new Date(0), new Date(0)), + pair(new Date(MILLIS_IN_DAY), new Date(MILLIS_IN_DAY)), + pair(new Date(MILLIS_IN_DAY * 2), new Date(MILLIS_IN_DAY * 2)) + ), + Arrays.asList( + pair(Instant.ofEpochMilli(2), new Date(0)), + pair(Instant.ofEpochMilli(MILLIS_IN_DAY), new Date(MILLIS_IN_DAY)), + pair(Instant.parse("1970-01-01T00:00:03.111112Z"), new Date(0)), + pair("1970-01-01T00:00:03.111112Z", new Date(0)), + pair(3L, new Date(0)), + pair(MILLIS_IN_DAY * 3, new Date(MILLIS_IN_DAY * 3)), + pair(LocalDate.of(1970, 1, 2), new Date(MILLIS_IN_DAY)), + pair(new Timestamp(4), new Date(0)), + pair(new Timestamp(MILLIS_IN_DAY * 3), new Date(MILLIS_IN_DAY * 3)), + pair(new java.util.Date(5), new Date(0)), + pair(new java.util.Date(6999), new Date(0)), + pair(new java.util.Date(MILLIS_IN_DAY * 7), new Date(MILLIS_IN_DAY * 7)) + ), + Arrays.asList( + 6, + 8f, + 9d, + true, + false, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d), + PrimitiveValue.newText("test") + ) + ); + } + + @Test + void setDateToDatetime() throws SQLException { + // precision - seconds + checkInsert("c_Datetime", "Datetime", + YdbPreparedStatement::setDate, + YdbPreparedStatement::setDate, + ResultSet::getDate, + Arrays.asList( + pair(new Date(1), new Date(0)), + pair(new Date(0), new Date(0)), + pair(new Date(1000), new Date(1000)), + pair(new Date(1999), new Date(1000)), + pair(new Date(MILLIS_IN_DAY), new Date(MILLIS_IN_DAY)), + pair(new Date(MILLIS_IN_DAY * 2), new Date(MILLIS_IN_DAY * 2)) + ), + Arrays.asList( + pair(Instant.ofEpochMilli(2), new Date(0)), + pair(Instant.ofEpochMilli(MILLIS_IN_DAY), new Date(MILLIS_IN_DAY)), + pair(Instant.parse("1970-01-01T00:00:03.111112Z"), new Date(3000)), + pair("1970-01-01T00:00:03.111112Z", new Date(3000)), + pair(3L, new Date(0)), + pair(2000L, new Date(2000L)), + pair(2999L, new Date(2000L)), + pair(MILLIS_IN_DAY * 3, new Date(MILLIS_IN_DAY * 3)), + pair(new Timestamp(4), new Date(0)), + pair(new Timestamp(4000), new Date(4000)), + pair(new Timestamp(4999), new Date(4000)), + pair(new Timestamp(MILLIS_IN_DAY * 3), new Date(MILLIS_IN_DAY * 3)), + pair(new Time(10), new Date(0)), + pair(new Time(5000), new Date(5000)), + pair(new Time(5999), new Date(5000)), + pair(new Time(MILLIS_IN_DAY * 4), new Date(MILLIS_IN_DAY * 4)), + pair(new java.util.Date(5), new Date(0)), + pair(new java.util.Date(6999), new Date(6000)), + pair(new java.util.Date(MILLIS_IN_DAY * 7), new Date(MILLIS_IN_DAY * 7)) + ), + Arrays.asList( + 6, + 8f, + 9d, + true, + false, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d), + PrimitiveValue.newText("test"), + LocalDate.of(1970, 1, 2) + ) + ); + } + + @Test + void setDateToTimestamp() throws SQLException { + // precision - microseconds + checkInsert("c_Timestamp", "Timestamp", + YdbPreparedStatement::setDate, + YdbPreparedStatement::setDate, + ResultSet::getDate, + Arrays.asList( + pair(new Date(1), new Date(1)), + pair(new Date(0), new Date(0)), + pair(new Date(1000), new Date(1000)), + pair(new Date(1999), new Date(1999)), + pair(new Date(MILLIS_IN_DAY), new Date(MILLIS_IN_DAY)), + pair(new Date(MILLIS_IN_DAY * 2), new Date(MILLIS_IN_DAY * 2)) + ), + Arrays.asList( + pair(Instant.ofEpochMilli(2), new Date(2)), + pair(Instant.ofEpochMilli(MILLIS_IN_DAY), new Date(MILLIS_IN_DAY)), + pair(Instant.parse("1970-01-01T00:00:03.111112Z"), new Date(3111)), + pair("1970-01-01T00:00:03.111112Z", new Date(3111)), + pair(3L, new Date(3)), + pair(2000L, new Date(2000L)), + pair(2999L, new Date(2999L)), + pair(MILLIS_IN_DAY * 3, new Date(MILLIS_IN_DAY * 3)), + pair(new Timestamp(4), new Date(4)), + pair(new Timestamp(4000), new Date(4000)), + pair(new Timestamp(4999), new Date(4999)), + pair(new Timestamp(MILLIS_IN_DAY * 3), new Date(MILLIS_IN_DAY * 3)), + pair(new Time(10), new Date(10)), + pair(new Time(5000), new Date(5000)), + pair(new Time(5999), new Date(5999)), + pair(new Time(MILLIS_IN_DAY * 4), new Date(MILLIS_IN_DAY * 4)), + pair(new java.util.Date(5), new Date(5)), + pair(new java.util.Date(6999), new Date(6999)), + pair(new java.util.Date(MILLIS_IN_DAY * 7), new Date(MILLIS_IN_DAY * 7)) + ), + Arrays.asList( + 6, + 8f, + 9d, + true, + false, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d), + PrimitiveValue.newText("test"), + LocalDate.of(1970, 1, 2) + ) + ); + } + + @Test + void setTimeToDatetime() throws SQLException { + checkInsert("c_Datetime", "Datetime", + YdbPreparedStatement::setTime, + YdbPreparedStatement::setTime, + ResultSet::getTime, + Arrays.asList( + pair(new Time(1), new Time(0)), + pair(new Time(0), new Time(0)), + pair(new Time(1000), new Time(1000)), + pair(new Time(1999), new Time(1000)), + pair(new Time(MILLIS_IN_DAY), new Time(MILLIS_IN_DAY)), + pair(new Time(MILLIS_IN_DAY * 2), new Time(MILLIS_IN_DAY * 2)) + ), + Arrays.asList( + pair(Instant.ofEpochMilli(2), new Time(0)), + pair(Instant.ofEpochMilli(MILLIS_IN_DAY), new Time(MILLIS_IN_DAY)), + pair(Instant.parse("1970-01-01T00:00:03.111112Z"), new Date(3000)), + pair(3L, new Time(0)), + pair(2000L, new Time(2000L)), + pair(2999L, new Time(2000L)), + pair(MILLIS_IN_DAY * 3, new Time(MILLIS_IN_DAY * 3)), + pair(new Timestamp(4), new Time(0)), + pair(new Timestamp(4000), new Time(4000)), + pair(new Timestamp(4999), new Time(4000)), + pair(new Timestamp(MILLIS_IN_DAY * 3), new Time(MILLIS_IN_DAY * 3)), + pair(new Date(10), new Time(0)), + pair(new Date(5000), new Time(5000)), + pair(new Date(5999), new Time(5000)), + pair(new Date(MILLIS_IN_DAY * 4), new Time(MILLIS_IN_DAY * 4)) + ), + Arrays.asList( + 6, + 8f, + 9d, + true, + false, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d), + PrimitiveValue.newText("test"), + LocalDate.of(1970, 1, 2) + ) + ); + } + + @Test + void setTimeToTimestamp() throws SQLException { + checkInsert("c_Timestamp", "Timestamp", + YdbPreparedStatement::setTime, + YdbPreparedStatement::setTime, + ResultSet::getTime, + Arrays.asList( + pair(new Time(1), new Time(1)), + pair(new Time(0), new Time(0)), + pair(new Time(1000), new Time(1000)), + pair(new Time(1999), new Time(1999)), + pair(new Time(MILLIS_IN_DAY), new Time(MILLIS_IN_DAY)), + pair(new Time(MILLIS_IN_DAY * 2), new Time(MILLIS_IN_DAY * 2)) + ), + Arrays.asList( + pair(Instant.ofEpochMilli(2), new Time(2)), + pair(Instant.ofEpochMilli(MILLIS_IN_DAY), new Time(MILLIS_IN_DAY)), + pair(Instant.parse("1970-01-01T00:00:03.111112Z"), new Date(3111)), + pair(3L, new Time(3)), + pair(2000L, new Time(2000L)), + pair(2999L, new Time(2999L)), + pair(MILLIS_IN_DAY * 3, new Time(MILLIS_IN_DAY * 3)), + pair(new Timestamp(4), new Time(4)), + pair(new Timestamp(4000), new Time(4000)), + pair(new Timestamp(4999), new Time(4999)), + pair(new Timestamp(MILLIS_IN_DAY * 3), new Time(MILLIS_IN_DAY * 3)), + pair(new Date(10), new Time(10)), + pair(new Date(5000), new Time(5000)), + pair(new Date(5999), new Time(5999)), + pair(new Date(MILLIS_IN_DAY * 4), new Time(MILLIS_IN_DAY * 4)) + ), + Arrays.asList( + 6, + 8f, + 9d, + true, + false, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d), + PrimitiveValue.newText("test"), + LocalDate.of(1970, 1, 2) + ) + ); + } + + @Test + void setTimestampToDate() throws SQLException { + checkInsert("c_Date", "Date", + YdbPreparedStatement::setTimestamp, + YdbPreparedStatement::setTimestamp, + ResultSet::getTimestamp, + Arrays.asList( + pair(new Timestamp(1), new Timestamp(0)), + pair(new Timestamp(0), new Timestamp(0)), + pair(new Timestamp(MILLIS_IN_DAY), new Timestamp(MILLIS_IN_DAY)), + pair(new Timestamp(MILLIS_IN_DAY * 2), new Timestamp(MILLIS_IN_DAY * 2)) + ), + Arrays.asList( + pair(Instant.ofEpochMilli(2), new Timestamp(0)), + pair(Instant.ofEpochMilli(MILLIS_IN_DAY), new Timestamp(MILLIS_IN_DAY)), + pair(Instant.parse("1970-01-01T00:00:03.111112Z"), new Timestamp(0)), + pair(3L, new Timestamp(0)), + pair(MILLIS_IN_DAY * 3, new Timestamp(MILLIS_IN_DAY * 3)), + pair(LocalDate.of(1970, 1, 2), new Timestamp(MILLIS_IN_DAY)), + pair(new Timestamp(4), new Timestamp(0)), + pair(new Timestamp(MILLIS_IN_DAY * 3), new Timestamp(MILLIS_IN_DAY * 3)) + ), + Arrays.asList( + 6, + 8f, + 9d, + true, + false, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d), + PrimitiveValue.newText("test") + ) + ); + } + + @Test + void setTimestampToDatetime() throws SQLException { + checkInsert("c_Datetime", "Datetime", + YdbPreparedStatement::setTimestamp, + YdbPreparedStatement::setTimestamp, + ResultSet::getTimestamp, + Arrays.asList( + pair(new Timestamp(1), new Timestamp(0)), + pair(new Timestamp(0), new Timestamp(0)), + pair(new Timestamp(1000), new Timestamp(1000)), + pair(new Timestamp(1999), new Timestamp(1000)), + pair(new Timestamp(MILLIS_IN_DAY), new Timestamp(MILLIS_IN_DAY)), + pair(new Timestamp(MILLIS_IN_DAY * 2), new Timestamp(MILLIS_IN_DAY * 2)) + ), + Arrays.asList( + pair(Instant.ofEpochMilli(2), new Timestamp(0)), + pair(Instant.ofEpochMilli(MILLIS_IN_DAY), new Timestamp(MILLIS_IN_DAY)), + pair(Instant.parse("1970-01-01T00:00:03.111112Z"), new Timestamp(3000)), + pair(3L, new Timestamp(0)), + pair(2000L, new Timestamp(2000L)), + pair(2999L, new Timestamp(2000L)), + pair(MILLIS_IN_DAY * 3, new Timestamp(MILLIS_IN_DAY * 3)), + pair(new Timestamp(4), new Timestamp(0)), + pair(new Timestamp(4000), new Timestamp(4000)), + pair(new Timestamp(4999), new Timestamp(4000)), + pair(new Timestamp(MILLIS_IN_DAY * 3), new Timestamp(MILLIS_IN_DAY * 3)), + pair(new Date(10), new Timestamp(0)), + pair(new Date(5000), new Timestamp(5000)), + pair(new Date(5999), new Timestamp(5000)), + pair(new Date(MILLIS_IN_DAY * 4), new Timestamp(MILLIS_IN_DAY * 4)), + pair(new Time(10), new Timestamp(0)), + pair(new Time(5000), new Timestamp(5000)), + pair(new Time(5999), new Timestamp(5000)), + pair(new Time(MILLIS_IN_DAY * 4), new Timestamp(MILLIS_IN_DAY * 4)) + ), + Arrays.asList( + 6, + 8f, + 9d, + true, + false, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d), + PrimitiveValue.newText("test"), + LocalDate.of(1970, 1, 2) + ) + ); + } + + @Test + void setTimestampToTimestamp() throws SQLException { + checkInsert("c_Timestamp", "Timestamp", + YdbPreparedStatement::setTimestamp, + YdbPreparedStatement::setTimestamp, + ResultSet::getTimestamp, + Arrays.asList( + pair(new Timestamp(1), new Timestamp(1)), + pair(new Timestamp(0), new Timestamp(0)), + pair(new Timestamp(1000), new Timestamp(1000)), + pair(new Timestamp(1999), new Timestamp(1999)), + pair(new Timestamp(MILLIS_IN_DAY), new Timestamp(MILLIS_IN_DAY)), + pair(new Timestamp(MILLIS_IN_DAY * 2), new Timestamp(MILLIS_IN_DAY * 2)) + ), + Arrays.asList( + pair(Instant.ofEpochMilli(2), new Timestamp(2)), + pair(Instant.ofEpochMilli(MILLIS_IN_DAY), new Timestamp(MILLIS_IN_DAY)), + pair(Instant.parse("1970-01-01T00:00:03.111112Z"), new Timestamp(3111)), + pair(3L, new Timestamp(3)), + pair(2000L, new Timestamp(2000L)), + pair(2999L, new Timestamp(2999L)), + pair(MILLIS_IN_DAY * 3, new Timestamp(MILLIS_IN_DAY * 3)), + pair(new Timestamp(4), new Timestamp(4)), + pair(new Timestamp(4000), new Timestamp(4000)), + pair(new Timestamp(4999), new Timestamp(4999)), + pair(new Timestamp(MILLIS_IN_DAY * 3), new Timestamp(MILLIS_IN_DAY * 3)), + pair(new Date(10), new Timestamp(10)), + pair(new Date(5000), new Timestamp(5000)), + pair(new Date(5999), new Timestamp(5999)), + pair(new Date(MILLIS_IN_DAY * 4), new Timestamp(MILLIS_IN_DAY * 4)), + pair(new Time(10), new Timestamp(10)), + pair(new Time(5000), new Timestamp(5000)), + pair(new Time(5999), new Timestamp(5999)), + pair(new Time(MILLIS_IN_DAY * 4), new Timestamp(MILLIS_IN_DAY * 4)) + ), + Arrays.asList( + 6, + 8f, + 9d, + true, + false, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d), + PrimitiveValue.newText("test"), + LocalDate.of(1970, 1, 2) + ) + ); + } + + @Test + void setTimestampToInterval() throws SQLException { + checkInsert("c_Interval", "Interval", + YdbPreparedStatement::setLong, + YdbPreparedStatement::setLong, + ResultSet::getLong, + Arrays.asList( + pair(1L, 1L), + pair(0L, 0L), + pair(1000L, 1000L) + ), + Arrays.asList( + pair(Duration.parse("PT3.111113S"), 3111113L), + pair(3L, 3L), + pair(2000L, 2000L), + pair(2999L, 2999L), + pair(MICROS_IN_DAY, MICROS_IN_DAY), + pair(MICROS_IN_DAY * 3, MICROS_IN_DAY * 3) + ), + Arrays.asList( + 6, + 8f, + 9d, + true, + false, + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d), + PrimitiveValue.newText("test"), + LocalDate.of(1970, 1, 2), + new Timestamp(4), + new Date(10), + new Time(10) + ) + ); + } + + @Test + void setAsciiStream() throws SQLException { + retry(connection -> + assertThrowsMsg(SQLFeatureNotSupportedException.class, + () -> getTextStatement(connection).setAsciiStream("value", stream("value")), + "AsciiStreams are not supported")); + retry(connection -> + assertThrowsMsg(SQLFeatureNotSupportedException.class, + () -> getTextStatement(connection).setAsciiStream("value", stream("value"), 1), + "AsciiStreams are not supported")); + retry(connection -> + assertThrowsMsg(SQLFeatureNotSupportedException.class, + () -> getTextStatement(connection).setAsciiStream("value", stream("value"), 1L), + "AsciiStreams are not supported")); + } + + @SuppressWarnings("deprecation") + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text", "Json", "JsonDocument", "Yson"}) + void setUnicodeStream(String type) throws SQLException { + checkInsert("c_" + type, type, + (ps, name, value) -> ps.setUnicodeStream(name, value, 3), + (ps, name, value) -> ps.setUnicodeStream(name, value, 3), + ResultSet::getUnicodeStream, + Arrays.asList( + pair(stream("[3]-limited!"), stream("[3]")) + ), + Arrays.asList( + pair("[1]", stream("[1]")), + pair("[2]".getBytes(), stream("[2]")), + pair(reader("[4]"), stream("[4]")) + ), + Arrays.asList( + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text", "Json", "JsonDocument", "Yson"}) + void setBinaryStream(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setBinaryStream, + YdbPreparedStatement::setBinaryStream, + ResultSet::getBinaryStream, + Arrays.asList( + pair(stream("[3]"), stream("[3]")) + ), + Arrays.asList( + pair("[1]", stream("[1]")), + pair("[2]".getBytes(), stream("[2]")), + pair(reader("[4]"), stream("[4]")) + ), + Arrays.asList( + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text", "Json", "JsonDocument", "Yson"}) + void setBinaryStreamInt(String type) throws SQLException { + checkInsert("c_" + type, type, + (ps, name, value) -> ps.setBinaryStream(name, value, 3), + (ps, name, value) -> ps.setBinaryStream(name, value, 3), + ResultSet::getBinaryStream, + Arrays.asList( + pair(stream("[3]-limited!"), stream("[3]")) + ), + Arrays.asList( + pair("[1]", stream("[1]")), + pair("[2]".getBytes(), stream("[2]")), + pair(reader("[4]"), stream("[4]")) + ), + Arrays.asList( + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text", "Json", "JsonDocument", "Yson"}) + void setBinaryStreamLong(String type) throws SQLException { + checkInsert("c_" + type, type, + (ps, name, value) -> ps.setBinaryStream(name, value, 3L), + (ps, name, value) -> ps.setBinaryStream(name, value, 3L), + ResultSet::getBinaryStream, + Arrays.asList( + pair(stream("[3]-limited!"), stream("[3]")) + ), + Arrays.asList( + pair("[1]", stream("[1]")), + pair("[2]".getBytes(), stream("[2]")), + pair(reader("[4]"), stream("[4]")) + ), + Arrays.asList( + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text", "Json", "JsonDocument", "Yson"}) + void setCharacterStream(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setCharacterStream, + YdbPreparedStatement::setCharacterStream, + ResultSet::getCharacterStream, + Arrays.asList( + pair(reader("[4]"), reader("[4]")) + ), + Arrays.asList( + pair("[1]", reader("[1]")), + pair("[2]".getBytes(), reader("[2]")), + pair(stream("[3]"), reader("[3]")) + ), + Arrays.asList( + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text", "Json", "JsonDocument", "Yson"}) + void setCharacterStreamInt(String type) throws SQLException { + checkInsert("c_" + type, type, + (ps, name, value) -> ps.setCharacterStream(name, value, 3), + (ps, name, value) -> ps.setCharacterStream(name, value, 3), + ResultSet::getCharacterStream, + Arrays.asList( + pair(reader("[4]-limited!"), reader("[4]")) + ), + Arrays.asList(), + Arrays.asList() + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text"}) + void setCharacterStreamIntEmpty(String type) throws SQLException { + checkInsert("c_" + type, type, + (ps, name, value) -> ps.setCharacterStream(name, value, 0), + (ps, name, value) -> ps.setCharacterStream(name, value, 0), + ResultSet::getCharacterStream, + Arrays.asList( + pair(reader("[4]-limited!"), reader("")) + ), + Arrays.asList(), + Arrays.asList() + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text", "Json", "JsonDocument", "Yson"}) + void setCharacterStreamLong(String type) throws SQLException { + checkInsert("c_" + type, type, + (ps, name, value) -> ps.setCharacterStream(name, value, 3L), + (ps, name, value) -> ps.setCharacterStream(name, value, 3L), + ResultSet::getCharacterStream, + Arrays.asList( + pair(reader("[4]-limited!"), reader("[4]")) + ), + Arrays.asList(), + Arrays.asList() + ); + } + + + @Test + void setRef() throws SQLException { + retry(connection -> + assertThrowsMsg(SQLFeatureNotSupportedException.class, + () -> getTextStatement(connection).setRef("value", new RefImpl()), + "Refs are not supported")); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text", "Json", "JsonDocument", "Yson"}) + void asBlob(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setBlob, + YdbPreparedStatement::setBlob, + ResultSet::getBinaryStream, + Arrays.asList( + pair(stream("[3]"), stream("[3]")) + ), + Arrays.asList( + pair("[1]", stream("[1]")), + pair("[2]".getBytes(), stream("[2]")), + pair(reader("[4]"), stream("[4]")) + ), + Arrays.asList( + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text", "Json", "JsonDocument", "Yson"}) + void asBlobLong(String type) throws SQLException { + checkInsert("c_" + type, type, + (ps, name, value) -> ps.setBlob(name, value, 3L), + (ps, name, value) -> ps.setBlob(name, value, 3L), + ResultSet::getBinaryStream, + Arrays.asList( + pair(stream("[3]-limited!"), stream("[3]")) + ), + Arrays.asList(), + Arrays.asList() + ); + } + + @Test + void setBlobUnsupported() throws SQLException { + retry(connection -> + assertThrowsMsg(SQLFeatureNotSupportedException.class, + () -> getTextStatement(connection).setBlob("value", new SerialBlob("".getBytes())), + "Blobs are not supported")); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text", "Json", "JsonDocument", "Yson"}) + void asClob(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setClob, + YdbPreparedStatement::setClob, + ResultSet::getCharacterStream, + Arrays.asList( + pair(reader("[4]"), reader("[4]")) + ), + Arrays.asList( + pair("[1]", reader("[1]")), + pair("[2]".getBytes(), reader("[2]")), + pair(stream("[3]"), reader("[3]")) + ), + Arrays.asList( + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text", "Json", "JsonDocument", "Yson"}) + void asClobLong(String type) throws SQLException { + checkInsert("c_" + type, type, + (ps, name, value) -> ps.setClob(name, value, 3L), + (ps, name, value) -> ps.setClob(name, value, 3L), + ResultSet::getCharacterStream, + Arrays.asList( + pair(reader("[4]-limited!"), reader("[4]")) + ), + Arrays.asList(), + Arrays.asList() + ); + } + + @Test + void setClobUnsupported() throws SQLException { + retry(connection -> + assertThrowsMsg(SQLFeatureNotSupportedException.class, + () -> getTextStatement(connection).setClob("value", new NClobImpl("".toCharArray())), + "Clobs are not supported")); + } + + @Test + void setArray() throws SQLException { + retry(connection -> + assertThrowsMsg(SQLFeatureNotSupportedException.class, + () -> getTextStatement(connection).setArray("value", new ArrayImpl()), + "Arrays are not supported")); + } + + @Test + void getMetaData() throws SQLException { + retry(connection -> + assertThrowsMsg(SQLFeatureNotSupportedException.class, + () -> getTextStatement(connection).getMetaData(), + "ResultSet metadata is not supported in prepared statements")); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text"}) + void setURL(String type) throws SQLException, MalformedURLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setURL, + YdbPreparedStatement::setURL, + ResultSet::getURL, + Arrays.asList( + pair(new URL("https://localhost"), new URL("https://localhost")), + pair(new URL("ftp://localhost"), new URL("ftp://localhost")) + ), + Arrays.asList( + pair("https://localhost", new URL("https://localhost")), + pair("ftp://localhost".getBytes(), new URL("ftp://localhost")), + pair(stream("https://localhost"), new URL("https://localhost")), + pair(reader("ftp://localhost"), new URL("ftp://localhost")) + ), + Arrays.asList( + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d) + ) + ); + } + + @Test + void setRowId() throws SQLException { + retry(connection -> + assertThrowsMsg(SQLFeatureNotSupportedException.class, + () -> getTextStatement(connection).setRowId(1, new RowIdImpl()), + "RowIds are not supported")); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text"}) + void setNString(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setNString, + YdbPreparedStatement::setNString, + ResultSet::getNString, + Arrays.asList( + pair("", ""), + pair("test1", "test1") + ), + Arrays.asList( + pair(1d, "1.0"), + pair(0d, "0.0"), + pair(-1d, "-1.0"), + pair(127d, "127.0"), + pair((byte) 4, "4"), + pair((short) 5, "5"), + pair(6, "6"), + pair(7L, "7"), + pair(8f, "8.0"), + pair(9d, "9.0"), + pair(true, "true"), + pair(false, "false"), + pair("".getBytes(), ""), + pair("test2".getBytes(), "test2"), + pair(stream("test3"), "test3"), + pair(reader("test4"), "test4") + ), + Arrays.asList( + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d), + PrimitiveValue.newJson("test") + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text", "Json", "JsonDocument", "Yson"}) + void setNCharacterStream(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setNCharacterStream, + YdbPreparedStatement::setNCharacterStream, + ResultSet::getNCharacterStream, + Arrays.asList( + pair(reader("[4]"), reader("[4]")) + ), + Arrays.asList( + pair("[1]", reader("[1]")), + pair("[2]".getBytes(), reader("[2]")), + pair(stream("[3]"), reader("[3]")) + ), + Arrays.asList( + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text", "Json", "JsonDocument", "Yson"}) + void setNCharacterStreamLong(String type) throws SQLException { + checkInsert("c_" + type, type, + (ps, name, value) -> ps.setNCharacterStream(name, value, 3L), + (ps, name, value) -> ps.setNCharacterStream(name, value, 3L), + ResultSet::getNCharacterStream, + Arrays.asList( + pair(reader("[4]-limited!"), reader("[4]")) + ), + Arrays.asList(), + Arrays.asList() + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text", "Json", "JsonDocument", "Yson"}) + void asNClob(String type) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setNClob, + YdbPreparedStatement::setNClob, + ResultSet::getCharacterStream, + Arrays.asList( + pair(reader("[4]"), reader("[4]")) + ), + Arrays.asList( + pair("[1]", reader("[1]")), + pair("[2]".getBytes(), reader("[2]")), + pair(stream("[3]"), reader("[3]")) + ), + Arrays.asList( + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"Bytes", "Text", "Json", "JsonDocument", "Yson"}) + void asNClobLong(String type) throws SQLException { + checkInsert("c_" + type, type, + (ps, name, reader) -> ps.setNClob(name, reader, 3L), + (ps, name, reader) -> ps.setNClob(name, reader, 3L), + ResultSet::getCharacterStream, + Arrays.asList( + pair(reader("[4]-limited!"), reader("[4]")) + ), + Arrays.asList(), + Arrays.asList() + ); + } + + @Test + void setNClobUnsupported() throws SQLException { + retry(connection -> + assertThrowsMsg(SQLFeatureNotSupportedException.class, + () -> getTextStatement(connection).setNClob("value", new NClobImpl("".toCharArray())), + "NClobs are not supported")); + } + + @Test + void setSQLXML() throws SQLException { + retry(connection -> + assertThrowsMsg(SQLFeatureNotSupportedException.class, + () -> getTextStatement(connection).setSQLXML("value", new SQLXMLImpl()), + "SQLXMLs are not supported")); + } + + @ParameterizedTest + @MethodSource("bytesAndText") + void setObject(String type, List> callSetObject, List unsupported) throws SQLException { + checkInsert("c_" + type, type, + YdbPreparedStatement::setObject, + YdbPreparedStatement::setObject, + ResultSet::getObject, + Arrays.asList( + pair("", ""), + pair("test1", "test1"), + pair(1d, "1.0"), + pair(0d, "0.0"), + pair(-1d, "-1.0"), + pair(127d, "127.0"), + pair((byte) 4, "4"), + pair((short) 5, "5"), + pair(6, "6"), + pair(7L, "7"), + pair(8f, "8.0"), + pair(9d, "9.0"), + pair(true, "true"), + pair(false, "false"), + pair("".getBytes(), ""), + pair("test2".getBytes(), "test2"), + pair(stream("test3"), "test3"), + pair(reader("test4"), "test4") + ), + callSetObject, + merge(unsupported, + Arrays.asList( + PrimitiveValue.newBool(true), + PrimitiveValue.newBool(true).makeOptional(), + PrimitiveValue.newDouble(1.1d), + PrimitiveValue.newJson("test") + )) + ); + } + + @Test + public void unknownColumns() throws SQLException { + retry(connection -> + assertThrowsMsg(SQLException.class, + () -> { + YdbPreparedStatement statement = getTextStatement(connection); + statement.setObject("column0", "value"); + statement.execute(); + }, + "Parameter not found: " + (expectParameterPrefixed() ? "$column0" : "column0"))); + } + + @Test + public void queryInList() throws SQLException { + DecimalType defaultType = YdbTypes.DEFAULT_DECIMAL_TYPE; + + Set skip = set("c_Bool", "c_Json", "c_JsonDocument", "c_Yson"); + List> values = new ArrayList<>(); + for (int i = 1; i <= 3; i++) { + Map params = new LinkedHashMap<>(); + int prefix = 100 * i; + params.put("c_Bool", i % 2 == 0); + params.put("c_Int32", prefix++); + params.put("c_Int64", (long) prefix++); + params.put("c_Uint8", PrimitiveValue.newUint8((byte) prefix++).makeOptional()); + params.put("c_Uint32", PrimitiveValue.newUint32(prefix++).makeOptional()); + params.put("c_Uint64", PrimitiveValue.newUint64(prefix++).makeOptional()); + params.put("c_Float", (float) prefix++); + params.put("c_Double", (double) prefix++); + params.put("c_Bytes", PrimitiveValue.newBytes(String.valueOf(prefix++).getBytes()).makeOptional()); + params.put("c_Text", String.valueOf(prefix++)); + params.put("c_Json", PrimitiveValue.newJson("[" + (prefix++) + "]").makeOptional()); + params.put("c_JsonDocument", PrimitiveValue.newJsonDocument("[" + (prefix++) + "]").makeOptional()); + params.put("c_Yson", PrimitiveValue.newYson(("[" + (prefix++) + "]").getBytes()).makeOptional()); + params.put("c_Date", new Date(MILLIS_IN_DAY * (prefix++))); + params.put("c_Datetime", new Time(MILLIS_IN_DAY * (prefix++) + 111000)); + params.put("c_Timestamp", Instant.ofEpochMilli(MILLIS_IN_DAY * (prefix++) + 112112)); + params.put("c_Interval", Duration.of(prefix++, ChronoUnit.MICROS)); + params.put("c_Decimal", defaultType.newValue((prefix) + ".1").makeOptional()); + values.add(params); + } + + retry(connection -> { + YdbPreparedStatement statement = getTestAllValuesStatement(connection); + int key = 0; + for (Map params : values) { + for (Map.Entry entry : params.entrySet()) { + statement.setObject(entry.getKey(), entry.getValue()); + } + statement.setInt("key", ++key); + statement.executeUpdate(); + } + connection.commit(); + }); + + for (String key : values.get(0).keySet()) { + if (skip.contains(key)) { + continue; + } + retry(false, connection -> { + String type = key.substring("c_".length()); + if (type.equals("Decimal")) { + type = defaultType.toString(); + } + YdbPreparedStatement ps = connection.prepareStatement(String.format( + "declare $keys as List<%s?>;\n" + + "select count(1) as rows from unit_2 where %s in $keys", + type, key)); + + ps.setObject("keys", Arrays.asList()); + checkRows(0, ps.executeQuery()); + + ps.setObject("keys", Arrays.asList((Object) null)); + checkRows(0, ps.executeQuery()); + + ps.setObject("keys", Arrays.asList( + values.get(0).get(key))); + checkRows(1, ps.executeQuery()); + + ps.setObject("keys", Arrays.asList( + values.get(0).get(key), + values.get(1).get(key))); + checkRows(2, ps.executeQuery()); + + ps.setObject("keys", Arrays.asList( + values.get(0).get(key), + null, + values.get(1).get(key))); + checkRows(2, ps.executeQuery()); + + ps.setObject("keys", Arrays.asList( + values.get(0).get(key), + values.get(1).get(key), + values.get(2).get(key))); + checkRows(3, ps.executeQuery()); + }); + } + + retry(false, connection -> { + YdbPreparedStatement ps = connection.prepareStatement(String.format( + "declare $keys as List<%s?>;\n" + + "select count(1) as rows from unit_2 where %s in $keys", + "Bool", "c_Bool")); + + ps.setObject("keys", Arrays.asList()); + checkRows(0, ps.executeQuery()); + + ps.setObject("keys", Arrays.asList((Object) null)); + checkRows(0, ps.executeQuery()); + + ps.setObject("keys", Arrays.asList(true)); + checkRows(1, ps.executeQuery()); + + ps.setObject("keys", Arrays.asList(false)); + checkRows(2, ps.executeQuery()); + + ps.setObject("keys", Arrays.asList(true, false)); + checkRows(3, ps.executeQuery()); + + ps.setObject("keys", Arrays.asList(true, null, false)); + checkRows(3, ps.executeQuery()); + }); + } + + @Test + void unwrap() throws SQLException { + retry(connection -> { + YdbPreparedStatement statement = getTextStatement(connection); + assertTrue(statement.isWrapperFor(YdbPreparedStatement.class)); + assertSame(statement, statement.unwrap(YdbPreparedStatement.class)); + + assertFalse(statement.isWrapperFor(YdbConnection.class)); + assertThrowsMsg(SQLException.class, + () -> statement.unwrap(YdbConnection.class), + "Cannot unwrap to " + YdbConnection.class); + }); + } + + + // + + static Collection bytesAndText() { + return Arrays.asList( + Arguments.of("Bytes", + Arrays.asList( + pair(PrimitiveValue.newBytes("test-bytes".getBytes()), + "test-bytes"), + pair(PrimitiveValue.newBytes("test-bytes".getBytes()).makeOptional(), + "test-bytes") + ), + Arrays.asList( + PrimitiveValue.newText("test-utf8"), + PrimitiveValue.newText("test-utf8").makeOptional() + ) + ), + Arguments.of("Text", + Arrays.asList( + pair(PrimitiveValue.newText("test-utf8"), "test-utf8"), + pair(PrimitiveValue.newText("test-utf8").makeOptional(), "test-utf8") + ), + Arrays.asList( + PrimitiveValue.newBytes("test-bytes".getBytes()), + PrimitiveValue.newBytes("test-bytes".getBytes()).makeOptional() + ) + ) + ); + } + + static Collection jsonAndJsonDocumentAndYson() { + return Arrays.asList( + Arguments.of("Json", + Arrays.asList( + pair(PrimitiveValue.newJson("[1]"), "[1]"), + pair(PrimitiveValue.newJson("[1]").makeOptional(), "[1]") + ), + Arrays.asList( + PrimitiveValue.newText("test-utf8"), + PrimitiveValue.newText("test-utf8").makeOptional() + ) + ), + Arguments.of("JsonDocument", + Arrays.asList( + pair(PrimitiveValue.newJsonDocument("[1]"), "[1]"), + pair(PrimitiveValue.newJsonDocument("[1]").makeOptional(), "[1]") + ), + Arrays.asList( + PrimitiveValue.newText("test-utf8"), + PrimitiveValue.newText("test-utf8").makeOptional() + ) + ), + Arguments.of("Yson", + Arrays.asList( + pair(PrimitiveValue.newYson("[1]".getBytes()), "[1]"), + pair(PrimitiveValue.newYson("[1]".getBytes()).makeOptional(), "[1]") + ), + Arrays.asList( + PrimitiveValue.newText("test-utf8"), + PrimitiveValue.newText("test-utf8").makeOptional() + ) + ) + ); + } +*/ +} diff --git a/jdbc/src/test/java/tech/ydb/jdbc/impl/helper/JdbcConnectionExtention.java b/jdbc/src/test/java/tech/ydb/jdbc/impl/helper/JdbcConnectionExtention.java index bef884c..8ae3c52 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/impl/helper/JdbcConnectionExtention.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/impl/helper/JdbcConnectionExtention.java @@ -32,16 +32,26 @@ public class JdbcConnectionExtention implements ExecutionCondition, private final Map map = new HashMap<>(); private final Stack stack = new Stack<>(); + private JdbcConnectionExtention(JdbcUrlHelper jdbcURL) { + this.jdbcURL = jdbcURL; + } + public JdbcConnectionExtention(YdbHelperExtension ydb, boolean autoCommit) { - this.jdbcURL = new JdbcUrlHelper(ydb) + this(new JdbcUrlHelper(ydb) .withArg("failOnTruncatedResult", "true") - .withArg("autoCommit", String.valueOf(autoCommit)); + .withArg("autoCommit", String.valueOf(autoCommit)) +// .withArg("useQueryService", "true") + ); } public JdbcConnectionExtention(YdbHelperExtension ydb) { this(ydb, true); } + public JdbcConnectionExtention withArg(String key, String value) { + return new JdbcConnectionExtention(jdbcURL.withArg(key, value)); + } + private void register(ExtensionContext ctx) throws SQLException { Assert.assertFalse("Dublicate of context registration", map.containsKey(ctx)); diff --git a/jdbc/src/test/java/tech/ydb/jdbc/query/QueryLexerTest.java b/jdbc/src/test/java/tech/ydb/jdbc/query/QueryLexerTest.java index 80f9107..ada47b1 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/query/QueryLexerTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/query/QueryLexerTest.java @@ -2,10 +2,14 @@ import java.sql.SQLException; +import java.util.Properties; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import tech.ydb.jdbc.settings.YdbConfig; +import tech.ydb.jdbc.settings.YdbQueryProperties; + /** @@ -13,17 +17,31 @@ * @author Aleksandr Gorshenin */ public class QueryLexerTest { - private static YdbQuery parseQuery(YdbQueryOptions opts, String sql) throws SQLException { + private class ParamsBuilder { + private final Properties props = new Properties(); + + ParamsBuilder with(String name, String value) { + props.put(name, value); + return this; + } + + YdbQueryProperties build() throws SQLException { + YdbConfig config = YdbConfig.from("jdbc:ydb:localhost:2136/local", props); + return new YdbQueryProperties(config); + } + } + + private static YdbQuery parseQuery(YdbQueryProperties opts, String sql) throws SQLException { YdbQueryBuilder builder = new YdbQueryBuilder(sql, opts.getForcedQueryType()); JdbcQueryLexer.buildQuery(builder, opts); return builder.build(opts); } - private static QueryType parsedQueryType(YdbQueryOptions opts, String sql) throws SQLException { + private static QueryType parsedQueryType(YdbQueryProperties opts, String sql) throws SQLException { return parseQuery(opts, sql).type(); } - private void assertMixType(YdbQueryOptions opts, String types, String sql) { + private void assertMixType(YdbQueryProperties opts, String types, String sql) { SQLException ex = Assertions.assertThrows(SQLException.class, () -> { YdbQueryBuilder builder = new YdbQueryBuilder(sql, null); JdbcQueryLexer.buildQuery(builder, opts); @@ -33,7 +51,7 @@ private void assertMixType(YdbQueryOptions opts, String types, String sql) { @Test public void queryTypesTest() throws SQLException { - YdbQueryOptions opts = new YdbQueryOptions(true, false, false, false, false, null); + YdbQueryProperties opts = new ParamsBuilder().build(); Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "CREATE TABLE test_table (id int, value text)" @@ -79,7 +97,7 @@ public void queryTypesTest() throws SQLException { @Test public void mixQueryExceptionTest() throws SQLException { - YdbQueryOptions opts = new YdbQueryOptions(true, false, false, false, false, null); + YdbQueryProperties opts = new ParamsBuilder().build(); assertMixType(opts, "SCHEME_QUERY, DATA_QUERY", "CREATE TABLE test_table (id int, value text);" + @@ -101,7 +119,9 @@ public void mixQueryExceptionTest() throws SQLException { @Test public void forsedTypeTest() throws SQLException { - YdbQueryOptions opts = new YdbQueryOptions(true, false, false, false, false, QueryType.SCHEME_QUERY); + YdbQueryProperties opts = new ParamsBuilder() + .with("forceQueryMode", "SCHEME_QUERY") + .build(); Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "CREATE TABLE test_table (id int, value text)" diff --git a/jdbc/src/test/java/tech/ydb/jdbc/settings/YdbDriverProperitesTest.java b/jdbc/src/test/java/tech/ydb/jdbc/settings/YdbDriverProperitesTest.java new file mode 100644 index 0000000..e861d75 --- /dev/null +++ b/jdbc/src/test/java/tech/ydb/jdbc/settings/YdbDriverProperitesTest.java @@ -0,0 +1,447 @@ +package tech.ydb.jdbc.settings; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.time.Duration; +import java.util.Arrays; +import java.util.Collection; +import java.util.Properties; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.annotation.Nullable; + +import com.google.common.io.Files; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import tech.ydb.jdbc.YdbConst; +import tech.ydb.jdbc.YdbDriver; +import tech.ydb.jdbc.impl.helper.ExceptionAssert; + +public class YdbDriverProperitesTest { + public static final String TOKEN_FROM_FILE = "token-from-file"; + public static final String CERTIFICATE_FROM_FILE = "certificate-from-file"; + + private static File TOKEN_FILE; + private static File CERTIFICATE_FILE; + + private YdbDriver driver; + + @BeforeAll + public static void beforeAll() throws SQLException, IOException { + TOKEN_FILE = safeCreateFile(TOKEN_FROM_FILE); + CERTIFICATE_FILE = safeCreateFile(CERTIFICATE_FROM_FILE); + } + + @AfterAll + public static void afterAll() throws SQLException { + safeDeleteFile(TOKEN_FILE); + safeDeleteFile(CERTIFICATE_FILE); + } + + @BeforeEach + public void beforeEach() { + driver = new YdbDriver(); + } + + @AfterEach + public void afterEach() { + driver.close(); + } + + @Test + public void connectToUnsupportedUrl() throws SQLException { + Assertions.assertNull(driver.connect("jdbc:clickhouse:localhost:123", new Properties())); + } + + @ParameterizedTest + @MethodSource("urlsToParse") + public void parseURL(String url, @Nullable String connectionString, @Nullable String localDatacenter) + throws SQLException { + YdbConfig config = YdbConfig.from(url, new Properties()); + Assertions.assertEquals(connectionString, config.getConnectionString()); + + YdbConnectionProperties connectionProperties = new YdbConnectionProperties(config); + Assertions.assertEquals(localDatacenter, connectionProperties.getLocalDataCenter()); + } + + @ParameterizedTest(name = "[{index}] {0} => {1}") + @CsvSource(delimiter = ' ', value = { + "jdbc:ydb: \'\'", + "jdbc:ydb:ydb-demo.testhost.org:2135 grpc://ydb-demo.testhost.org:2135", + "jdbc:ydb:ydb-demo.testhost.org grpc://ydb-demo.testhost.org", + "jdbc:ydb:ydb-demo.testhost.org:2135?database=test/db grpc://ydb-demo.testhost.org:2135/test/db", + "jdbc:ydb:grpcs://ydb-demo.testhost.org grpcs://ydb-demo.testhost.org", + "jdbc:ydb:ydb-demo.testhost.org:2170?database=/test/db grpc://ydb-demo.testhost.org:2170/test/db", + "jdbc:ydb:ydb-demo.testhost.org:2133/test/db grpc://ydb-demo.testhost.org:2133/test/db", + "jdbc:ydb:grpcs://ydb-demo.testhost.org?database=test/db&dc=man grpcs://ydb-demo.testhost.org/test/db", + "jdbc:ydb:ydb-demo.testhost.org:2135/test/db?dc=man grpc://ydb-demo.testhost.org:2135/test/db", + }) + public void validURL(String url, String connectionString) throws SQLException { + Assertions.assertTrue(driver.acceptsURL(url)); + + DriverPropertyInfo[] properties = driver.getPropertyInfo(url, new Properties()); + Assertions.assertNotNull(properties); + + YdbConfig config = YdbConfig.from(url, new Properties()); + Assertions.assertNotNull(config); + Assertions.assertEquals(connectionString, config.getConnectionString()); + } + + @ParameterizedTest(name = "[{index}] {0}") + @ValueSource(strings = { + "ydb:", + "jdbc:ydb", + "jdbc:clickhouse://man", + }) + public void notValidURL(String url) throws SQLException { + Assertions.assertFalse(driver.acceptsURL(url)); + ExceptionAssert.sqlException("[" + url + "] is not a YDB URL, must starts from jdbc:ydb:", + () -> YdbConfig.from(url, new Properties()) + ); + } + + @Test + public void getPropertyInfoDefault() throws SQLException { + String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db"; + + Properties properties = new Properties(); + DriverPropertyInfo[] propertyInfo = driver.getPropertyInfo(url, properties); + Assertions.assertEquals(new Properties(), properties); + + assertPropertiesInfo(defaultPropertyInfo(), propertyInfo); + + YdbConfig config = YdbConfig.from(url, properties); + Assertions.assertEquals("grpc://ydb-demo.testhost.org:2135/test/db", config.getConnectionString()); + } + + private static String urlEncode(String value) { + try { + return URLEncoder.encode(value, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException ex) { + return value; + } + } + + @Test + public void getPropertyInfoAllFromUrl() throws SQLException { + Stream params = Stream.of(customizedPropertyInfo()) + .map(e -> e.name + "=" + urlEncode(e.value)); + String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db?" + params.collect(Collectors.joining("&")); + + DriverPropertyInfo[] propertyInfo = driver.getPropertyInfo(url, null); + + assertPropertiesInfo(customizedPropertyInfo(), propertyInfo); + + YdbConfig config = YdbConfig.from(url, null); + checkCustomizedProperties(config); + } + + @Test + public void getPropertyInfoFromProperties() throws SQLException { + String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db"; + + Properties properties = new Properties(); + for (DriverPropertyInfo info: customizedPropertyInfo()) { + properties.put(info.name, info.value); + } + + DriverPropertyInfo[] propertyInfo = driver.getPropertyInfo(url, properties); + assertPropertiesInfo(customizedPropertyInfo(), propertyInfo); + + YdbConfig config = YdbConfig.from(url, properties); + checkCustomizedProperties(config); + } + + @Test + public void getPropertyInfoOverwrite() throws SQLException { + String url = "jdbc:ydb:ydb-demo.testhost.org:2135/testing/ydb?localDatacenter=sas"; + Properties properties = new Properties(); + properties.put("localDatacenter", "vla"); + + Properties copy = new Properties(); + copy.putAll(properties); + + DriverPropertyInfo[] propertyInfo = driver.getPropertyInfo(url, properties); + Assertions.assertEquals(copy, properties); + + // URL will always overwrite properties + assertPropertiesInfo(defaultPropertyInfo("sas"), propertyInfo); + + YdbConfig config = YdbConfig.from(url, properties); + Assertions.assertEquals("grpc://ydb-demo.testhost.org:2135/testing/ydb", + config.getConnectionString()); + } + + @ParameterizedTest(name = "[{index}] {0} => {1}") + @MethodSource("tokensToCheck") + public void getTokenAs(String token, String expectValue) throws SQLException { + if ("file:".equals(token)) { + token += TOKEN_FILE.getAbsolutePath(); + } + + String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db?token=" + token; + Properties properties = new Properties(); + YdbConfig config = YdbConfig.from(url, properties); + + YdbConnectionProperties props = new YdbConnectionProperties(config); + + Assertions.assertEquals(expectValue, props.getToken()); + } + + @ParameterizedTest(name = "[{index}] {0} => {1}") + @CsvSource(delimiter = ',', value = { + "classpath:data/unknown-file.txt,Unable to find classpath resource: classpath:data/unknown-file.txt", + "file:data/unknown-file.txt,Unable to read resource from file:data/unknown-file.txt", + }) + public void getTokenAsInvalid(String token, String expectException) { + String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db?token=" + token; + ExceptionAssert.sqlException("Unable to convert property token: " + expectException, + () -> driver.getPropertyInfo(url, new Properties()) + ); + } + + @ParameterizedTest(name = "[{index}] {0} => {1}") + @MethodSource("certificatesToCheck") + public void getCaCertificateAs(String certificate, String expectValue) throws SQLException { + if ("file:".equals(certificate)) { + certificate += CERTIFICATE_FILE.getAbsolutePath(); + } + String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db" + + "?secureConnectionCertificate=" + certificate; + Properties properties = new Properties(); + YdbConfig config = YdbConfig.from(url, properties); + + YdbConnectionProperties props = new YdbConnectionProperties(config); + Assertions.assertArrayEquals(expectValue.getBytes(), props.getSecureConnectionCert()); + } + + @ParameterizedTest(name = "[{index}] {0} => {1}") + @CsvSource(delimiter = ',', value = { + "classpath:data/unknown-file.txt,Unable to find classpath resource: classpath:data/unknown-file.txt", + "file:data/unknown-file.txt,Unable to read resource from file:data/unknown-file.txt", + }) + public void getCaCertificateAsInvalid(String certificate, String expectException) { + String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db" + + "?secureConnectionCertificate=" + certificate; + ExceptionAssert.sqlException("Unable to convert property secureConnectionCertificate: " + expectException, + () -> driver.getPropertyInfo(url, new Properties()) + ); + } + + @ParameterizedTest + @ValueSource(strings = { + "sessionKeepAliveTime", + "sessionMaxIdleTime", + "joinDuration", + "queryTimeout", + "scanQueryTimeout", + "sessionTimeout", + "deadlineTimeout" + }) + public void invalidDuration(String param) { + String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db?" + param + "=1bc"; + ExceptionAssert.sqlException("Unable to convert property " + param + + ": Unable to parse value [1bc] -> [PT1BC] as Duration: Text cannot be parsed to a Duration", + () -> driver.getPropertyInfo(url, null)); + } + + @ParameterizedTest + @ValueSource(strings = { + "preparedStatementCacheQueries", + "sessionPoolSizeMin", + "sessionPoolSizeMax", + "transactionLevel" + }) + public void invalidInteger(String param) { + String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db?" + param + "=1bc"; + ExceptionAssert.sqlException("Unable to convert property " + param + + ": Unable to parse value [1bc] as Integer: For input string: \"1bc\"", + () -> driver.getPropertyInfo(url, null)); + } + + @Test + public void getMajorVersion() { + Assertions.assertEquals(2, driver.getMajorVersion()); + } + + @Test + public void getMinorVersion() { + Assertions.assertTrue(driver.getMinorVersion() >= 0); + } + + @Test + public void jdbcCompliant() { + Assertions.assertFalse(driver.jdbcCompliant()); + } + + @Test + public void getParentLogger() throws SQLFeatureNotSupportedException { + Assertions.assertNotNull(driver.getParentLogger()); + } + + static DriverPropertyInfo[] defaultPropertyInfo() { + return defaultPropertyInfo(""); + } + + static DriverPropertyInfo[] defaultPropertyInfo(@Nullable String localDatacenter) { + return new DriverPropertyInfo[]{ + new DriverPropertyInfo("cacheConnectionsInDriver", "true"), + new DriverPropertyInfo("preparedStatementCacheQueries", "256"), + new DriverPropertyInfo("useQueryService", "false"), + new DriverPropertyInfo("localDatacenter", localDatacenter), + new DriverPropertyInfo("secureConnection", ""), + new DriverPropertyInfo("secureConnectionCertificate", ""), + new DriverPropertyInfo("token", ""), + new DriverPropertyInfo("saFile", ""), + new DriverPropertyInfo("useMetadata", ""), + new DriverPropertyInfo("iamEndpoint", ""), + new DriverPropertyInfo("metadataURL", ""), + new DriverPropertyInfo("keepQueryText", ""), + new DriverPropertyInfo("sessionKeepAliveTime", ""), + new DriverPropertyInfo("sessionMaxIdleTime", ""), + new DriverPropertyInfo("sessionPoolSizeMin", ""), + new DriverPropertyInfo("sessionPoolSizeMax", ""), + new DriverPropertyInfo("joinDuration", "5m"), + new DriverPropertyInfo("queryTimeout", "0s"), + new DriverPropertyInfo("scanQueryTimeout", "5m"), + new DriverPropertyInfo("failOnTruncatedResult", "false"), + new DriverPropertyInfo("sessionTimeout", "5s"), + new DriverPropertyInfo("deadlineTimeout", "0s"), + new DriverPropertyInfo("autoCommit", "true"), + new DriverPropertyInfo("transactionLevel", "8"), + new DriverPropertyInfo("schemeQueryTxMode", "ERROR"), + new DriverPropertyInfo("scanQueryTxMode", "ERROR"), + new DriverPropertyInfo("disablePrepareDataQuery", "false"), + new DriverPropertyInfo("disableAutoPreparedBatches", "false"), + new DriverPropertyInfo("disableDetectSqlOperations", "false"), + new DriverPropertyInfo("disableJdbcParameters", "false"), + new DriverPropertyInfo("disableJdbcParameterDeclare", "false"), + new DriverPropertyInfo("forceQueryMode", ""), + }; + } + + static DriverPropertyInfo[] customizedPropertyInfo() { + return new DriverPropertyInfo[]{ + new DriverPropertyInfo("cacheConnectionsInDriver", "false"), + new DriverPropertyInfo("preparedStatementCacheQueries", "100"), + new DriverPropertyInfo("useQueryService", "true"), + new DriverPropertyInfo("localDatacenter", "sas"), + new DriverPropertyInfo("secureConnection", "true"), + new DriverPropertyInfo("secureConnectionCertificate", "classpath:data/certificate.txt"), + new DriverPropertyInfo("token", "x-secured-token"), + new DriverPropertyInfo("saFile", "x-secured-json"), + new DriverPropertyInfo("useMetadata", "true"), + new DriverPropertyInfo("iamEndpoint", "iam.endpoint.com"), + new DriverPropertyInfo("metadataURL", "https://metadata.com"), + new DriverPropertyInfo("keepQueryText", "true"), + new DriverPropertyInfo("sessionKeepAliveTime", "15m"), + new DriverPropertyInfo("sessionMaxIdleTime", "5m"), + new DriverPropertyInfo("sessionPoolSizeMin", "3"), + new DriverPropertyInfo("sessionPoolSizeMax", "4"), + new DriverPropertyInfo("joinDuration", "6m"), + new DriverPropertyInfo("queryTimeout", "2m"), + new DriverPropertyInfo("scanQueryTimeout", "3m"), + new DriverPropertyInfo("failOnTruncatedResult", "false"), + new DriverPropertyInfo("sessionTimeout", "6s"), + new DriverPropertyInfo("deadlineTimeout", "1s"), + new DriverPropertyInfo("autoCommit", "true"), + new DriverPropertyInfo("transactionLevel", "16"), + new DriverPropertyInfo("schemeQueryTxMode", "SHADOW_COMMIT"), + new DriverPropertyInfo("scanQueryTxMode", "FAKE_TX"), + new DriverPropertyInfo("disablePrepareDataQuery", "true"), + new DriverPropertyInfo("disableAutoPreparedBatches", "true"), + new DriverPropertyInfo("disableDetectSqlOperations", "true"), + new DriverPropertyInfo("disableJdbcParameters", "true"), + new DriverPropertyInfo("disableJdbcParameterDeclare", "true"), + new DriverPropertyInfo("forceQueryMode", "SCAN_QUERY"), + }; + } + + static void checkCustomizedProperties(YdbConfig config) throws SQLException { + Assertions.assertEquals("grpc://ydb-demo.testhost.org:2135/test/db", + config.getConnectionString()); + + YdbOperationProperties ops = new YdbOperationProperties(config); + Assertions.assertEquals(Duration.ofMinutes(6), ops.getJoinDuration()); + Assertions.assertEquals(Duration.ofMinutes(2), ops.getQueryTimeout()); + Assertions.assertEquals(Duration.ofMinutes(3), ops.getScanQueryTimeout()); + Assertions.assertFalse(ops.isFailOnTruncatedResult()); + Assertions.assertEquals(Duration.ofSeconds(6), ops.getSessionTimeout()); + Assertions.assertTrue(ops.isAutoCommit()); + Assertions.assertEquals(YdbConst.ONLINE_CONSISTENT_READ_ONLY, ops.getTransactionLevel()); + Assertions.assertFalse(config.isCacheConnectionsInDriver()); + } + + @SuppressWarnings("UnstableApiUsage") + public static Collection urlsToParse() { + return Arrays.asList( + Arguments.of("jdbc:ydb:ydb-demo.testhost.org:2135", + "grpc://ydb-demo.testhost.org:2135", + null), + Arguments.of("jdbc:ydb:ydb-demo.testhost.org:2135/demo/ci/testing/ci", + "grpc://ydb-demo.testhost.org:2135/demo/ci/testing/ci", + null), + Arguments.of("jdbc:ydb:grpc://ydb-demo.testhost.org:2135/demo/ci/testing/ci?localDatacenter=man", + "grpc://ydb-demo.testhost.org:2135/demo/ci/testing/ci", + "man"), + Arguments.of("jdbc:ydb:grpcs://ydb-demo.testhost.org:2135?localDatacenter=man", + "grpcs://ydb-demo.testhost.org:2135", + "man") + ); + } + + public static Collection tokensToCheck() { + return Arrays.asList( + Arguments.of("classpath:data/token.txt", "token-from-classpath"), + Arguments.of("file:", TOKEN_FROM_FILE)); + } + + public static Collection certificatesToCheck() { + return Arrays.asList( + Arguments.of("classpath:data/certificate.txt", "certificate-from-classpath"), + Arguments.of("file:", CERTIFICATE_FROM_FILE)); + } + + @SuppressWarnings("UnstableApiUsage") + private static File safeCreateFile(String content) throws IOException { + File file = File.createTempFile("junit", "ydb"); + Files.write(content.getBytes(), file); + return file; + } + + private static void safeDeleteFile(@Nullable File file) { + if (file != null) { + if (!file.delete()) { + file.deleteOnExit(); + } + } + } + + private static void assertPropertiesInfo(DriverPropertyInfo[] expected, DriverPropertyInfo[] actual) { + Assertions.assertEquals(expected.length, actual.length, "Wrong size of driver properties array"); + for (int idx = 0; idx < expected.length; idx += 1) { + Assertions.assertEquals(expected[idx].name, actual[idx].name, "Wrong name of property " + idx); + String name = expected[idx].name; + Assertions.assertEquals(expected[idx].value, actual[idx].value, "Wrong value of property " + name); + Assertions.assertFalse(actual[idx].required, "Wrong required of property " + name); + Assertions.assertNotNull(actual[idx].description, "Empty description of property " + name); + } + } +} diff --git a/jdbc/src/test/resources/logging.properties b/jdbc/src/test/resources/logging.properties index 5d4c392..2713ad2 100644 --- a/jdbc/src/test/resources/logging.properties +++ b/jdbc/src/test/resources/logging.properties @@ -4,4 +4,5 @@ java.util.logging.ConsoleHandler.level = ALL java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter java.util.logging.SimpleFormatter.format= [%1$tF %1$tT] %3$s [%4$s] %5$s %n -tech.ydb.jdbc.level=ALL \ No newline at end of file +tech.ydb.jdbc.level=ALL +#tech.ydb.level=FINEST diff --git a/pom.xml b/pom.xml index 8a0763d..2616f01 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ tech.ydb.jdbc ydb-jdbc-driver-parent - 2.0.7 + 2.1.0 YDB JDBC Module JDBC Driver over YDB Java SDK @@ -20,7 +20,7 @@ 1.7.36 5.9.3 - 2.1.11 + 2.2.0 @@ -87,7 +87,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.2.3 + 3.2.5 org.apache.maven.plugins @@ -146,7 +146,27 @@ org.apache.maven.plugins maven-shade-plugin - 3.5.1 + 3.5.2 + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.3.1 + + config/ydb.checkstyle.xml + config/ydb.suppressions.xml + true + + + + test + + check + + +