diff --git a/jdbc-shaded/pom.xml b/jdbc-shaded/pom.xml index b2b3b58..41c33ca 100644 --- a/jdbc-shaded/pom.xml +++ b/jdbc-shaded/pom.xml @@ -6,7 +6,7 @@ tech.ydb.jdbc ydb-jdbc-driver-parent - 2.0.3 + 2.0.4 ydb-jdbc-driver-shaded diff --git a/jdbc/pom.xml b/jdbc/pom.xml index 009e3ae..9764992 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -6,7 +6,7 @@ tech.ydb.jdbc ydb-jdbc-driver-parent - 2.0.3 + 2.0.4 ydb-jdbc-driver diff --git a/jdbc/src/main/java/tech/ydb/jdbc/YdbDriver.java b/jdbc/src/main/java/tech/ydb/jdbc/YdbDriver.java index 55ce441..27c14ff 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/YdbDriver.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/YdbDriver.java @@ -58,7 +58,7 @@ public YdbConnection connect(String url, Properties info) throws SQLException { return new YdbConnectionImpl(getCachedContext(config)); } - // create new context + // findOrCreateJdbcParams new context final YdbContext context = YdbContext.createContext(config); return new YdbConnectionImpl(context) { @Override 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 206127f..3a3b152 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/common/MappingSetters.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/common/MappingSetters.java @@ -22,7 +22,6 @@ import com.google.common.io.ByteStreams; import com.google.common.io.CharStreams; -import tech.ydb.jdbc.exception.YdbExecutionException; import tech.ydb.table.values.DecimalType; import tech.ydb.table.values.DecimalValue; import tech.ydb.table.values.ListType; @@ -461,7 +460,7 @@ static CharStream fromReader(Reader reader, long length) { return CharStreams.toString(reader); } } catch (IOException e) { - throw new YdbExecutionException(CANNOT_LOAD_DATA_FROM_READER + e.getMessage(), e); + throw new RuntimeException(CANNOT_LOAD_DATA_FROM_READER + e.getMessage(), e); } }; } @@ -480,7 +479,7 @@ static ByteStream fromInputStream(InputStream stream, long length) { return ByteStreams.toByteArray(stream); } } catch (IOException e) { - throw new YdbExecutionException(CANNOT_LOAD_DATA_FROM_IS + e.getMessage(), e); + throw new RuntimeException(CANNOT_LOAD_DATA_FROM_IS + e.getMessage(), e); } }; } 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 cf4f56a..ed7a07c 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/context/YdbContext.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/context/YdbContext.java @@ -1,16 +1,34 @@ package tech.ydb.jdbc.context; +import java.sql.SQLDataException; 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; import java.util.logging.Logger; +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.jdbc.exception.YdbConfigurationException; +import tech.ydb.jdbc.YdbConst; +import tech.ydb.jdbc.YdbPrepareMode; +import tech.ydb.jdbc.exception.ExceptionFactory; +import tech.ydb.jdbc.query.JdbcParams; +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; @@ -18,9 +36,15 @@ import tech.ydb.jdbc.settings.YdbConnectionProperty; import tech.ydb.jdbc.settings.YdbOperationProperties; 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; /** * @@ -41,6 +65,10 @@ public class YdbContext implements AutoCloseable { private final PooledTableClient tableClient; private final SchemeClient schemeClient; private final YdbQueryOptions queryOptions; + private final SessionRetryContext retryCtx; + + private final Cache queriesCache; + private final Cache> queryParamsCache; private final boolean autoResizeSessionPool; private final AtomicInteger connectionsCount = new AtomicInteger(); @@ -52,8 +80,20 @@ private YdbContext(YdbConfig config, GrpcTransport transport, PooledTableClient this.schemeClient = SchemeClient.newClient(transport).build(); this.queryOptions = YdbQueryOptions.createFrom(config.getOperationProperties()); this.autoResizeSessionPool = autoResize; + this.retryCtx = SessionRetryContext.create(tableClient).build(); + + int cacheSize = config.getOperationProperties().getPreparedStatementCacheSize(); + if (cacheSize > 0) { + queriesCache = CacheBuilder.newBuilder().maximumSize(cacheSize).build(); + queryParamsCache = CacheBuilder.newBuilder().maximumSize(cacheSize).build(); + } else { + queriesCache = null; + queryParamsCache = null; + } } + + public String getDatabase() { return grpcTransport.getDatabase(); } @@ -70,10 +110,6 @@ public String getUrl() { return config.getUrl(); } - public YdbQueryOptions getQueryOptions() { - return queryOptions; - } - public int getConnectionsCount() { return connectionsCount.get(); } @@ -131,8 +167,8 @@ public static YdbContext createContext(YdbConfig config) throws SQLException { boolean autoResize = buildTableClient(tableClient, clientProps); return new YdbContext(config, grpcTransport, tableClient.build(), autoResize); - } catch (Exception ex) { - throw new YdbConfigurationException("Cannot connect to YDB: " + ex.getMessage(), ex); + } catch (RuntimeException ex) { + throw new SQLException("Cannot connect to YDB: " + ex.getMessage(), ex); } } @@ -148,6 +184,17 @@ public static GrpcTransport buildGrpcTransport(YdbConnectionProperties props) { 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(); } @@ -179,4 +226,73 @@ private static boolean buildTableClient(TableClient.Builder builder, YdbClientPr builder.sessionPoolSize(minSize, maxSize); return false; } + + public > T withDefaultTimeout(T settings) { + Duration operation = config.getOperationProperties().getDeadlineTimeout(); + if (!operation.isZero() && !operation.isNegative()) { + settings.setOperationTimeout(operation); + settings.setTimeout(operation.plusSeconds(1)); + } + return settings; + } + + public CompletableFuture> describeTable(String tablePath, DescribeTableSettings settings) { + return retryCtx.supplyResult(session -> session.describeTable(tablePath, settings)); + } + + public YdbQuery parseYdbQuery(String sql) throws SQLException { + YdbQueryBuilder builder = new YdbQueryBuilder(sql, queryOptions.getForcedQueryType()); + JdbcQueryLexer.buildQuery(builder, queryOptions); + return builder.build(queryOptions); + } + + public YdbQuery findOrParseYdbQuery(String sql) throws SQLException { + if (queriesCache == null) { + return parseYdbQuery(sql); + } + + YdbQuery cached = queriesCache.getIfPresent(sql); + if (cached == null) { + cached = parseYdbQuery(sql); + queriesCache.put(sql, cached); + } + + return cached; + } + + public JdbcParams findOrCreateJdbcParams(YdbQuery query, YdbPrepareMode mode) throws SQLException { + if (query.hasIndexesParameters() + || mode == YdbPrepareMode.IN_MEMORY + || !queryOptions.iPrepareDataQueries()) { + return new InMemoryParams(query.getIndexesParameters()); + } + + String yql = query.getYqlQuery(null); + PrepareDataQuerySettings settings = withDefaultTimeout(new PrepareDataQuerySettings()); + try { + Map types = queryParamsCache.getIfPresent(query.originSQL()); + if (types == null) { + types = retryCtx.supplyResult(session -> session.prepareDataQuery(yql, settings)) + .join() + .getValue() + .types(); + queryParamsCache.put(query.originSQL(), types); + } + + boolean requireBatch = mode == YdbPrepareMode.DATA_QUERY_BATCH; + if (requireBatch || (mode == YdbPrepareMode.AUTO && queryOptions.isDetectBatchQueries())) { + BatchedParams params = BatchedParams.tryCreateBatched(types); + if (params != null) { + return params; + } + + if (requireBatch) { + throw new SQLDataException(YdbConst.STATEMENT_IS_NOT_A_BATCH + query.originSQL()); + } + } + return new PreparedParams(types); + } catch (UnexpectedResultException ex) { + throw ExceptionFactory.createException("Cannot prepare data query: " + ex.getMessage(), ex); + } + } } 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 d0784ba..3d27988 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/context/YdbExecutor.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/context/YdbExecutor.java @@ -16,8 +16,8 @@ import tech.ydb.core.Issue; import tech.ydb.core.Result; import tech.ydb.core.Status; -import tech.ydb.jdbc.exception.YdbExecutionException; -import tech.ydb.jdbc.exception.YdbStatusException; +import tech.ydb.core.UnexpectedResultException; +import tech.ydb.jdbc.exception.ExceptionFactory; import tech.ydb.table.Session; /** @@ -62,7 +62,7 @@ public Session createSession(YdbContext ctx) throws SQLException { public void execute(String msg, Supplier> runnableSupplier) throws SQLException { if (!isDebug) { - simpleExecute(runnableSupplier); + simpleExecute(msg, runnableSupplier); return; } @@ -70,7 +70,7 @@ public void execute(String msg, Supplier> runnableSupp Stopwatch sw = Stopwatch.createStarted(); try { - simpleExecute(runnableSupplier); + 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() }); @@ -80,14 +80,14 @@ public void execute(String msg, Supplier> runnableSupp public T call(String msg, Supplier>> callSupplier) throws SQLException { if (!isDebug) { - return simpleCall(callSupplier); + return simpleCall(msg, callSupplier); } logger.finest(msg); Stopwatch sw = Stopwatch.createStarted(); try { - T value = simpleCall(callSupplier); + T value = simpleCall(msg, callSupplier); logger.log(Level.FINEST, "[{0}] OK ", sw.stop()); return value; } catch (SQLException | RuntimeException ex) { @@ -96,29 +96,22 @@ public T call(String msg, Supplier>> callSupplie } } - private T simpleCall(Supplier>> supplier) throws SQLException { + private T simpleCall(String msg, Supplier>> supplier) throws SQLException { try { Result result = supplier.get().join(); - validate(result.getStatus().toString(), result.getStatus()); + issues.addAll(Arrays.asList(result.getStatus().getIssues())); return result.getValue(); - } catch (RuntimeException ex) { - throw new YdbExecutionException(ex.getMessage(), ex); + } catch (UnexpectedResultException ex) { + throw ExceptionFactory.createException("Cannot call '" + msg + "' with " + ex.getStatus(), ex); } } - private void simpleExecute(Supplier> supplier) throws SQLException { - try { - Status status = supplier.get().join(); - validate(status.toString(), status); - } catch (RuntimeException ex) { - throw new YdbExecutionException(ex.getMessage(), ex); - } - } - - private void validate(String message, Status status) 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 YdbStatusException.newException(message, status); + throw ExceptionFactory.createException("Cannot execute '" + msg + "' with " + status, + new UnexpectedResultException("Unexpected status", status)); } } } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/exception/ExceptionFactory.java b/jdbc/src/main/java/tech/ydb/jdbc/exception/ExceptionFactory.java new file mode 100644 index 0000000..0d1ed73 --- /dev/null +++ b/jdbc/src/main/java/tech/ydb/jdbc/exception/ExceptionFactory.java @@ -0,0 +1,36 @@ +package tech.ydb.jdbc.exception; + +import java.sql.SQLException; + +import tech.ydb.core.StatusCode; +import tech.ydb.core.UnexpectedResultException; + +/** + * + * @author Aleksandr Gorshenin + */ +public class ExceptionFactory { + static String getSQLState(StatusCode status) { + // TODO: Add SQLSTATE message with order with https://en.wikipedia.org/wiki/SQLSTATE + return null; + } + + static int getVendorCode(StatusCode code) { + return code.getCode(); + } + + public static SQLException createException(String message, UnexpectedResultException cause) { + StatusCode code = cause.getStatus().getCode(); + String sqlState = getSQLState(code); + int vendorCode = getVendorCode(code); + + if (code.isRetryable(false)) { + return new YdbRetryableException(message, sqlState, vendorCode, cause); + } + if (code.isRetryable(true)) { + return new YdbConditionallyRetryableException(message, sqlState, vendorCode, cause); + } + + return new YdbSQLException(message, sqlState, vendorCode, cause); + } +} 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 599e60d..2a45d0a 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbConditionallyRetryableException.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbConditionallyRetryableException.java @@ -1,12 +1,20 @@ package tech.ydb.jdbc.exception; +import java.sql.SQLTransientException; + import tech.ydb.core.Status; +import tech.ydb.core.UnexpectedResultException; + +public class YdbConditionallyRetryableException extends SQLTransientException { + private static final long serialVersionUID = 2155728765762467203L; + private final Status status; -// Treat this as non retryable exception by nature, i.e. need to handle in consciously -public class YdbConditionallyRetryableException extends YdbNonRetryableException { - private static final long serialVersionUID = -2371144941971339449L; + YdbConditionallyRetryableException(String message, String sqlState, int code, UnexpectedResultException cause) { + super(message, sqlState, code, cause); + this.status = cause.getStatus(); + } - YdbConditionallyRetryableException(String message, String sqlState, Status status) { - super(message, sqlState, status); + public Status getStatus() { + return status; } } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbConfigurationException.java b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbConfigurationException.java deleted file mode 100644 index 2812b6f..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbConfigurationException.java +++ /dev/null @@ -1,15 +0,0 @@ -package tech.ydb.jdbc.exception; - -import java.sql.SQLException; - -public class YdbConfigurationException extends SQLException { - private static final long serialVersionUID = -9023124863392765984L; - - public YdbConfigurationException(String message) { - super(message); - } - - public YdbConfigurationException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbExecutionException.java b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbExecutionException.java deleted file mode 100644 index 9da7bad..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbExecutionException.java +++ /dev/null @@ -1,19 +0,0 @@ -package tech.ydb.jdbc.exception; - -import java.sql.SQLException; - -public class YdbExecutionException extends SQLException { - private static final long serialVersionUID = -9189855688894485591L; - - public YdbExecutionException(String reason) { - super(reason); - } - - public YdbExecutionException(String reason, Throwable cause) { - super(reason, cause); - } - - public YdbExecutionException(String reason, String SQLState, int vendorCode) { - super(reason, SQLState, vendorCode); - } -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbNonRetryableException.java b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbNonRetryableException.java deleted file mode 100644 index 8044984..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbNonRetryableException.java +++ /dev/null @@ -1,11 +0,0 @@ -package tech.ydb.jdbc.exception; - -import tech.ydb.core.Status; - -public class YdbNonRetryableException extends YdbStatusException { - private static final long serialVersionUID = 687247673341671225L; - - YdbNonRetryableException(String message, String sqlState, Status status) { - super(message, sqlState, status); - } -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbResultTruncatedException.java b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbResultTruncatedException.java deleted file mode 100644 index 092192c..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbResultTruncatedException.java +++ /dev/null @@ -1,9 +0,0 @@ -package tech.ydb.jdbc.exception; - -public class YdbResultTruncatedException extends YdbExecutionException { - private static final long serialVersionUID = -1887300249465409232L; - - public YdbResultTruncatedException(String reason) { - super(reason); - } -} 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 ed1b922..ae1c384 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbRetryableException.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbRetryableException.java @@ -1,11 +1,20 @@ package tech.ydb.jdbc.exception; +import java.sql.SQLRecoverableException; + import tech.ydb.core.Status; +import tech.ydb.core.UnexpectedResultException; + +public class YdbRetryableException extends SQLRecoverableException { + private static final long serialVersionUID = -7171306648623023924L; + private final Status status; -public class YdbRetryableException extends YdbStatusException { - private static final long serialVersionUID = 2082287790625648960L; + YdbRetryableException(String message, String sqlState, int code, UnexpectedResultException cause) { + super(message, sqlState, code, cause); + this.status = cause.getStatus(); + } - YdbRetryableException(String message, String sqlState, Status status) { - super(message, sqlState, status); + public Status getStatus() { + return status; } } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbRuntimeException.java b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbRuntimeException.java deleted file mode 100644 index 0feb754..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbRuntimeException.java +++ /dev/null @@ -1,13 +0,0 @@ -package tech.ydb.jdbc.exception; - -public class YdbRuntimeException extends RuntimeException { - private static final long serialVersionUID = 7195253335276429670L; - - public YdbRuntimeException(String message) { - super(message); - } - - public YdbRuntimeException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbSQLException.java b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbSQLException.java new file mode 100644 index 0000000..c8af441 --- /dev/null +++ b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbSQLException.java @@ -0,0 +1,21 @@ +package tech.ydb.jdbc.exception; + +import java.sql.SQLException; + +import tech.ydb.core.Status; +import tech.ydb.core.UnexpectedResultException; + +public class YdbSQLException extends SQLException { + private static final long serialVersionUID = 6204553083196091739L; + + private final Status status; + + YdbSQLException(String message, String sqlState, int code, UnexpectedResultException cause) { + super(message, sqlState, code, cause); + this.status = cause.getStatus(); + } + + public Status getStatus() { + return status; + } +} diff --git a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbStatusException.java b/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbStatusException.java deleted file mode 100644 index 4edb87b..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/exception/YdbStatusException.java +++ /dev/null @@ -1,40 +0,0 @@ -package tech.ydb.jdbc.exception; - -import tech.ydb.core.Status; -import tech.ydb.core.StatusCode; - -public class YdbStatusException extends YdbExecutionException { - private static final long serialVersionUID = -8082086858749679589L; - - private final Status status; - - protected YdbStatusException(String message, String state, Status status) { - super(message, state, status.getCode().getCode()); - this.status = status; - } - - public Status getStatus() { - return status; - } - - public static YdbStatusException newException(String message, Status status) { - if (status.getCode().isRetryable(false)) { - String sqlState = "Retryable[" + status.toString() + "]"; - return new YdbRetryableException(message, sqlState, status); - } - - if (status.getCode().isRetryable(true)) { - String sqlState = "ConditionallyRetryable[" + status.toString() + "]"; - return new YdbConditionallyRetryableException(message, sqlState, status); - } - - String sqlState = "NonRetryable[" + status.toString() + "]"; - return new YdbNonRetryableException(message, sqlState, status); - } - - public static YdbStatusException newBadRequest(String message) { - Status status = Status.of(StatusCode.BAD_REQUEST); - String sqlState = "NonRetryable[" + status.toString() + "]"; - return new YdbNonRetryableException(message, sqlState, 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 3ed75ad..aac17b3 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/BaseYdbStatement.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/impl/BaseYdbStatement.java @@ -18,9 +18,8 @@ import tech.ydb.jdbc.YdbStatement; import tech.ydb.jdbc.common.FixedResultSetFactory; import tech.ydb.jdbc.context.YdbExecutor; -import tech.ydb.jdbc.exception.YdbResultTruncatedException; +import tech.ydb.jdbc.query.YdbExpression; import tech.ydb.jdbc.query.YdbQuery; -import tech.ydb.jdbc.query.YdbQueryOptions; import tech.ydb.jdbc.settings.YdbOperationProperties; import tech.ydb.table.query.DataQueryResult; import tech.ydb.table.query.ExplainDataQueryResult; @@ -28,15 +27,16 @@ import tech.ydb.table.result.ResultSetReader; import tech.ydb.table.settings.ExecuteDataQuerySettings; -import static tech.ydb.jdbc.YdbConst.NAMED_CURSORS_UNSUPPORTED; -import static tech.ydb.jdbc.YdbConst.RESULT_SET_MODE_UNSUPPORTED; - /** * * @author Aleksandr Gorshenin */ public abstract class BaseYdbStatement implements YdbStatement { - protected static final ResultState EMPTY_STATE = new ResultState(); + private static final ResultState EMPTY_STATE = new ResultState(null); + private static final YdbResult NO_UPDATED = new YdbResult(0); + + // 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() .addTextColumn(YdbConst.EXPLAIN_COLUMN_AST) @@ -45,7 +45,6 @@ public abstract class BaseYdbStatement implements YdbStatement { private final YdbConnection connection; private final YdbExecutor executor; - private final YdbQueryOptions queryOptions; private final int resultSetType; private final int maxRows; private final boolean failOnTruncatedResult; @@ -59,7 +58,6 @@ public BaseYdbStatement(Logger logger, YdbConnection connection, int resultSetTy this.connection = Objects.requireNonNull(connection); this.executor = new YdbExecutor(logger); this.resultSetType = resultSetType; - this.queryOptions = connection.getCtx().getQueryOptions(); this.isPoolable = isPoolable; YdbOperationProperties props = connection.getCtx().getOperationProperties(); @@ -73,10 +71,6 @@ public YdbConnection getConnection() { return connection; } - public YdbQuery createYdbQuery(String sql) throws SQLException { - return YdbQuery.from(queryOptions, sql); - } - @Override public void close() { isClosed = true; @@ -169,16 +163,23 @@ protected void cleanState() throws SQLException { state = EMPTY_STATE; } - protected boolean updateState(ResultState results) { - state = results; - return results.hasResultSets(); + protected boolean updateState(List results) { + state = results == null ? EMPTY_STATE : new ResultState(results); + return state.hasResultSets(); } - protected void executeSchemeQuery(YdbQuery query) throws SQLException { + protected List executeSchemeQuery(YdbQuery query) throws SQLException { connection.executeSchemeQuery(query, executor); + + int expressionsCount = query.getExpressions().isEmpty() ? 1 : query.getExpressions().size(); + List results = new ArrayList<>(); + for (int i = 0; i < expressionsCount; i++) { + results.add(NO_UPDATED); + } + return results; } - protected ResultState executeExplainQuery(YdbQuery query) throws SQLException { + protected List executeExplainQuery(YdbQuery query) throws SQLException { ExplainDataQueryResult explainDataQuery = connection.executeExplainQuery(query, executor); ResultSetReader result = EXPLAIN_RS_FACTORY.createResultSet() @@ -188,17 +189,15 @@ protected ResultState executeExplainQuery(YdbQuery query) throws SQLException { .build() .build(); - List list = Collections.singletonList(new YdbResultSetImpl(this, result)); - return new ResultState(list); + return Collections.singletonList(new YdbResult(new YdbResultSetImpl(this, result))); } - protected ResultState executeScanQuery(YdbQuery query, Params params) throws SQLException { + protected List executeScanQuery(YdbQuery query, Params params) throws SQLException { ResultSetReader result = connection.executeScanQuery(query, executor, params); - List list = Collections.singletonList(new YdbResultSetImpl(this, result)); - return new ResultState(list); + return Collections.singletonList(new YdbResult(new YdbResultSetImpl(this, result))); } - protected ResultState executeDataQuery(YdbQuery query, Params params) throws SQLException { + protected List executeDataQuery(YdbQuery query, Params params) throws SQLException { int timeout = getQueryTimeout(); ExecuteDataQuerySettings settings = new ExecuteDataQuerySettings() .setOperationTimeout(Duration.ofSeconds(timeout)) @@ -208,23 +207,47 @@ protected ResultState executeDataQuery(YdbQuery query, Params params) throws SQL } DataQueryResult result = connection.executeDataQuery(query, executor, settings, params); - List list = new ArrayList<>(); - for (int idx = 0; idx < result.getResultSetCount(); idx += 1) { + + List results = new ArrayList<>(); + int idx = 0; + for (YdbExpression exp: query.getExpressions()) { + if (exp.isDDL()) { + results.add(NO_UPDATED); + continue; + } + if (!exp.isSelect()) { + results.add(HAS_UPDATED); + continue; + } + + if (idx < result.getResultSetCount()) { + ResultSetReader rs = result.getResultSet(idx); + if (failOnTruncatedResult && rs.isTruncated()) { + String msg = String.format(YdbConst.RESULT_IS_TRUNCATED, idx, rs.getRowCount()); + throw new SQLException(msg); + } + results.add(new YdbResult(new YdbResultSetImpl(this, rs))); + idx++; + } + } + + while (idx < result.getResultSetCount()) { ResultSetReader rs = result.getResultSet(idx); if (failOnTruncatedResult && rs.isTruncated()) { String msg = String.format(YdbConst.RESULT_IS_TRUNCATED, idx, rs.getRowCount()); - throw new YdbResultTruncatedException(msg); + throw new SQLException(msg); } - list.add(new YdbResultSetImpl(this, rs)); + results.add(new YdbResult(new YdbResultSetImpl(this, rs))); + idx++; } - return new ResultState(list); + return results; } // UNSUPPORTED @Override public void setCursorName(String name) throws SQLFeatureNotSupportedException { - throw new SQLFeatureNotSupportedException(NAMED_CURSORS_UNSUPPORTED); + throw new SQLFeatureNotSupportedException(YdbConst.NAMED_CURSORS_UNSUPPORTED); } @Override @@ -300,47 +323,62 @@ public int getResultSetConcurrency() { return ResultSet.CONCUR_READ_ONLY; } - protected static class ResultState { - private final List results; - private int resultSetIndex; + protected static class YdbResult { + private final int updateCount; + private final YdbResultSet resultSet; - private ResultState() { - results = null; - resultSetIndex = -1; + YdbResult(int updateCount) { + this.updateCount = updateCount; + this.resultSet = null; } - private ResultState(List list) { - results = list; - resultSetIndex = 0; + YdbResult(YdbResultSet resultSet) { + this.updateCount = -1; + this.resultSet = resultSet; } + } + + private static class ResultState { + private final List results; + private int resultIndex; - public boolean hasResultSets() { - return results != null && !results.isEmpty(); + ResultState(List results) { + this.results = results; + this.resultIndex = 0; } - // TODO: YDB doesn't return the count of affected rows, so we use little hach to return always 1 - public int getUpdateCount() { - return (results != null && results.isEmpty() && resultSetIndex == 0) ? 1 : -1; + boolean hasResultSets() { + if (results == null || resultIndex >= results.size()) { + return false; + } + + return results.get(resultIndex).resultSet != null; } - public YdbResultSet getCurrentResultSet() throws SQLException { - return getResultSet(resultSetIndex); + int getUpdateCount() { + if (results == null || resultIndex >= results.size()) { + return -1; + } + + return results.get(resultIndex).updateCount; } - public YdbResultSet getResultSet(int index) throws SQLException { - if (results == null) { + YdbResultSet getCurrentResultSet() { + if (results == null || resultIndex >= results.size()) { return null; } - if (index < 0 || index >= results.size()) { + return results.get(resultIndex).resultSet; + } + + YdbResultSet getResultSet(int index) { + if (results == null || index < 0 || index >= results.size()) { return null; } - - return results.get(index); + return results.get(index).resultSet; } - public boolean getMoreResults(int current) throws SQLException { - if (results == null || results.isEmpty()) { - resultSetIndex = -1; // reset updateCount + boolean getMoreResults(int current) throws SQLException { + if (results == null || resultIndex >= results.size()) { return false; } @@ -348,19 +386,19 @@ public boolean getMoreResults(int current) throws SQLException { case Statement.KEEP_CURRENT_RESULT: break; case Statement.CLOSE_CURRENT_RESULT: - results.get(resultSetIndex).close(); + results.get(resultIndex).resultSet.close(); break; case Statement.CLOSE_ALL_RESULTS: - for (int idx = 0; idx <= resultSetIndex; idx += 1) { - results.get(idx).close(); + for (int idx = 0; idx <= resultIndex; idx += 1) { + results.get(idx).resultSet.close(); } break; default: - throw new SQLException(RESULT_SET_MODE_UNSUPPORTED + current); + throw new SQLException(YdbConst.RESULT_SET_MODE_UNSUPPORTED + current); } - resultSetIndex += 1; - return resultSetIndex >= 0 && resultSetIndex < results.size(); + resultIndex += 1; + return hasResultSets(); } } } 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 4e735c6..0bcefff 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbConnectionImpl.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbConnectionImpl.java @@ -1,5 +1,6 @@ package tech.ydb.jdbc.impl; + import java.sql.Array; import java.sql.Blob; import java.sql.CallableStatement; @@ -36,13 +37,12 @@ import tech.ydb.jdbc.context.YdbContext; import tech.ydb.jdbc.context.YdbExecutor; import tech.ydb.jdbc.context.YdbTxState; -import tech.ydb.jdbc.exception.YdbExecutionException; +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.DataQuery; import tech.ydb.table.query.DataQueryResult; import tech.ydb.table.query.ExplainDataQueryResult; import tech.ydb.table.query.Params; @@ -54,8 +54,6 @@ import tech.ydb.table.settings.ExecuteSchemeQuerySettings; import tech.ydb.table.settings.ExplainDataQuerySettings; import tech.ydb.table.settings.KeepAliveSessionSettings; -import tech.ydb.table.settings.PrepareDataQuerySettings; -import tech.ydb.table.settings.RequestSettings; import tech.ydb.table.settings.RollbackTxSettings; public class YdbConnectionImpl implements YdbConnection { @@ -82,15 +80,6 @@ public YdbConnectionImpl(YdbContext context) throws SQLException { this.ctx.register(); } - > T withDefaultTimeout(T settings) { - Duration operation = ctx.getOperationProperties().getDeadlineTimeout(); - if (!operation.isZero() && !operation.isNegative()) { - settings.setOperationTimeout(operation); - settings.setTimeout(operation.plusSeconds(1)); - } - return settings; - } - @Override public YdbStatement createStatement() throws SQLException { return createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY, @@ -106,7 +95,7 @@ public YdbPreparedStatement prepareStatement(String sql) throws SQLException { @Override public String nativeSQL(String sql) { try { - return YdbQuery.from(ctx.getQueryOptions(), sql).getYqlQuery(null); + return ctx.parseYdbQuery(sql).getYqlQuery(null); } catch (SQLException ex) { return ex.getMessage(); } @@ -150,7 +139,7 @@ public void commit() throws SQLException { } Session session = state.getSession(ctx, executor); - CommitTxSettings settings = withDefaultTimeout(new CommitTxSettings()); + CommitTxSettings settings = ctx.withDefaultTimeout(new CommitTxSettings()); try { executor.clearWarnings(); @@ -172,7 +161,7 @@ public void rollback() throws SQLException { } Session session = state.getSession(ctx, executor); - RollbackTxSettings settings = withDefaultTimeout(new RollbackTxSettings()); + RollbackTxSettings settings = ctx.withDefaultTimeout(new RollbackTxSettings()); try { executor.clearWarnings(); @@ -270,13 +259,13 @@ public void executeSchemeQuery(YdbQuery query, YdbExecutor executor) throws SQLE break; case ERROR: default: - throw new YdbExecutionException(YdbConst.SCHEME_QUERY_INSIDE_TRANSACTION); + throw new SQLException(YdbConst.SCHEME_QUERY_INSIDE_TRANSACTION); } } // Scheme query does not affect transactions or result sets - ExecuteSchemeQuerySettings settings = withDefaultTimeout(new ExecuteSchemeQuerySettings()); + ExecuteSchemeQuerySettings settings = ctx.withDefaultTimeout(new ExecuteSchemeQuerySettings()); final String yql = query.getYqlQuery(null); try (Session session = executor.createSession(ctx)) { @@ -317,8 +306,7 @@ public ResultSetReader executeScanQuery(YdbQuery query, YdbExecutor executor, Pa break; case ERROR: default: - throw new YdbExecutionException(YdbConst.SCAN_QUERY_INSIDE_TRANSACTION); - + throw new SQLException(YdbConst.SCAN_QUERY_INSIDE_TRANSACTION); } } @@ -341,22 +329,13 @@ public ExplainDataQueryResult executeExplainQuery(YdbQuery query, YdbExecutor ex ensureOpened(); String yql = query.getYqlQuery(null); - ExplainDataQuerySettings settings = withDefaultTimeout(new ExplainDataQuerySettings()); + 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)); } } - DataQuery prepareDataQuery(YdbQuery query) throws SQLException { - String yql = query.getYqlQuery(null); - PrepareDataQuerySettings settings = withDefaultTimeout(new PrepareDataQuerySettings()); - try (Session session = executor.createSession(ctx)) { - String msg = "Preparing Query >>\n" + yql; - return executor.call(msg, () -> session.prepareDataQuery(yql, settings)); - } - } - @Override public YdbStatement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { return createStatement(resultSetType, resultSetConcurrency, ResultSet.HOLD_CURSORS_OVER_COMMIT); @@ -426,13 +405,13 @@ private YdbPreparedStatement prepareStatement(String sql, int resultSetType, Ydb ensureOpened(); executor.clearWarnings(); - YdbQuery query = YdbQuery.from(ctx.getQueryOptions(), sql); + YdbQuery query = ctx.findOrParseYdbQuery(sql); if (query.type() != QueryType.DATA_QUERY && query.type() != QueryType.SCAN_QUERY) { throw new SQLException(YdbConst.UNSUPPORTED_QUERY_TYPE_IN_PS + query.type()); } - YdbJdbcParams params = YdbJdbcParams.create(this, query, mode); + JdbcParams params = ctx.findOrCreateJdbcParams(query, mode); return new YdbPreparedStatementImpl(this, query, params, resultSetType); } 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 cb0474b..099218f 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbDatabaseMetaDataImpl.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbDatabaseMetaDataImpl.java @@ -21,6 +21,7 @@ import com.google.common.base.Strings; +import tech.ydb.core.Result; import tech.ydb.core.StatusCode; import tech.ydb.jdbc.YdbConnection; import tech.ydb.jdbc.YdbConst; @@ -30,11 +31,9 @@ import tech.ydb.jdbc.common.FixedResultSetFactory; import tech.ydb.jdbc.common.YdbFunctions; import tech.ydb.jdbc.context.YdbExecutor; -import tech.ydb.jdbc.exception.YdbStatusException; import tech.ydb.proto.scheme.SchemeOperationProtos; import tech.ydb.scheme.SchemeClient; import tech.ydb.scheme.description.ListDirectoryResult; -import tech.ydb.table.Session; import tech.ydb.table.description.TableColumn; import tech.ydb.table.description.TableDescription; import tech.ydb.table.description.TableIndex; @@ -1345,26 +1344,24 @@ private List tables(String databasePrefix, String path, Predicate session.describeTable(databaseWithSuffix + table, settings) - ); - } catch (YdbStatusException ex) { - if (ex.getStatus().getCode() != StatusCode.SCHEME_ERROR) { // ignore scheme errors like path not found - throw ex; - } - LOGGER.log(Level.WARNING, "Cannot describe table {0} -> {1}", - new Object[]{ table, ex.getMessage() } - ); + String databaseWithSuffix = withSuffix(connection.getCtx().getDatabase()); - return null; - } - } + return executor.call("Describe table " + table, () -> connection.getCtx() + .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() }); + return Result.success(null); + } + return result; + }) + ); } private ResultSet emptyResultSet(FixedResultSetFactory factory) { diff --git a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbJdbcParams.java b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbJdbcParams.java deleted file mode 100644 index 79264e7..0000000 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbJdbcParams.java +++ /dev/null @@ -1,70 +0,0 @@ -package tech.ydb.jdbc.impl; - -import java.sql.SQLException; -import java.util.List; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import tech.ydb.jdbc.YdbConst; -import tech.ydb.jdbc.YdbPrepareMode; -import tech.ydb.jdbc.common.TypeDescription; -import tech.ydb.jdbc.exception.YdbExecutionException; -import tech.ydb.jdbc.impl.params.BatchedParams; -import tech.ydb.jdbc.impl.params.InMemoryParams; -import tech.ydb.jdbc.impl.params.PreparedParams; -import tech.ydb.jdbc.query.YdbQuery; -import tech.ydb.jdbc.query.YdbQueryOptions; -import tech.ydb.table.query.DataQuery; -import tech.ydb.table.query.Params; -import tech.ydb.table.values.Type; - -/** - * - * @author Aleksandr Gorshenin - */ -public interface YdbJdbcParams { - void clearParameters(); - - void setParam(int index, @Nullable Object obj, @Nonnull Type type) throws SQLException; - void setParam(String name, @Nullable Object obj, @Nonnull Type type) throws SQLException; - - String getNameByIndex(int index) throws SQLException; - - void addBatch() throws SQLException; - void clearBatch(); - int batchSize(); - - int parametersCount(); - - TypeDescription getDescription(int index) throws SQLException; - - List getBatchParams() throws SQLException; - Params getCurrentParams() throws SQLException; - - static YdbJdbcParams create(YdbConnectionImpl connection, YdbQuery query, YdbPrepareMode mode) throws SQLException { - YdbQueryOptions opts = connection.getCtx().getQueryOptions(); - - if (query.hasIndexesParameters() - || mode == YdbPrepareMode.IN_MEMORY - || !opts.iPrepareDataQueries()) { - return new InMemoryParams(query.getIndexesParameters()); - } - - DataQuery prepared = connection.prepareDataQuery(query); - - boolean requireBatch = mode == YdbPrepareMode.DATA_QUERY_BATCH; - if (requireBatch || (mode == YdbPrepareMode.AUTO && opts.isDetectBatchQueries())) { - BatchedParams params = BatchedParams.tryCreateBatched(prepared.types()); - if (params != null) { - return params; - } - - if (requireBatch) { - throw new YdbExecutionException(YdbConst.STATEMENT_IS_NOT_A_BATCH + query.originSQL()); - } - } - - return new PreparedParams(prepared.types()); - } -} 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 3a25a29..5fd57b2 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbParameterMetaDataImpl.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbParameterMetaDataImpl.java @@ -1,6 +1,7 @@ package tech.ydb.jdbc.impl; + import java.sql.ParameterMetaData; import java.sql.SQLException; import java.sql.Types; @@ -10,10 +11,12 @@ import static tech.ydb.jdbc.YdbConst.CANNOT_UNWRAP_TO; +import tech.ydb.jdbc.query.JdbcParams; + public class YdbParameterMetaDataImpl implements YdbParameterMetaData { - private final YdbJdbcParams params; + private final JdbcParams params; - public YdbParameterMetaDataImpl(YdbJdbcParams params) { + public YdbParameterMetaDataImpl(JdbcParams params) { this.params = params; } 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 c94bbd2..806ac0e 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbPreparedStatementImpl.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbPreparedStatementImpl.java @@ -1,5 +1,6 @@ package tech.ydb.jdbc.impl; + import java.io.InputStream; import java.io.Reader; import java.math.BigDecimal; @@ -12,6 +13,7 @@ import java.sql.Ref; import java.sql.ResultSetMetaData; import java.sql.RowId; +import java.sql.SQLDataException; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.SQLXML; @@ -20,6 +22,7 @@ import java.sql.Types; import java.util.Arrays; import java.util.Calendar; +import java.util.List; import java.util.Objects; import java.util.logging.Logger; @@ -30,19 +33,19 @@ import tech.ydb.jdbc.YdbResultSet; import tech.ydb.jdbc.YdbTypes; import tech.ydb.jdbc.common.MappingSetters; -import tech.ydb.jdbc.exception.YdbExecutionException; 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()); private final YdbQuery query; - private final YdbJdbcParams params; + private final JdbcParams params; private final YdbTypes types; - public YdbPreparedStatementImpl(YdbConnection connection, YdbQuery query, YdbJdbcParams params, int resultSetType) { + public YdbPreparedStatementImpl(YdbConnection connection, YdbQuery query, JdbcParams params, int resultSetType) { super(LOGGER, connection, resultSetType, true); // is poolable by default this.query = Objects.requireNonNull(query); @@ -123,7 +126,7 @@ public boolean execute() throws SQLException { cleanState(); clearBatch(); - ResultState newState = EMPTY_STATE; + List newState = null; switch (query.type()) { case DATA_QUERY: newState = executeDataQuery(query, params.getCurrentParams()); @@ -142,7 +145,7 @@ public boolean execute() throws SQLException { @Override public YdbResultSet executeScanQuery() throws SQLException { cleanState(); - ResultState state = executeScanQuery(query, params.getCurrentParams()); + List state = executeScanQuery(query, params.getCurrentParams()); params.clearParameters(); updateState(state); return getResultSet(); @@ -151,7 +154,7 @@ public YdbResultSet executeScanQuery() throws SQLException { @Override public YdbResultSet executeExplainQuery() throws SQLException { cleanState(); - ResultState state = executeExplainQuery(query); + List state = executeExplainQuery(query); updateState(state); return getResultSet(); } @@ -193,7 +196,7 @@ public void setObject(int parameterIndex, Object value, Type type) throws SQLExc @Override public void setObject(String parameterName, Object x) throws SQLException { if (x == null) { - throw new YdbExecutionException(String.format(YdbConst.UNABLE_TO_SET_NULL_OBJECT)); + throw new SQLDataException(YdbConst.UNABLE_TO_SET_NULL_OBJECT); } params.setParam(parameterName, x, ydbType(x.getClass())); } @@ -201,7 +204,7 @@ public void setObject(String parameterName, Object x) throws SQLException { @Override public void setObject(int parameterIndex, Object x) throws SQLException { if (x == null) { - throw new YdbExecutionException(String.format(YdbConst.UNABLE_TO_SET_NULL_OBJECT)); + throw new SQLDataException(YdbConst.UNABLE_TO_SET_NULL_OBJECT); } params.setParam(parameterIndex, x, ydbType(x.getClass())); } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbStatementImpl.java b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbStatementImpl.java index d909b0f..158398d 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbStatementImpl.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbStatementImpl.java @@ -29,7 +29,7 @@ public void executeSchemeQuery(String sql) throws SQLException { cleanState(); clearBatch(); - YdbQuery query = createYdbQuery(sql); + YdbQuery query = getConnection().getCtx().parseYdbQuery(sql); executeSchemeQuery(query); } @@ -38,8 +38,8 @@ public YdbResultSet executeScanQuery(String sql) throws SQLException { cleanState(); clearBatch(); - YdbQuery query = createYdbQuery(sql); - ResultState results = executeScanQuery(query, Params.empty()); + YdbQuery query = getConnection().getCtx().parseYdbQuery(sql); + List results = executeScanQuery(query, Params.empty()); if (!updateState(results)) { throw new SQLException(YdbConst.QUERY_EXPECT_RESULT_SET); } @@ -51,8 +51,8 @@ public YdbResultSet executeExplainQuery(String sql) throws SQLException { cleanState(); clearBatch(); - YdbQuery query = createYdbQuery(sql); - ResultState newState = executeExplainQuery(query); + YdbQuery query = getConnection().getCtx().parseYdbQuery(sql); + List newState = executeExplainQuery(query); if (!updateState(newState)) { throw new SQLException(YdbConst.QUERY_EXPECT_RESULT_SET); } @@ -79,11 +79,11 @@ public int executeUpdate(String sql) throws SQLException { public boolean execute(String sql) throws SQLException { cleanState(); - YdbQuery query = createYdbQuery(sql); - ResultState newState = EMPTY_STATE; + YdbQuery query = getConnection().getCtx().parseYdbQuery(sql); + List newState = null; switch (query.type()) { case SCHEME_QUERY: - executeSchemeQuery(query); + newState = executeSchemeQuery(query); break; case DATA_QUERY: newState = executeDataQuery(query, Params.empty()); 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 7a8585d..440e4bf 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbTypesImpl.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbTypesImpl.java @@ -25,7 +25,6 @@ import tech.ydb.jdbc.YdbConst; import tech.ydb.jdbc.YdbTypes; -import tech.ydb.jdbc.exception.YdbRuntimeException; import tech.ydb.table.values.DecimalValue; import tech.ydb.table.values.PrimitiveType; import tech.ydb.table.values.Type; @@ -210,8 +209,7 @@ public int unwrapYdbJdbcType(int sqlType) { Integer value = sqlTypeByPrimitiveNumId.get(type); if (value == null) { - throw new YdbRuntimeException("Internal error. Unsupported YDB type: " + idType + - " as " + type); + throw new RuntimeException("Internal error. Unsupported YDB type: " + idType + " as " + type); } return value; } else { diff --git a/jdbc/src/main/java/tech/ydb/jdbc/query/JdbcParams.java b/jdbc/src/main/java/tech/ydb/jdbc/query/JdbcParams.java new file mode 100644 index 0000000..9b961a4 --- /dev/null +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/JdbcParams.java @@ -0,0 +1,35 @@ +package tech.ydb.jdbc.query; + +import java.sql.SQLException; +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import tech.ydb.jdbc.common.TypeDescription; +import tech.ydb.table.query.Params; +import tech.ydb.table.values.Type; + +/** + * + * @author Aleksandr Gorshenin + */ +public interface JdbcParams { + void clearParameters(); + + void setParam(int index, @Nullable Object obj, @Nonnull Type type) throws SQLException; + void setParam(String name, @Nullable Object obj, @Nonnull Type type) throws SQLException; + + String getNameByIndex(int index) throws SQLException; + + void addBatch() throws SQLException; + void clearBatch(); + int batchSize(); + + int parametersCount(); + + TypeDescription getDescription(int index) throws SQLException; + + List getBatchParams() throws SQLException; + Params getCurrentParams() throws SQLException; +} 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 4002c22..beeae22 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/query/JdbcQueryLexer.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/JdbcQueryLexer.java @@ -68,14 +68,19 @@ public static void buildQuery(YdbQueryBuilder builder, YdbQueryOptions options) } // Detect data query expression - starts with SELECT, UPDATE, INSERT, UPSERT, DELETE, REPLACE - if (parseSelectKeyword(chars, i) - || parseUpdateKeyword(chars, i) + if (parseSelectKeyword(chars, i)) { + builder.addExpression(QueryType.DATA_QUERY, YdbExpression.SELECT); + detectJdbcArgs = options.isDetectJdbcParameters(); + break; + } + + if (parseUpdateKeyword(chars, i) || parseInsertKeyword(chars, i) || parseUpsertKeyword(chars, i) || parseDeleteKeyword(chars, i) || parseReplaceKeyword(chars, i) ) { - builder.setQueryType(QueryType.DATA_QUERY); + builder.addExpression(QueryType.DATA_QUERY, YdbExpression.OTHER_DML); detectJdbcArgs = options.isDetectJdbcParameters(); break; } @@ -84,13 +89,13 @@ public static void buildQuery(YdbQueryBuilder builder, YdbQueryOptions options) if (parseAlterKeyword(chars, i) || parseCreateKeyword(chars, i) || parseDropKeyword(chars, i)) { - builder.setQueryType(QueryType.SCHEME_QUERY); + builder.addExpression(QueryType.SCHEME_QUERY, YdbExpression.DDL); break; } // Detect scan expression - starts with SCAN if (parseScanKeyword(chars, i)) { - builder.setQueryType(QueryType.SCAN_QUERY); + builder.addExpression(QueryType.SCAN_QUERY, YdbExpression.SELECT); detectJdbcArgs = options.isDetectJdbcParameters(); // Skip SCAN prefix @@ -101,7 +106,7 @@ public static void buildQuery(YdbQueryBuilder builder, YdbQueryOptions options) // Detect explain expression - starts with EXPLAIN if (parseExplainKeyword(chars, i)) { - builder.setQueryType(QueryType.EXPLAIN_QUERY); + builder.addExpression(QueryType.EXPLAIN_QUERY, YdbExpression.SELECT); detectJdbcArgs = options.isDetectJdbcParameters(); // Skip EXPLAIN prefix diff --git a/jdbc/src/main/java/tech/ydb/jdbc/query/YdbExpression.java b/jdbc/src/main/java/tech/ydb/jdbc/query/YdbExpression.java new file mode 100644 index 0000000..96c54ea --- /dev/null +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/YdbExpression.java @@ -0,0 +1,27 @@ +package tech.ydb.jdbc.query; + +/** + * + * @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); + + private final boolean isDDL; // CREATE, DROP and ALTER + private final boolean isSelect; + + private YdbExpression(boolean isDDL, boolean isSelect) { + this.isDDL = isDDL; + this.isSelect = isSelect; + } + + public boolean isDDL() { + return isDDL; + } + + public boolean isSelect() { + return 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 361ec70..888618d 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQuery.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQuery.java @@ -2,12 +2,12 @@ +import java.sql.SQLDataException; import java.sql.SQLException; import java.util.List; import java.util.Map; import tech.ydb.jdbc.YdbConst; -import tech.ydb.jdbc.exception.YdbStatusException; import tech.ydb.table.query.Params; import tech.ydb.table.values.Value; @@ -21,19 +21,25 @@ public class YdbQuery { private final String yqlQuery; private final QueryType type; private final List indexesArgsNames; + private final List expressions; - private YdbQuery(YdbQueryOptions opts, YdbQueryBuilder builder) { + YdbQuery(YdbQueryOptions opts, YdbQueryBuilder builder) { this.opts = opts; this.originSQL = builder.getOriginSQL(); this.yqlQuery = builder.buildYQL(); this.indexesArgsNames = builder.getIndexedArgs(); this.type = builder.getQueryType(); + this.expressions = builder.getExpressions(); } public String originSQL() { return originSQL; } + public List getExpressions() { + return expressions; + } + public boolean hasIndexesParameters() { return indexesArgsNames != null && !indexesArgsNames.isEmpty(); } @@ -51,7 +57,7 @@ public String getYqlQuery(Params params) throws SQLException { for (int idx = 0; idx < indexesArgsNames.size(); idx += 1) { String prm = indexesArgsNames.get(idx); if (!values.containsKey(prm)) { - throw YdbStatusException.newBadRequest(YdbConst.MISSING_VALUE_FOR_PARAMETER + prm); + throw new SQLDataException(YdbConst.MISSING_VALUE_FOR_PARAMETER + prm); } if (opts.isDeclareJdbcParameters()) { @@ -78,10 +84,4 @@ public String getYqlQuery(Params params) throws SQLException { public QueryType type() { return type; } - - public static YdbQuery from(YdbQueryOptions opts, String sql) throws SQLException { - YdbQueryBuilder builder = new YdbQueryBuilder(sql, opts.getForcedQueryType()); - JdbcQueryLexer.buildQuery(builder, opts); - return new YdbQuery(opts, builder); - } } 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 7100bdd..4e08600 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQueryBuilder.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/YdbQueryBuilder.java @@ -1,10 +1,11 @@ package tech.ydb.jdbc.query; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; import java.util.ArrayList; import java.util.List; import tech.ydb.jdbc.YdbConst; -import tech.ydb.jdbc.exception.YdbStatusException; /** * @@ -15,6 +16,7 @@ public class YdbQueryBuilder { private final StringBuilder query; private final List args = new ArrayList<>(); private final QueryType forcedType; + private final List expressions = new ArrayList<>(); private int argsCounter = 0; private QueryType currentType = null; @@ -36,13 +38,15 @@ public String createNextArgName() { } } - public void setQueryType(QueryType type) throws YdbStatusException { + public void addExpression(QueryType type, YdbExpression expression) throws SQLException { + expressions.add(expression); + if (forcedType != null) { return; } if (currentType != null && currentType != type) { - throw YdbStatusException.newBadRequest(YdbConst.MULTI_TYPES_IN_ONE_QUERY + currentType + ", " + type); + throw new SQLFeatureNotSupportedException(YdbConst.MULTI_TYPES_IN_ONE_QUERY + currentType + ", " + type); } this.currentType = type; } @@ -59,6 +63,10 @@ public QueryType getQueryType() { return QueryType.DATA_QUERY; } + public List getExpressions() { + return expressions; + } + public String getOriginSQL() { return origin; } @@ -82,4 +90,8 @@ public void append(char ch) { public void append(String string) { query.append(string); } + + public YdbQuery build(YdbQueryOptions opts) { + return new YdbQuery(opts, this); + } } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/impl/params/BatchedParams.java b/jdbc/src/main/java/tech/ydb/jdbc/query/params/BatchedParams.java similarity index 69% rename from jdbc/src/main/java/tech/ydb/jdbc/impl/params/BatchedParams.java rename to jdbc/src/main/java/tech/ydb/jdbc/query/params/BatchedParams.java index 1ec911c..2338697 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/params/BatchedParams.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/params/BatchedParams.java @@ -1,17 +1,20 @@ -package tech.ydb.jdbc.impl.params; +package tech.ydb.jdbc.query.params; +import java.sql.SQLDataException; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import tech.ydb.jdbc.YdbConst; import tech.ydb.jdbc.common.TypeDescription; -import tech.ydb.jdbc.exception.YdbStatusException; -import tech.ydb.jdbc.impl.YdbJdbcParams; import tech.ydb.table.query.Params; import tech.ydb.table.values.ListType; import tech.ydb.table.values.ListValue; @@ -19,12 +22,13 @@ import tech.ydb.table.values.StructValue; import tech.ydb.table.values.Type; import tech.ydb.table.values.Value; +import tech.ydb.jdbc.query.JdbcParams; /** * * @author Aleksandr Gorshenin */ -public class BatchedParams implements YdbJdbcParams { +public class BatchedParams implements JdbcParams { private final String batchParamName; private final Map paramsByName; private final ParamDescription[] params; @@ -37,14 +41,44 @@ private BatchedParams(String listName, StructType structType) { this.paramsByName = new HashMap<>(); this.params = new ParamDescription[structType.getMembersCount()]; + Map types = new HashMap<>(); for (int idx = 0; idx < structType.getMembersCount(); idx += 1) { - String paramName = structType.getMemberName(idx); - String displayName = YdbConst.VARIABLE_PARAMETER_PREFIX + paramName; - TypeDescription type = TypeDescription.of(structType.getMemberType(idx)); + types.put(structType.getMemberName(idx), structType.getMemberType(idx)); + } + + // Firstly put all indexed params (p1, p2, ..., pN) in correct places of paramNames + Set indexedNames = new HashSet<>(); + for (int idx = 0; idx < structType.getMembersCount(); idx += 1) { + String indexedName = YdbConst.INDEXED_PARAMETER_PREFIX + (1 + idx); + if (types.containsKey(indexedName)) { + String displayName = YdbConst.VARIABLE_PARAMETER_PREFIX + indexedName; + TypeDescription typeDesc = TypeDescription.of(types.get(indexedName)); + ParamDescription paramDesc = new ParamDescription(idx, indexedName, displayName, typeDesc); + + params[idx] = paramDesc; + paramsByName.put(indexedName, paramDesc); + indexedNames.add(indexedName); + } + } + + // Then put all others params in free places of paramNames in alphabetic order + Iterator sortedIter = new TreeSet<>(types.keySet()).iterator(); + for (int idx = 0; idx < params.length; idx += 1) { + if (params[idx] != null) { + continue; + } + + String param = sortedIter.next(); + while (indexedNames.contains(param)) { + param = sortedIter.next(); + } + + String displayName = YdbConst.VARIABLE_PARAMETER_PREFIX + param; + TypeDescription typeDesc = TypeDescription.of(types.get(param)); + ParamDescription paramDesc = new ParamDescription(idx, param, displayName, typeDesc); - ParamDescription param = new ParamDescription(idx, paramName, displayName, type); - params[idx] = param; - paramsByName.put(paramName, param); + params[idx] = paramDesc; + paramsByName.put(param, paramDesc); } } @@ -85,7 +119,7 @@ private StructValue validatedCurrentStruct() throws SQLException { continue; } - throw YdbStatusException.newBadRequest(YdbConst.MISSING_VALUE_FOR_PARAMETER + prm.displayName()); + throw new SQLDataException(YdbConst.MISSING_VALUE_FOR_PARAMETER + prm.displayName()); } return StructValue.of(currentValues); } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/impl/params/InMemoryParams.java b/jdbc/src/main/java/tech/ydb/jdbc/query/params/InMemoryParams.java similarity index 95% rename from jdbc/src/main/java/tech/ydb/jdbc/impl/params/InMemoryParams.java rename to jdbc/src/main/java/tech/ydb/jdbc/query/params/InMemoryParams.java index 003fa67..d8babfa 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/params/InMemoryParams.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/params/InMemoryParams.java @@ -1,4 +1,4 @@ -package tech.ydb.jdbc.impl.params; +package tech.ydb.jdbc.query.params; @@ -10,17 +10,17 @@ import tech.ydb.jdbc.YdbConst; import tech.ydb.jdbc.common.TypeDescription; -import tech.ydb.jdbc.impl.YdbJdbcParams; import tech.ydb.table.query.Params; import tech.ydb.table.values.Type; import tech.ydb.table.values.Value; +import tech.ydb.jdbc.query.JdbcParams; /** * * @author Aleksandr Gorshenin */ -public class InMemoryParams implements YdbJdbcParams { +public class InMemoryParams implements JdbcParams { private final String[] paramNames; private final Map> paramValues; private final List batchList; diff --git a/jdbc/src/main/java/tech/ydb/jdbc/impl/params/ParamDescription.java b/jdbc/src/main/java/tech/ydb/jdbc/query/params/ParamDescription.java similarity index 98% rename from jdbc/src/main/java/tech/ydb/jdbc/impl/params/ParamDescription.java rename to jdbc/src/main/java/tech/ydb/jdbc/query/params/ParamDescription.java index a8d41b2..41922fc 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/params/ParamDescription.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/params/ParamDescription.java @@ -1,4 +1,4 @@ -package tech.ydb.jdbc.impl.params; +package tech.ydb.jdbc.query.params; import java.sql.SQLException; diff --git a/jdbc/src/main/java/tech/ydb/jdbc/impl/params/PreparedParams.java b/jdbc/src/main/java/tech/ydb/jdbc/query/params/PreparedParams.java similarity index 86% rename from jdbc/src/main/java/tech/ydb/jdbc/impl/params/PreparedParams.java rename to jdbc/src/main/java/tech/ydb/jdbc/query/params/PreparedParams.java index d76239d..597be96 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/impl/params/PreparedParams.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/query/params/PreparedParams.java @@ -1,6 +1,7 @@ -package tech.ydb.jdbc.impl.params; +package tech.ydb.jdbc.query.params; +import java.sql.SQLDataException; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; @@ -13,16 +14,16 @@ import tech.ydb.jdbc.YdbConst; import tech.ydb.jdbc.common.TypeDescription; -import tech.ydb.jdbc.impl.YdbJdbcParams; import tech.ydb.table.query.Params; import tech.ydb.table.values.Type; import tech.ydb.table.values.Value; +import tech.ydb.jdbc.query.JdbcParams; /** * * @author Aleksandr Gorshenin */ -public class PreparedParams implements YdbJdbcParams { +public class PreparedParams implements JdbcParams { private final Map params; private final String[] paramNames; @@ -94,7 +95,7 @@ public void clearParameters() { } @Override - public void addBatch() { + public void addBatch() throws SQLException { batchList.add(getCurrentParams()); clearParameters(); } @@ -114,14 +115,23 @@ public int batchSize() { return batchList.size(); } + private Params validateParams(Map> values) throws SQLException { + for (String key: this.params.keySet()) { + if (!values.containsKey(key)) { + throw new SQLDataException(YdbConst.MISSING_VALUE_FOR_PARAMETER + key); + } + } + return Params.copyOf(values); + } + @Override public List getBatchParams() { return batchList; } @Override - public Params getCurrentParams() { - return Params.copyOf(paramValues); + public Params getCurrentParams() throws SQLException { + return validateParams(paramValues); } @Override diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/PropertyConverter.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/PropertyConverter.java index 44f5358..ccbe86d 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/PropertyConverter.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/settings/PropertyConverter.java @@ -5,7 +5,6 @@ import java.time.format.DateTimeParseException; import java.util.Locale; -import tech.ydb.jdbc.exception.YdbConfigurationException; interface PropertyConverter { T convert(String value) throws SQLException; @@ -31,7 +30,7 @@ static PropertyConverter durationValue() { try { return Duration.parse(targetValue); } catch (DateTimeParseException e) { - throw new YdbConfigurationException("Unable to parse value [" + value + "] -> [" + + throw new RuntimeException("Unable to parse value [" + value + "] -> [" + targetValue + "] as Duration: " + e.getMessage(), e); } }; @@ -42,7 +41,7 @@ static PropertyConverter integerValue() { try { return Integer.valueOf(value); } catch (NumberFormatException e) { - throw new YdbConfigurationException("Unable to parse value [" + value + "] as Integer: " + + throw new RuntimeException("Unable to parse value [" + value + "] as Integer: " + e.getMessage(), e); } }; diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbJdbcTools.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbJdbcTools.java index 80a9d4a..48515c5 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbJdbcTools.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbJdbcTools.java @@ -14,7 +14,6 @@ import tech.ydb.core.utils.URITools; import tech.ydb.jdbc.YdbConst; -import tech.ydb.jdbc.exception.YdbConfigurationException; /** @@ -31,7 +30,7 @@ public static boolean isYdb(String url) { 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 YdbConfigurationException(msg); + throw new SQLException(msg); } try { @@ -111,7 +110,7 @@ public static YdbProperties from(String jdbcURL, Properties origProperties) thro return new YdbProperties(ydbConnectionProps, ydbClientProperties, ydbOperationProperties); } catch (URISyntaxException | RuntimeException | UnsupportedEncodingException ex) { - throw new YdbConfigurationException(ex.getMessage(), ex); + throw new SQLException(ex.getMessage(), ex); } } @@ -130,16 +129,15 @@ public static YdbProperties from(String jdbcURL, Properties origProperties) thro String stringValue = (String) value; try { parsed = new ParsedProperty(stringValue, converter.convert(stringValue)); - } catch (SQLException e) { - throw new YdbConfigurationException("Unable to convert property " + - title + ": " + e.getMessage(), e); + } 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()); + throw new SQLException("Invalid object property " + title +", must be " + property.getType() + + ", got " + value.getClass()); } } } else { @@ -147,9 +145,8 @@ public static YdbProperties from(String jdbcURL, Properties origProperties) thro if (stringValue != null) { try { parsed = new ParsedProperty(stringValue, converter.convert(stringValue)); - } catch (SQLException e) { - throw new YdbConfigurationException("Unable to convert property " + - title + ": " + e.getMessage(), e); + } catch (RuntimeException e) { + throw new SQLException("Unable to convert property " + title + ": " + e.getMessage(), e); } } else { parsed = null; 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 87ce018..4c18787 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbOperationProperties.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbOperationProperties.java @@ -20,6 +20,7 @@ public class YdbOperationProperties { private final int transactionLevel; private final int maxRows; private final boolean cacheConnectionsInDriver; + private final int preparedStatementCacheSize; private final FakeTxMode scanQueryTxMode; private final FakeTxMode schemeQueryTxMode; @@ -38,6 +39,8 @@ public YdbOperationProperties(Map, ParsedProperty> param 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(); @@ -101,4 +104,8 @@ public int getMaxRows() { public boolean isCacheConnectionsInDriver() { return cacheConnectionsInDriver; } + + public int getPreparedStatementCacheSize() { + return preparedStatementCacheSize; + } } diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbOperationProperty.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbOperationProperty.java index 8aef66c..3d414a9 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbOperationProperty.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbOperationProperty.java @@ -87,6 +87,13 @@ public class YdbOperationProperty extends AbstractYdbProperty { 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", diff --git a/jdbc/src/test/java/tech/ydb/jdbc/YdbDriverExampleTest.java b/jdbc/src/test/java/tech/ydb/jdbc/YdbDriverExampleTest.java index 52e12e5..87d9a10 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/YdbDriverExampleTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/YdbDriverExampleTest.java @@ -137,8 +137,7 @@ public void testYdb() throws SQLException { public void testYdbNotNull() throws SQLException { try (Connection connection = DriverManager.getConnection(jdbcURL())) { try { - connection.createStatement() - .execute("drop table table_sample"); + connection.createStatement().execute("drop table table_sample"); } catch (SQLException e) { // } diff --git a/jdbc/src/test/java/tech/ydb/jdbc/YdbDriverProperitesTest.java b/jdbc/src/test/java/tech/ydb/jdbc/YdbDriverProperitesTest.java index ca7a8a3..6177a04 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/YdbDriverProperitesTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/YdbDriverProperitesTest.java @@ -29,7 +29,6 @@ import org.junit.jupiter.params.provider.MethodSource; import tech.ydb.auth.AuthProvider; -import tech.ydb.jdbc.exception.YdbConfigurationException; import tech.ydb.jdbc.impl.helper.ExceptionAssert; import tech.ydb.jdbc.settings.ParsedProperty; import tech.ydb.jdbc.settings.YdbClientProperty; @@ -51,7 +50,7 @@ public class YdbDriverProperitesTest { private YdbDriver driver; @BeforeAll - public static void beforeAll() throws YdbConfigurationException, IOException { + public static void beforeAll() throws SQLException, IOException { TOKEN_FILE = safeCreateFile(TOKEN_FROM_FILE); CERTIFICATE_FILE = safeCreateFile(CERTIFICATE_FROM_FILE); } @@ -222,7 +221,10 @@ public void getTokenAs(String token, String expectValue) throws SQLException { @MethodSource("unknownFiles") public void getTokenAsInvalid(String token, String expectException) { String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db?token=" + token; - ExceptionAssert.ydbConfiguration(expectException, () -> YdbJdbcTools.from(url, new Properties())); + ExceptionAssert.sqlException( + "Unable to convert property token: " + expectException, + () -> YdbJdbcTools.from(url, new Properties()) + ); } @ParameterizedTest(name = "[{index}] {1}") @@ -246,14 +248,17 @@ public void getCaCertificateAs(String certificate, String expectValue) throws SQ public void getCaCertificateAsInvalid(String certificate, String expectException) { String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db" + "?secureConnectionCertificate=" + certificate; - ExceptionAssert.ydbConfiguration(expectException, () -> YdbJdbcTools.from(url, new Properties())); + 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.ydbConfiguration("Unable to convert property " + param + + 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())); } @@ -262,7 +267,7 @@ public void invalidDuration(String param) { @MethodSource("invalidIntegerParams") public void invalidInteger(String param) { String url = "jdbc:ydb:ydb-demo.testhost.org:2135/test/db?" + param + "=1bc"; - ExceptionAssert.ydbConfiguration("Unable to convert property " + param + + ExceptionAssert.sqlException("Unable to convert property " + param + ": Unable to parse value [1bc] as Integer: For input string: \"1bc\"", () -> YdbJdbcTools.from(url, new Properties())); } @@ -323,6 +328,7 @@ static DriverPropertyInfo[] defaultPropertyInfo(@Nullable String localDatacenter 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"), @@ -363,6 +369,7 @@ static Properties customizedProperties() { properties.setProperty("transactionLevel", "4"); properties.setProperty("cacheConnectionsInDriver", "false"); + properties.setProperty("preparedStatementCacheQueries", "100"); properties.setProperty("disablePrepareDataQuery", "true"); properties.setProperty("disableAutoPreparedBatches", "true"); @@ -403,6 +410,7 @@ static DriverPropertyInfo[] customizedPropertyInfo() { YdbOperationProperty.TRANSACTION_LEVEL.toDriverPropertyInfo("4"), 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"), diff --git a/jdbc/src/test/java/tech/ydb/jdbc/YdbDriverStaticCredsTest.java b/jdbc/src/test/java/tech/ydb/jdbc/YdbDriverStaticCredsTest.java index 3531d14..1e22e41 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/YdbDriverStaticCredsTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/YdbDriverStaticCredsTest.java @@ -73,7 +73,7 @@ private void testConnection(ConnectionSupplier connectionSupplier) throws SQLExc } private void wrongConnection(ConnectionSupplier connectionSupplier) { - ExceptionAssert.ydbConfiguration("Cannot connect to YDB: gRPC error: (INTERNAL) get token exception: " + ExceptionAssert.sqlException("Cannot connect to YDB: gRPC error: (INTERNAL) get token exception: " + "Can't login, code: UNAUTHORIZED, issues: [#400020 Invalid password (S_FATAL)]", () -> testConnection(connectionSupplier)); } diff --git a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbConnectionImplTest.java b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbConnectionImplTest.java index ec96d5a..ea5cfeb 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbConnectionImplTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbConnectionImplTest.java @@ -249,7 +249,7 @@ public void invalidSQLCancelTransaction() throws SQLException { String txId = getTxId(jdbc.connection()); Assertions.assertNotNull(txId); - ExceptionAssert.ydbNonRetryable("Column reference 'x' (S_ERROR)", () -> statement.execute("select 2 + x")); + ExceptionAssert.ydbException("Column reference 'x' (S_ERROR)", () -> statement.execute("select 2 + x")); statement.execute(SELECT_2_2); Assertions.assertNotNull(getTxId(jdbc.connection())); @@ -480,7 +480,7 @@ public void commitInvalidTx() throws SQLException { statement.execute(SIMPLE_UPSERT); statement.execute(SIMPLE_UPSERT); - ExceptionAssert.ydbNonRetryable("Member not found: key2. Did you mean key?", + ExceptionAssert.ydbException("Member not found: key2. Did you mean key?", () -> statement.executeQuery(QUERIES.wrongSelectSQL())); Assertions.assertNull(getTxId(jdbc.connection())); @@ -502,7 +502,7 @@ public void rollbackInvalidTx() throws SQLException { statement.execute(SIMPLE_UPSERT); - ExceptionAssert.ydbNonRetryable("Member not found: key2. Did you mean key?", + ExceptionAssert.ydbException("Member not found: key2. Did you mean key?", () -> statement.executeQuery(QUERIES.wrongSelectSQL())); Assertions.assertNull(getTxId(jdbc.connection())); @@ -737,7 +737,6 @@ public void testAnsiLexerForIdea() throws SQLException { @DisplayName("Check unsupported by storage type {arguments}") @ParameterizedTest() @ValueSource(strings = { - "Uuid", "TzDate", "TzDatetime", "TzTimestamp", @@ -747,7 +746,7 @@ public void testUnsupportedByStorageTableTypes(String type) throws SQLException String sql = "create table " + tableName + " (key Int32, payload " + type + ", primary key(key))"; try (Statement statement = jdbc.connection().createStatement()) { - ExceptionAssert.ydbNonRetryable("is not supported by storage", () -> statement.execute(sql)); + ExceptionAssert.ydbException("is not supported by storage", () -> statement.execute(sql)); } } @@ -763,7 +762,7 @@ public void testUnsupportedComplexTypes(String type) throws SQLException { String sql = "create table " + tableName + " (key Int32, payload " + type + ", primary key(key))"; try (Statement statement = jdbc.connection().createStatement()) { - ExceptionAssert.ydbNonRetryable("Invalid type for column: payload.", + ExceptionAssert.ydbException("Invalid type for column: payload.", () -> statement.execute(sql)); } } 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 0346d12..3141948 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementImplTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementImplTest.java @@ -143,7 +143,6 @@ public void unknownColumns(SqlQueries.YqlQuery mode) throws SQLException { statement.setInt("key", 1); ExceptionAssert.sqlException("Parameter not found: column0", () -> statement.setObject("column0", "value")); - statement.execute(); } } @@ -268,8 +267,8 @@ public void addAndClearBatch(SqlQueries.YqlQuery mode) throws SQLException { 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.ydbNonRetryable("Missing value for parameter", () -> statement.execute()); - ExceptionAssert.ydbNonRetryable("Missing value for parameter", () -> statement.executeUpdate()); + ExceptionAssert.sqlDataException("Missing value for parameter", () -> statement.execute()); + ExceptionAssert.sqlDataException("Missing value for parameter", () -> statement.executeUpdate()); statement.executeBatch(); } @@ -303,7 +302,7 @@ public void executeQueryBatchWithScanRead() throws SQLException { } } - ExceptionAssert.ydbResultTruncated("Result #0 was truncated to 1000 rows", () -> { + ExceptionAssert.sqlException("Result #0 was truncated to 1000 rows", () -> { // Result is truncated (and we catch that) try (PreparedStatement select = prepareSimpleSelect("c_Text")) { select.executeQuery(); @@ -427,7 +426,7 @@ public void executeScanQueryAsUpdate() throws SQLException { statement.setInt("key", 1); statement.setString("c_Text", "value-1"); - ExceptionAssert.ydbNonRetryable("Scan query should have a single result set", + ExceptionAssert.ydbException("Scan query should have a single result set", statement::executeScanQuery); } } diff --git a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementTest.java b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementTest.java index fecd81c..f30c610 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementTest.java @@ -1,12 +1,22 @@ package tech.ydb.jdbc.impl; +import java.sql.Date; import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; - +import java.sql.Timestamp; +import java.sql.Types; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; + +import org.junit.Assert; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; @@ -31,6 +41,13 @@ public class YdbPreparedStatementTest { private static final SqlQueries TEST_TABLE = new SqlQueries("ydb_prepared_test"); + private static final Instant INSTANT = Instant.ofEpochMilli(1585932011123l); // Friday, April 3, 2020 4:40:11.123 PM + + // remove time part from instant in UTC + private static Instant calcStartDayUTC(Instant instant) { + return instant.atOffset(ZoneOffset.UTC).toLocalDate().atStartOfDay().toInstant(ZoneOffset.UTC); + } + @BeforeAll public static void createTable() throws SQLException { try (Statement statement = jdbc.connection().createStatement();) { @@ -46,17 +63,12 @@ public static void dropTable() throws SQLException { } } - @AfterEach - public void afterEach() throws SQLException { - if (jdbc.connection().isClosed()) { - return; - } - - try (Statement statement = jdbc.connection().createStatement()) { + @BeforeEach + public void beforaEach() throws SQLException { + try (Statement statement = jdbc.connection().createStatement();) { + // clean table statement.execute(TEST_TABLE.deleteAllSQL()); } - - jdbc.connection().close(); } @ParameterizedTest(name = "with {0}") @@ -104,7 +116,7 @@ public void executeWithMissingParameter(SqlQueries.JdbcQuery query) throws SQLEx try (PreparedStatement statement = jdbc.connection().prepareStatement(sql)) { statement.setInt(1, 1); - ExceptionAssert.ydbNonRetryable("Missing value for parameter", statement::execute); + ExceptionAssert.sqlDataException("Missing value for parameter", statement::execute); } } @@ -198,4 +210,128 @@ public void batchUpsertTest(SqlQueries.JdbcQuery query) throws SQLException { .noNextRows(); } }; + + private void fillRowValues(PreparedStatement statement, int id) throws SQLException { + statement.setInt(1, id); // id + + statement.setBoolean(2, id % 2 == 0); // c_Bool + + statement.setByte(3, (byte)(id + 1)); // c_Int8 + statement.setShort(4, (short)(id + 2)); // c_Int16 + statement.setInt(5, id + 3); // c_Int32 + statement.setLong(6, id + 4); // c_Int64 + + statement.setByte(7, (byte)(id + 5)); // c_Uint8 + statement.setShort(8, (short)(id + 6)); // c_Uint16 + statement.setInt(9, id + 7); // c_Uint32 + statement.setLong(10, id + 8); // c_Uint64 + + statement.setFloat(11, 1.5f * id); // c_Float + statement.setDouble(12, 2.5d * id); // c_Double + + statement.setBytes(13, new byte[] { (byte)id }); // c_Bytes + statement.setString(14, "Text_" + id); // c_Text + statement.setString(15, "{\"json\": " + id + "}"); // c_Json + statement.setString(16, "{\"jsonDoc\": " + id + "}"); // c_JsonDocument + statement.setString(17, "{yson=" + id + "}"); // c_Yson + + + Date sqlDate = new Date(calcStartDayUTC(INSTANT.plus(id, ChronoUnit.DAYS)).toEpochMilli()); + LocalDateTime dateTime = LocalDateTime.ofInstant(INSTANT, ZoneOffset.UTC).plusMinutes(id); + Timestamp timestamp = Timestamp.from(INSTANT.plusSeconds(id)); + Duration duration = Duration.ofMinutes(id); + + statement.setDate(18, sqlDate); // c_Date + statement.setObject(19, dateTime); // c_Datetime + statement.setTimestamp(20, timestamp); // c_Timestamp + statement.setObject(21, duration); // c_Interval + + statement.setNull(22, Types.DECIMAL); // c_Decimal + } + + private void assertRowValues(ResultSet rs, int id) throws SQLException { + Assert.assertTrue(rs.next()); + + Assert.assertEquals(id, rs.getInt("key")); + + Assert.assertEquals(id % 2 == 0, rs.getBoolean("c_Bool")); + + Assert.assertEquals(id + 1, rs.getByte("c_Int8")); + Assert.assertEquals(id + 2, rs.getShort("c_Int16")); + Assert.assertEquals(id + 3, rs.getInt("c_Int32")); + Assert.assertEquals(id + 4, rs.getLong("c_Int64")); + + Assert.assertEquals(id + 5, rs.getByte("c_Uint8")); + Assert.assertEquals(id + 6, rs.getShort("c_Uint16")); + Assert.assertEquals(id + 7, rs.getInt("c_Uint32")); + Assert.assertEquals(id + 8, rs.getLong("c_Uint64")); + + Assert.assertEquals(1.5f * id, rs.getFloat("c_Float"), 0.001f); + Assert.assertEquals(2.5d * id, rs.getDouble("c_Double"), 0.001d); + + Assert.assertArrayEquals(new byte[] { (byte)id }, rs.getBytes("c_Bytes")); + Assert.assertEquals("Text_" + id, rs.getString("c_Text")); + Assert.assertEquals("{\"json\": " + id + "}", rs.getString("c_Json")); + Assert.assertEquals("{\"jsonDoc\":" + id + "}", rs.getString("c_JsonDocument")); + Assert.assertEquals("{yson=" + id + "}", rs.getString("c_Yson")); + + + Date sqlDate = new Date(calcStartDayUTC(INSTANT.plus(id, ChronoUnit.DAYS)).toEpochMilli()); + LocalDateTime dateTime = LocalDateTime.ofInstant(INSTANT, ZoneOffset.UTC).plusMinutes(id) + .truncatedTo(ChronoUnit.SECONDS); + Timestamp timestamp = Timestamp.from(INSTANT.plusSeconds(id)); + Duration duration = Duration.ofMinutes(id); + + Date rsDate = rs.getDate("c_Date"); + + Assert.assertEquals(sqlDate, rsDate); + Assert.assertEquals(dateTime, rs.getObject("c_Datetime")); + Assert.assertEquals(timestamp, rs.getTimestamp("c_Timestamp")); + Assert.assertEquals(duration, rs.getObject("c_Interval")); + + Assert.assertNull(rs.getString("c_Decimal")); + Assert.assertTrue(rs.wasNull()); + } + + @ParameterizedTest(name = "with {0}") + @EnumSource(SqlQueries.JdbcQuery.class) + public void batchUpsertAllTest(SqlQueries.JdbcQuery query) throws SQLException { + String upsert = TEST_TABLE.upsertAll(query); + + try (PreparedStatement statement = jdbc.connection().prepareStatement(upsert)) { + // ----- base usage ----- + fillRowValues(statement, 1); + statement.addBatch(); + + fillRowValues(statement, 2); + statement.addBatch(); + + statement.executeBatch(); + + // ----- executeBatch without addBatch ----- + fillRowValues(statement, 3); + statement.addBatch(); + + fillRowValues(statement, 4); + statement.executeBatch(); + + // ----- execute instead of executeBatch ----- + fillRowValues(statement, 5); + statement.addBatch(); + + fillRowValues(statement, 6); + statement.execute(); + } + + try (Statement statement = jdbc.connection().createStatement()) { + try (ResultSet rs = statement.executeQuery(TEST_TABLE.selectSQL())) { + assertRowValues(rs, 1); + assertRowValues(rs, 2); + assertRowValues(rs, 3); + assertRowValues(rs, 6); + + Assert.assertFalse(rs.next()); + } + } + }; } diff --git a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementWithDataQueryBatchedImplTest.java b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementWithDataQueryBatchedImplTest.java index 9f4d86e..924a6d4 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementWithDataQueryBatchedImplTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementWithDataQueryBatchedImplTest.java @@ -119,7 +119,7 @@ private PreparedStatement prepareScanSelect(String column) throws SQLException { @Test public void testInvalidStruct() throws SQLException { - ExceptionAssert.ydbNonRetryable("Duplicated member: key", () -> { + ExceptionAssert.ydbException("Duplicated member: key", () -> { jdbc.connection().unwrap(YdbConnection.class).prepareStatement( invalidBatchUpsertSql("c_Text", "Text"), YdbPrepareMode.DATA_QUERY_BATCH); @@ -283,7 +283,7 @@ public void executeQueryBatchWithScanRead() throws SQLException { jdbc.connection().commit(); - ExceptionAssert.ydbResultTruncated("Result #0 was truncated to 1000 rows", () -> { + ExceptionAssert.sqlException("Result #0 was truncated to 1000 rows", () -> { // Result is truncated (and we catch that) try (PreparedStatement select = prepareSimpleSelect("c_Text")) { select.executeQuery(); diff --git a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementWithDataQueryImplTest.java b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementWithDataQueryImplTest.java index 17d6651..4d1e459 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementWithDataQueryImplTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementWithDataQueryImplTest.java @@ -119,7 +119,7 @@ public void executeWrongParameters() throws SQLException { String sql = upsertSql("c_Text", "Text"); // Must be Text? try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(sql)) { statement.setInt("key", 1); - ExceptionAssert.ydbNonRetryable("Missing value for parameter", statement::execute); + ExceptionAssert.sqlDataException("Missing value for parameter", statement::execute); } try (YdbPreparedStatement statement = jdbc.connection().unwrap(YdbConnection.class).prepareStatement(sql)) { @@ -290,7 +290,7 @@ public void executeScanQueryAsUpdate() throws SQLException { statement.setInt("key", 1); statement.setString("c_Text", "value-1"); - ExceptionAssert.ydbNonRetryable("Scan query should have a single result set", statement::execute); + ExceptionAssert.ydbException("Scan query should have a single result set", statement::execute); } } } diff --git a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbStatementImplTest.java b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbStatementImplTest.java index fa5f231..e1d5417 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbStatementImplTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbStatementImplTest.java @@ -270,7 +270,7 @@ public void executeSchemeQueryExplicitly() throws SQLException { statement.execute("create table scheme_query_test(id Int32, primary key(id))"); String dropSql = "scan drop table scheme_query_test"; - ExceptionAssert.ydbNonRetryable("Operation is not supported in current execution mode, check query type. ", + ExceptionAssert.ydbException("Operation is not supported in current execution mode, check query type. ", () -> statement.unwrap(YdbStatement.class).executeQuery(dropSql)); statement.unwrap(YdbStatement.class).executeSchemeQuery(dropSql); } @@ -312,7 +312,7 @@ public void executeScanQueryOnSystemTable() throws SQLException { @Test public void executeScanQueryMultiResult() { - ExceptionAssert.ydbNonRetryable("Scan query should have a single result set", + ExceptionAssert.ydbException("Scan query should have a single result set", () -> statement.executeUpdate("scan select 2 + 2;scan select 2 + 3") ); } @@ -320,7 +320,7 @@ public void executeScanQueryMultiResult() { @Test public void executeScanQueryAsUpdate() { // Looks weird - ExceptionAssert.ydbNonRetryable("Scan query should have a single result set", + ExceptionAssert.ydbException("Scan query should have a single result set", () -> statement.executeUpdate("SCAN\n" + TEST_UPSERT1_SQL) ); } @@ -360,10 +360,10 @@ public void executeQueryExplainAndExplicitly() throws SQLException { public void getResultSet() throws SQLException { Assertions.assertNull(statement.getResultSet()); - statement.execute("select 2 + 2"); + Assertions.assertTrue(statement.execute("select 2 + 2")); Assertions.assertNotNull(statement.getResultSet()); - statement.execute(TEST_UPSERT1_SQL); + Assertions.assertFalse(statement.execute(TEST_UPSERT1_SQL)); Assertions.assertNull(statement.getResultSet()); jdbc.connection().commit(); @@ -373,7 +373,7 @@ public void getResultSet() throws SQLException { public void getUpdateCount() throws SQLException { Assertions.assertEquals(-1, statement.getUpdateCount()); - statement.execute("select 2 + 2"); + Assertions.assertTrue(statement.execute("select 2 + 2")); Assertions.assertEquals(-1, statement.getUpdateCount()); statement.execute(TEST_UPSERT1_SQL); @@ -384,9 +384,11 @@ public void getUpdateCount() throws SQLException { statement.execute(TEST_UPSERT1_SQL + ";\n" + TEST_UPSERT2_SQL + ";"); Assertions.assertEquals(1, statement.getUpdateCount()); // just a single statement Assertions.assertFalse(statement.getMoreResults()); + Assertions.assertEquals(1, statement.getUpdateCount()); + Assertions.assertFalse(statement.getMoreResults()); Assertions.assertEquals(-1, statement.getUpdateCount()); - statement.execute("select 2 + 2"); + Assertions.assertTrue(statement.execute("select 2 + 2")); Assertions.assertEquals(-1, statement.getUpdateCount()); jdbc.connection().commit(); @@ -413,12 +415,47 @@ public void getMoreResults() throws SQLException { Assertions.assertNotSame(rs0, rs1); Assertions.assertNotSame(rs0, rs2); - Assertions.assertSame(rs0, statement.unwrap(YdbStatement.class).getResultSetAt(0)); Assertions.assertSame(rs1, statement.unwrap(YdbStatement.class).getResultSetAt(1)); Assertions.assertSame(rs2, statement.unwrap(YdbStatement.class).getResultSetAt(2)); } + @Test + public void testMixedStatements() throws SQLException { + Assertions.assertTrue(statement.execute("select 2 + 1; " + TEST_UPSERT1_SQL + ";" + "select 2 + 3;")); + + Assertions.assertEquals(-1, statement.getUpdateCount()); + + ResultSet rs0 = statement.getResultSet(); + + Assertions.assertFalse(statement.getMoreResults()); + Assertions.assertEquals(1, statement.getUpdateCount()); + + Assertions.assertTrue(statement.getMoreResults()); + Assertions.assertEquals(-1, statement.getUpdateCount()); + + ResultSet rs1 = statement.getResultSet(); + Assertions.assertNotSame(rs0, rs1); + + Assertions.assertFalse(statement.getMoreResults()); + Assertions.assertEquals(-1, statement.getUpdateCount()); + } + + @Test + public void testSchemeStatements() throws SQLException { + Assertions.assertFalse(statement.execute("CREATE TABLE tmp_table (id Int32, primary key (id))")); + Assertions.assertEquals(0, statement.getUpdateCount()); + + Assertions.assertFalse(statement.getMoreResults()); + Assertions.assertEquals(-1, statement.getUpdateCount()); + + Assertions.assertFalse(statement.execute("DROP TABLE tmp_table")); + Assertions.assertEquals(0, statement.getUpdateCount()); + + Assertions.assertFalse(statement.getMoreResults()); + Assertions.assertEquals(-1, statement.getUpdateCount()); + } + @Test public void getMoreResultsDifferentMode() throws SQLException { statement.execute("select 1 + 2; select 2 + 3; select 3 + 4"); diff --git a/jdbc/src/test/java/tech/ydb/jdbc/impl/helper/ExceptionAssert.java b/jdbc/src/test/java/tech/ydb/jdbc/impl/helper/ExceptionAssert.java index 0e59076..8911729 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/impl/helper/ExceptionAssert.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/impl/helper/ExceptionAssert.java @@ -1,16 +1,14 @@ package tech.ydb.jdbc.impl.helper; +import java.sql.SQLDataException; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLRecoverableException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.function.Executable; -import tech.ydb.jdbc.exception.YdbConditionallyRetryableException; -import tech.ydb.jdbc.exception.YdbConfigurationException; -import tech.ydb.jdbc.exception.YdbExecutionException; -import tech.ydb.jdbc.exception.YdbNonRetryableException; -import tech.ydb.jdbc.exception.YdbResultTruncatedException; +import tech.ydb.jdbc.exception.YdbSQLException; /** * @@ -19,42 +17,28 @@ public class ExceptionAssert { private ExceptionAssert() { } - public static void ydbConfiguration(String message, Executable exec) { - YdbConfigurationException ex = Assertions.assertThrows(YdbConfigurationException.class, exec, - "Invalid statement must throw YdbConfigurationException" - ); - Assertions.assertEquals(message, ex.getMessage()); - } - - public static void ydbNonRetryable(String message, Executable exec) { - YdbNonRetryableException ex = Assertions.assertThrows(YdbNonRetryableException.class, exec, - "Invalid statement must throw YdbNonRetryableException" + public static void ydbException(String message, Executable exec) { + YdbSQLException ex = Assertions.assertThrows(YdbSQLException.class, exec, + "Invalid statement must throw YdbSQLException" ); Assertions.assertTrue(ex.getMessage().contains(message), "YdbNonRetryableException '" + ex.getMessage() + "' doesn't contain message '" + message + "'"); } - public static void ydbExecution(String message, Executable exec) { - YdbExecutionException ex = Assertions.assertThrows(YdbExecutionException.class, exec, - "Invalid statement must throw YdbExecutionException" - ); - Assertions.assertEquals(message, ex.getMessage()); - } - - public static void ydbResultTruncated(String message, Executable exec) { - YdbResultTruncatedException ex = Assertions.assertThrows(YdbResultTruncatedException.class, exec, - "Invalid statement must throw YdbExecutionException" + public static void sqlDataException(String message, Executable exec) { + SQLDataException ex = Assertions.assertThrows(SQLDataException.class, exec, + "Invalid statement must throw SQLDataException" ); - Assertions.assertEquals(message, ex.getMessage()); + Assertions.assertTrue(ex.getMessage().contains(message), + "SQLDataException '" + ex.getMessage() + "' doesn't contain message '" + message + "'"); } - public static void ydbConditionallyRetryable(String message, Executable exec) { - YdbConditionallyRetryableException ex = Assertions.assertThrows(YdbConditionallyRetryableException.class, exec, - "Invalid statement must throw YdbConditionallyRetryableException" + public static void sqlRecoverable(String message, Executable exec) { + SQLRecoverableException ex = Assertions.assertThrows(SQLRecoverableException.class, exec, + "Invalid statement must throw SQLRecoverableException" ); Assertions.assertTrue(ex.getMessage().contains(message), - "YdbConditionallyRetryableException '" + ex.getMessage() - + "' doesn't contain message '" + message + "'"); + "SQLRecoverableException '" + ex.getMessage() + "' doesn't contain message '" + message + "'"); } public static void sqlFeatureNotSupported(String message, Executable exec) { diff --git a/jdbc/src/test/java/tech/ydb/jdbc/impl/helper/SqlQueries.java b/jdbc/src/test/java/tech/ydb/jdbc/impl/helper/SqlQueries.java index 6609e6a..67c2066 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/impl/helper/SqlQueries.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/impl/helper/SqlQueries.java @@ -28,10 +28,12 @@ public enum YqlQuery { private static final String SELECT = YdbLookup.stringFileReference("classpath:sql/select.sql"); - private static final String SIMPLE_UPSERT_ALL = YdbLookup.stringFileReference("classpath:sql/upsert/simple.sql"); - private static final String NAMED_UPSERT_ALL = YdbLookup.stringFileReference("classpath:sql/upsert/named.sql"); - private static final String BATCHED_UPSERT_ALL = YdbLookup.stringFileReference("classpath:sql/upsert/batched.sql"); - private static final String INDEXED_UPSERT_ALL = YdbLookup.stringFileReference("classpath:sql/upsert/types.sql"); + private static final String SIMPLE_UPSERT = YdbLookup.stringFileReference("classpath:sql/upsert/simple.sql"); + private static final String NAMED_UPSERT = YdbLookup.stringFileReference("classpath:sql/upsert/named.sql"); + private static final String TYPED_UPSERT = YdbLookup.stringFileReference("classpath:sql/upsert/typed.sql"); + + private static final String NAMED_BATCH = YdbLookup.stringFileReference("classpath:sql/upsert/named_batch.sql"); + private static final String TYPED_BATCH = YdbLookup.stringFileReference("classpath:sql/upsert/typed_batch.sql"); private static final String SELECT_ALL = "select * from #tableName"; private static final String DELETE_ALL = "delete from #tableName"; @@ -109,20 +111,24 @@ public String deleteAllSQL() { public String namedUpsertAll(YqlQuery mode) { switch (mode) { case BATCHED: - return withTableName(BATCHED_UPSERT_ALL, tableName); + return withTableName(NAMED_BATCH, tableName); case SIMPLE: default: - return withTableName(NAMED_UPSERT_ALL, tableName); + return withTableName(NAMED_UPSERT, tableName); } } - public String simpleUpsertAllSQL() { - return withTableName(SIMPLE_UPSERT_ALL, tableName); - } - - public String indexesUpsertAllSQL() { - return withTableName(INDEXED_UPSERT_ALL, tableName); + public String upsertAll(JdbcQuery mode) { + switch (mode) { + case BATCHED: + return withTableName(TYPED_BATCH, tableName); + case TYPED: + return withTableName(TYPED_UPSERT, tableName); + case STANDART: + default: + return withTableName(SIMPLE_UPSERT, tableName); + } } /** 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 d3c2acc..80f9107 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/query/QueryLexerTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/query/QueryLexerTest.java @@ -7,18 +7,20 @@ import org.junit.jupiter.api.Test; + /** * * @author Aleksandr Gorshenin */ public class QueryLexerTest { - - private QueryType parseQueryType(YdbQueryOptions opts, String sql) throws SQLException { - return YdbQuery.from(opts, sql).type(); + private static YdbQuery parseQuery(YdbQueryOptions opts, String sql) throws SQLException { + YdbQueryBuilder builder = new YdbQueryBuilder(sql, opts.getForcedQueryType()); + JdbcQueryLexer.buildQuery(builder, opts); + return builder.build(opts); } - private String parseQuery(YdbQueryOptions opts, String sql) throws SQLException { - return YdbQuery.from(opts, sql).getYqlQuery(null); + private static QueryType parsedQueryType(YdbQueryOptions opts, String sql) throws SQLException { + return parseQuery(opts, sql).type(); } private void assertMixType(YdbQueryOptions opts, String types, String sql) { @@ -33,44 +35,44 @@ private void assertMixType(YdbQueryOptions opts, String types, String sql) { public void queryTypesTest() throws SQLException { YdbQueryOptions opts = new YdbQueryOptions(true, false, false, false, false, null); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "CREATE TABLE test_table (id int, value text)" )); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "\tcreate TABLE test_table2 (id int, value text);" )); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, " drop TABLE test_table1 (id int, value text);" + "ALTER TABLE test_table2 (id int, value text);" )); - Assertions.assertEquals(QueryType.DATA_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.DATA_QUERY, parsedQueryType(opts, "SELECT id, value FROM test_table" )); - Assertions.assertEquals(QueryType.DATA_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.DATA_QUERY, parsedQueryType(opts, "UPSERT INTO test_table VALUES (?, ?)" )); - Assertions.assertEquals(QueryType.DATA_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.DATA_QUERY, parsedQueryType(opts, "DELETE FROM test_table" )); - Assertions.assertEquals(QueryType.DATA_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.DATA_QUERY, parsedQueryType(opts, "SELECT id, value FROM test_table;\n" + "UPSERT INTO test_table VALUES (?, ?);" + "DELETE FROM test_table" )); - Assertions.assertEquals(QueryType.DATA_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.DATA_QUERY, parsedQueryType(opts, "SELECT id, value FROM test_table;\n" + "UPDATE test_table SET value = ? WHERE id = ?;" + "SELECT id, value FROM test_table WHERE id=CREATE" )); - Assertions.assertEquals(QueryType.SCAN_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCAN_QUERY, parsedQueryType(opts, "SCAN SELECT id, value FROM test_table" )); - Assertions.assertEquals(QueryType.EXPLAIN_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.EXPLAIN_QUERY, parsedQueryType(opts, "EXPLAIN SELECT id, value FROM test_table" )); } @@ -101,61 +103,61 @@ public void mixQueryExceptionTest() throws SQLException { public void forsedTypeTest() throws SQLException { YdbQueryOptions opts = new YdbQueryOptions(true, false, false, false, false, QueryType.SCHEME_QUERY); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "CREATE TABLE test_table (id int, value text)" )); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "\tcreate TABLE test_table2 (id int, value text);" )); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, " drop TABLE test_table1 (id int, value text);" + "ALTER TABLE test_table2 (id int, value text);" )); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "SELECT id, value FROM test_table" )); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "UPSERT INTO test_table VALUES (?, ?)" )); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "DELETE FROM test_table" )); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "SELECT id, value FROM test_table;\n" + "UPSERT INTO test_table VALUES (?, ?);" + "DELETE FROM test_table" )); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "SELECT id, value FROM test_table;\n" + "UPDATE test_table SET value = ? WHERE id = ?;" + "SELECT id, value FROM test_table WHERE id=CREATE" )); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "SCAN SELECT id, value FROM test_table" )); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "EXPLAIN SELECT id, value FROM test_table" )); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "CREATE TABLE test_table (id int, value text);" + "SELECT * FROM test_table;" )); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "DROP TABLE test_table (id int, value text);SELECT * FROM test_table;" )); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "SELECT * FROM test_table;CREATE TABLE test_table (id int, value text);" )); - Assertions.assertEquals(QueryType.SCHEME_QUERY, parseQueryType(opts, + Assertions.assertEquals(QueryType.SCHEME_QUERY, parsedQueryType(opts, "SELECT * FROM test_table;\n\tCREATE TABLE test_table (id int, value text);" )); } diff --git a/jdbc/src/test/resources/sql/upsert/batched.sql b/jdbc/src/test/resources/sql/upsert/named_batch.sql similarity index 100% rename from jdbc/src/test/resources/sql/upsert/batched.sql rename to jdbc/src/test/resources/sql/upsert/named_batch.sql diff --git a/jdbc/src/test/resources/sql/upsert/simple.sql b/jdbc/src/test/resources/sql/upsert/simple.sql index a05f87c..2abe87f 100644 --- a/jdbc/src/test/resources/sql/upsert/simple.sql +++ b/jdbc/src/test/resources/sql/upsert/simple.sql @@ -1,4 +1,4 @@ -upsert into #tableName ( +UPSERT INTO #tableName ( key, c_Bool, @@ -28,8 +28,33 @@ upsert into #tableName ( c_Interval, c_Decimal -) values ( - ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, - ?, ?, ?, ?, ?, ?, ?, ? ,? ,?, - ?, ? +) VALUES ( + ?, -- key + ?, -- c_Bool + + CAST(? AS Int8), -- c_Int8 + CAST(? AS Int16), -- c_Int16 + ?, -- c_Int32 + ?, -- c_Int64 + + CAST(? AS Uint8), -- c_Uint8 + CAST(? AS Uint16), -- c_Uint16 + CAST(? AS Uint32), -- c_Uint32 + CAST(? AS Uint64), -- c_Uint64 + + ?, -- c_Float + ?, -- c_Double + + ?, -- c_Bytes + ?, -- c_Text + CAST(? AS Json), -- c_Json + CAST(? AS JsonDocument), -- c_JsonDocument + CAST(? AS Yson), -- c_Yson + + ?, -- c_Date + ?, -- c_Datetime + ?, -- c_Timestamp + ?, -- c_Interval + + ? -- c_Decimal ) \ No newline at end of file diff --git a/jdbc/src/test/resources/sql/upsert/types.sql b/jdbc/src/test/resources/sql/upsert/typed.sql similarity index 99% rename from jdbc/src/test/resources/sql/upsert/types.sql rename to jdbc/src/test/resources/sql/upsert/typed.sql index fd17536..0bba3a0 100644 --- a/jdbc/src/test/resources/sql/upsert/types.sql +++ b/jdbc/src/test/resources/sql/upsert/typed.sql @@ -80,5 +80,5 @@ upsert into #tableName ( $p19, $p20, $p21, - $p22, + $p22 ) \ No newline at end of file diff --git a/jdbc/src/test/resources/sql/upsert/typed_batch.sql b/jdbc/src/test/resources/sql/upsert/typed_batch.sql new file mode 100644 index 0000000..d8ed1c8 --- /dev/null +++ b/jdbc/src/test/resources/sql/upsert/typed_batch.sql @@ -0,0 +1,63 @@ +declare $list as List, + + p3:Optional, + p4:Optional, + p5:Optional, + p6:Optional, + + p7:Optional, + p8:Optional, + p9:Optional, + p10:Optional, + + p11:Optional, + p12:Optional, + + p13:Optional, + p14:Optional, + p15:Optional, + p16:Optional, + p17:Optional, + + p18:Optional, + p19:Optional, + p20:Optional, + p21:Optional, + + p22:Optional +>>; + +upsert into #tableName select + p1 as key, + + p2 as c_Bool, + + p3 as c_Int8, + p4 as c_Int16, + p5 as c_Int32, + p6 as c_Int64, + + p7 as c_Uint8, + p8 as c_Uint16, + p9 as c_Uint32, + p10 as c_Uint64, + + p11 as c_Float, + p12 as c_Double, + + p13 as c_Bytes, + p14 as c_Text, + p15 as c_Json, + p16 as c_JsonDocument, + p17 as c_Yson, + + p18 as c_Date, + p19 as c_Datetime, + p20 as c_Timestamp, + p21 as c_Interval, + + p22 as c_Decimal +from as_table($list); diff --git a/pom.xml b/pom.xml index 8bf8336..8cc503b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ tech.ydb.jdbc ydb-jdbc-driver-parent - 2.0.3 + 2.0.4 YDB JDBC Module JDBC Driver over YDB Java SDK