11package tech .ydb .jdbc .context ;
22
3+ import java .sql .SQLDataException ;
34import java .sql .SQLException ;
5+ import java .time .Duration ;
46import java .util .Map ;
57import java .util .Objects ;
8+ import java .util .concurrent .CompletableFuture ;
9+ import java .util .concurrent .Executors ;
610import java .util .concurrent .atomic .AtomicInteger ;
711import java .util .logging .Level ;
812import java .util .logging .Logger ;
913
14+ import com .google .common .cache .Cache ;
15+ import com .google .common .cache .CacheBuilder ;
16+
17+ import tech .ydb .core .Result ;
18+ import tech .ydb .core .UnexpectedResultException ;
1019import tech .ydb .core .grpc .GrpcTransport ;
1120import tech .ydb .core .grpc .GrpcTransportBuilder ;
12- import tech .ydb .jdbc .exception .YdbConfigurationException ;
21+ import tech .ydb .jdbc .YdbConst ;
22+ import tech .ydb .jdbc .YdbPrepareMode ;
23+ import tech .ydb .jdbc .exception .ExceptionFactory ;
24+ import tech .ydb .jdbc .query .JdbcParams ;
25+ import tech .ydb .jdbc .query .JdbcQueryLexer ;
26+ import tech .ydb .jdbc .query .YdbQuery ;
27+ import tech .ydb .jdbc .query .YdbQueryBuilder ;
1328import tech .ydb .jdbc .query .YdbQueryOptions ;
29+ import tech .ydb .jdbc .query .params .BatchedParams ;
30+ import tech .ydb .jdbc .query .params .InMemoryParams ;
31+ import tech .ydb .jdbc .query .params .PreparedParams ;
1432import tech .ydb .jdbc .settings .ParsedProperty ;
1533import tech .ydb .jdbc .settings .YdbClientProperties ;
1634import tech .ydb .jdbc .settings .YdbClientProperty ;
1735import tech .ydb .jdbc .settings .YdbConnectionProperties ;
1836import tech .ydb .jdbc .settings .YdbConnectionProperty ;
1937import tech .ydb .jdbc .settings .YdbOperationProperties ;
2038import tech .ydb .scheme .SchemeClient ;
39+ import tech .ydb .table .SessionRetryContext ;
2140import tech .ydb .table .TableClient ;
41+ import tech .ydb .table .description .TableDescription ;
2242import tech .ydb .table .impl .PooledTableClient ;
2343import tech .ydb .table .rpc .grpc .GrpcTableRpc ;
44+ import tech .ydb .table .settings .DescribeTableSettings ;
45+ import tech .ydb .table .settings .PrepareDataQuerySettings ;
46+ import tech .ydb .table .settings .RequestSettings ;
47+ import tech .ydb .table .values .Type ;
2448
2549/**
2650 *
@@ -41,6 +65,10 @@ public class YdbContext implements AutoCloseable {
4165 private final PooledTableClient tableClient ;
4266 private final SchemeClient schemeClient ;
4367 private final YdbQueryOptions queryOptions ;
68+ private final SessionRetryContext retryCtx ;
69+
70+ private final Cache <String , YdbQuery > queriesCache ;
71+ private final Cache <String , Map <String , Type >> queryParamsCache ;
4472
4573 private final boolean autoResizeSessionPool ;
4674 private final AtomicInteger connectionsCount = new AtomicInteger ();
@@ -52,8 +80,20 @@ private YdbContext(YdbConfig config, GrpcTransport transport, PooledTableClient
5280 this .schemeClient = SchemeClient .newClient (transport ).build ();
5381 this .queryOptions = YdbQueryOptions .createFrom (config .getOperationProperties ());
5482 this .autoResizeSessionPool = autoResize ;
83+ this .retryCtx = SessionRetryContext .create (tableClient ).build ();
84+
85+ int cacheSize = config .getOperationProperties ().getPreparedStatementCacheSize ();
86+ if (cacheSize > 0 ) {
87+ queriesCache = CacheBuilder .newBuilder ().maximumSize (cacheSize ).build ();
88+ queryParamsCache = CacheBuilder .newBuilder ().maximumSize (cacheSize ).build ();
89+ } else {
90+ queriesCache = null ;
91+ queryParamsCache = null ;
92+ }
5593 }
5694
95+
96+
5797 public String getDatabase () {
5898 return grpcTransport .getDatabase ();
5999 }
@@ -70,10 +110,6 @@ public String getUrl() {
70110 return config .getUrl ();
71111 }
72112
73- public YdbQueryOptions getQueryOptions () {
74- return queryOptions ;
75- }
76-
77113 public int getConnectionsCount () {
78114 return connectionsCount .get ();
79115 }
@@ -131,8 +167,8 @@ public static YdbContext createContext(YdbConfig config) throws SQLException {
131167 boolean autoResize = buildTableClient (tableClient , clientProps );
132168
133169 return new YdbContext (config , grpcTransport , tableClient .build (), autoResize );
134- } catch (Exception ex ) {
135- throw new YdbConfigurationException ("Cannot connect to YDB: " + ex .getMessage (), ex );
170+ } catch (RuntimeException ex ) {
171+ throw new SQLException ("Cannot connect to YDB: " + ex .getMessage (), ex );
136172 }
137173 }
138174
@@ -148,6 +184,17 @@ public static GrpcTransport buildGrpcTransport(YdbConnectionProperties props) {
148184 builder = builder .withAuthProvider (props .getStaticCredentials ());
149185 }
150186
187+ // Use custom single thread scheduler because JDBC driver doesn't need to execute retries except for DISCOERY
188+ builder .withSchedulerFactory (() -> {
189+ final String namePrefix = "ydb-jdbc-scheduler[" + props .hashCode () +"]-thread-" ;
190+ final AtomicInteger threadNumber = new AtomicInteger (1 );
191+ return Executors .newScheduledThreadPool (1 , (Runnable r ) -> {
192+ Thread t = new Thread (r , namePrefix + threadNumber .getAndIncrement ());
193+ t .setDaemon (true );
194+ return t ;
195+ });
196+ });
197+
151198 return builder .build ();
152199 }
153200
@@ -179,4 +226,73 @@ private static boolean buildTableClient(TableClient.Builder builder, YdbClientPr
179226 builder .sessionPoolSize (minSize , maxSize );
180227 return false ;
181228 }
229+
230+ public <T extends RequestSettings <?>> T withDefaultTimeout (T settings ) {
231+ Duration operation = config .getOperationProperties ().getDeadlineTimeout ();
232+ if (!operation .isZero () && !operation .isNegative ()) {
233+ settings .setOperationTimeout (operation );
234+ settings .setTimeout (operation .plusSeconds (1 ));
235+ }
236+ return settings ;
237+ }
238+
239+ public CompletableFuture <Result <TableDescription >> describeTable (String tablePath , DescribeTableSettings settings ) {
240+ return retryCtx .supplyResult (session -> session .describeTable (tablePath , settings ));
241+ }
242+
243+ public YdbQuery parseYdbQuery (String sql ) throws SQLException {
244+ YdbQueryBuilder builder = new YdbQueryBuilder (sql , queryOptions .getForcedQueryType ());
245+ JdbcQueryLexer .buildQuery (builder , queryOptions );
246+ return builder .build (queryOptions );
247+ }
248+
249+ public YdbQuery findOrParseYdbQuery (String sql ) throws SQLException {
250+ if (queriesCache == null ) {
251+ return parseYdbQuery (sql );
252+ }
253+
254+ YdbQuery cached = queriesCache .getIfPresent (sql );
255+ if (cached == null ) {
256+ cached = parseYdbQuery (sql );
257+ queriesCache .put (sql , cached );
258+ }
259+
260+ return cached ;
261+ }
262+
263+ public JdbcParams findOrCreateJdbcParams (YdbQuery query , YdbPrepareMode mode ) throws SQLException {
264+ if (query .hasIndexesParameters ()
265+ || mode == YdbPrepareMode .IN_MEMORY
266+ || !queryOptions .iPrepareDataQueries ()) {
267+ return new InMemoryParams (query .getIndexesParameters ());
268+ }
269+
270+ String yql = query .getYqlQuery (null );
271+ PrepareDataQuerySettings settings = withDefaultTimeout (new PrepareDataQuerySettings ());
272+ try {
273+ Map <String , Type > types = queryParamsCache .getIfPresent (query .originSQL ());
274+ if (types == null ) {
275+ types = retryCtx .supplyResult (session -> session .prepareDataQuery (yql , settings ))
276+ .join ()
277+ .getValue ()
278+ .types ();
279+ queryParamsCache .put (query .originSQL (), types );
280+ }
281+
282+ boolean requireBatch = mode == YdbPrepareMode .DATA_QUERY_BATCH ;
283+ if (requireBatch || (mode == YdbPrepareMode .AUTO && queryOptions .isDetectBatchQueries ())) {
284+ BatchedParams params = BatchedParams .tryCreateBatched (types );
285+ if (params != null ) {
286+ return params ;
287+ }
288+
289+ if (requireBatch ) {
290+ throw new SQLDataException (YdbConst .STATEMENT_IS_NOT_A_BATCH + query .originSQL ());
291+ }
292+ }
293+ return new PreparedParams (types );
294+ } catch (UnexpectedResultException ex ) {
295+ throw ExceptionFactory .createException ("Cannot prepare data query: " + ex .getMessage (), ex );
296+ }
297+ }
182298}
0 commit comments