2 * This file is part of yosql. It is subject to the license terms in the LICENSE file found in the top-level
3 * directory of this distribution and at https://creativecommons.org/publicdomain/zero/1.0/. No part of yosql,
4 * including this file, may be copied, modified, propagated, or distributed except according to the terms contained
7 package wtf
.metio
.yosql
.dao
.jdbc
;
9 import com
.squareup
.javapoet
.*;
10 import de
.xn__ho_hia
.javapoet
.TypeGuesser
;
11 import io
.reactivex
.Flowable
;
12 import io
.smallrye
.mutiny
.Multi
;
13 import reactor
.core
.publisher
.Flux
;
14 import wtf
.metio
.yosql
.codegen
.api
.ControlFlows
;
15 import wtf
.metio
.yosql
.codegen
.api
.Fields
;
16 import wtf
.metio
.yosql
.codegen
.api
.Variables
;
17 import wtf
.metio
.yosql
.codegen
.blocks
.GenericBlocks
;
18 import wtf
.metio
.yosql
.internals
.javapoet
.TypicalTypes
;
19 import wtf
.metio
.yosql
.internals
.jdk
.Buckets
;
20 import wtf
.metio
.yosql
.logging
.api
.LoggingGenerator
;
21 import wtf
.metio
.yosql
.models
.constants
.api
.LoggingApis
;
22 import wtf
.metio
.yosql
.models
.immutables
.NamesConfiguration
;
23 import wtf
.metio
.yosql
.models
.immutables
.RuntimeConfiguration
;
24 import wtf
.metio
.yosql
.models
.immutables
.SqlConfiguration
;
25 import wtf
.metio
.yosql
.models
.immutables
.SqlStatement
;
26 import wtf
.metio
.yosql
.models
.sql
.ResultRowConverter
;
30 import java
.util
.function
.Function
;
31 import java
.util
.stream
.Stream
;
32 import java
.util
.stream
.StreamSupport
;
34 import static wtf
.metio
.yosql
.codegen
.blocks
.CodeBlocks
.code
;
36 public final class DefaultJdbcBlocks
implements JdbcBlocks
{
38 private final RuntimeConfiguration runtimeConfiguration
;
39 private final GenericBlocks blocks
;
40 private final ControlFlows controlFlows
;
41 private final NamesConfiguration names
;
42 private final Variables variables
;
43 private final Fields fields
;
44 private final JdbcMethods jdbcMethods
;
45 private final LoggingGenerator logging
;
47 public DefaultJdbcBlocks(
48 final RuntimeConfiguration runtimeConfiguration
,
49 final GenericBlocks blocks
,
50 final ControlFlows controlFlows
,
51 final Variables variables
,
53 final JdbcMethods jdbcMethods
,
54 final LoggingGenerator logging
) {
55 this.runtimeConfiguration
= runtimeConfiguration
;
57 this.names
= runtimeConfiguration
.names();
58 this.variables
= variables
;
59 this.controlFlows
= controlFlows
;
61 this.jdbcMethods
= jdbcMethods
;
62 this.logging
= logging
;
66 public CodeBlock
connectionVariable() {
67 return variables
.inline(Connection
.class, names
.connection(), jdbcMethods
.dataSource().getConnection());
71 public CodeBlock
statementVariable() {
72 return variables
.inline(PreparedStatement
.class, names
.statement(),
73 jdbcMethods
.connection().prepareStatement());
77 public CodeBlock
callableVariable() {
78 return variables
.inline(CallableStatement
.class, names
.statement(),
79 jdbcMethods
.connection().prepareCallable());
83 public CodeBlock
readMetaData() {
84 return variables
.statement(ResultSetMetaData
.class, names
.resultSetMetaData(),
85 jdbcMethods
.resultSet().getMetaData());
89 public CodeBlock
readColumnCount() {
90 return variables
.statement(int.class, names
.columnCount(),
91 jdbcMethods
.resultSetMetaData().getColumnCount());
95 public CodeBlock
resultSetVariable() {
96 return variables
.inline(ResultSet
.class, names
.resultSet(),
97 jdbcMethods
.statement().executeQuery());
101 public CodeBlock
getResultSet() {
102 return controlFlows
.tryWithResource(variables
.inline(ResultSet
.class, names
.resultSet(),
103 jdbcMethods
.statement().getResultSet()));
107 public CodeBlock
resultSetVariableStatement() {
108 return variables
.statement(ResultSet
.class, names
.resultSet(),
109 jdbcMethods
.statement().executeQuery());
113 public CodeBlock
returnExecuteUpdate() {
114 return blocks
.returnValue(jdbcMethods
.statement().executeUpdate());
118 public CodeBlock
executeForReturning() {
119 return jdbcMethods
.statement().execute();
123 public CodeBlock
executeBatch() {
124 return blocks
.returnValue(jdbcMethods
.statement().executeBatch());
128 public CodeBlock
closeResultSet() {
129 return blocks
.close(names
.resultSet());
133 public CodeBlock
closePrepareStatement() {
134 return blocks
.close(names
.statement());
138 public CodeBlock
closeConnection() {
139 return blocks
.close(names
.connection());
143 public CodeBlock
closeState() {
144 return blocks
.close(names
.state());
148 public CodeBlock
executeStatement() {
149 return controlFlows
.tryWithResource(resultSetVariable());
153 public CodeBlock
openConnection() {
154 return controlFlows
.tryWithResource(connectionVariable());
158 public CodeBlock
tryPrepareCallable() {
159 return controlFlows
.tryWithResource(callableVariable());
163 public CodeBlock
createStatement() {
164 return controlFlows
.tryWithResource(statementVariable());
168 public CodeBlock
prepareBatch(final SqlConfiguration config
) {
169 return controlFlows
.forLoop(
170 code("int $N = 0; $N < $N.length; $N++",
173 config
.parameters().get(0).name(),
176 .add(setBatchParameters(config
))
177 .addStatement(jdbcMethods
.statement().addBatch())
182 public CodeBlock
pickVendorQuery(final List
<SqlStatement
> sqlStatements
) {
183 final var builder
= CodeBlock
.builder();
184 if (sqlStatements
.size() > 1) {
185 builder
.addStatement(variables
.inline(DatabaseMetaData
.class, names
.databaseMetaData(),
186 jdbcMethods
.connection().getMetaData()));
187 builder
.addStatement(variables
.inline(String
.class, names
.databaseProductName(),
188 jdbcMethods
.databaseMetaData().getDatabaseProductName()))
189 .add(logging
.vendorDetected());
190 if (logging
.isEnabled()) {
191 builder
.addStatement("$T $N = null", String
.class, names
.rawQuery());
193 builder
.addStatement("$T $N = null", String
.class, names
.query())
194 .addStatement("$T $N = null", TypicalTypes
.MAP_OF_STRING_AND_ARRAY_OF_INTS
, names
.indexVariable())
195 .beginControlFlow("switch ($N)", names
.databaseProductName());
196 sqlStatements
.stream()
197 .map(SqlStatement
::getConfiguration
)
198 .filter(config
-> Objects
.nonNull(config
.vendor()))
200 final var query
= fields
.constantSqlStatementFieldName(config
);
201 builder
.add("case $S:\n", config
.vendor())
202 .addStatement("$>$N = $N", names
.query(), query
)
203 .add(logging
.vendorQueryPicked(query
));
204 finalizeCase(builder
, config
);
206 final var firstConfigWithoutVendor
= sqlStatements
.stream()
207 .map(SqlStatement
::getConfiguration
)
208 .filter(config
-> Objects
.isNull(config
.vendor()))
210 if (firstConfigWithoutVendor
.isPresent()) {
211 final var config
= firstConfigWithoutVendor
.get();
212 final var query
= fields
.constantSqlStatementFieldName(config
);
213 builder
.add("default:\n")
214 .addStatement("$>$N = $N", names
.query(), query
)
215 .add(logging
.vendorQueryPicked(query
));
216 finalizeCase(builder
, config
);
218 builder
.add("default:\n")
219 .addStatement("$>throw new $T($T.format($S, $N))$<", IllegalStateException
.class, String
.class,
220 "No suitable query defined for vendor [%s]", names
.databaseProductName());
222 builder
.endControlFlow();
224 final var config
= sqlStatements
.get(0).getConfiguration();
225 final var query
= fields
.constantSqlStatementFieldName(config
);
226 builder
.addStatement(variables
.inline(String
.class, names
.query(), "$N", query
))
227 .add(logging
.queryPicked(query
));
228 if (logging
.isEnabled()) {
229 final var rawQuery
= fields
.constantRawSqlStatementFieldName(config
);
230 builder
.addStatement(variables
.inline(String
.class, names
.rawQuery(), "$N", rawQuery
));
232 if (Buckets
.hasEntries(config
.parameters())) {
233 final var indexFieldName
= fields
.constantSqlStatementParameterIndexFieldName(config
);
234 builder
.addStatement(variables
.inline(TypicalTypes
.MAP_OF_STRING_AND_ARRAY_OF_INTS
,
235 names
.indexVariable(), "$N", indexFieldName
))
236 .add(logging
.indexPicked(indexFieldName
));
239 return builder
.build();
242 private void finalizeCase(final CodeBlock
.Builder builder
, final SqlConfiguration config
) {
243 if (logging
.isEnabled()) {
244 final var rawQuery
= fields
.constantRawSqlStatementFieldName(config
);
245 builder
.addStatement("$N = $N", names
.rawQuery(), rawQuery
);
247 if (Buckets
.hasEntries(config
.parameters())) {
248 final var indexName
= fields
.constantSqlStatementParameterIndexFieldName(config
);
249 builder
.addStatement("$N = $N", names
.indexVariable(), indexName
)
250 .add(logging
.vendorIndexPicked(indexName
));
252 builder
.addStatement("break$<");
256 public CodeBlock
logExecutedQuery(final SqlConfiguration sqlConfiguration
) {
257 final var builder
= CodeBlock
.builder();
258 if (LoggingApis
.NONE
!= runtimeConfiguration
.api().loggingApi()) {
259 builder
.beginControlFlow("if ($L)", logging
.shouldLog());
260 builder
.add(variables
.inline(String
.class, names
.executedQuery(), "$N", names
.rawQuery()));
261 Stream
.ofNullable(sqlConfiguration
.parameters())
262 .flatMap(Collection
::stream
)
263 .forEach(parameter
-> {
264 if (TypeGuesser
.guessTypeName(parameter
.type()).isPrimitive()) {
265 builder
.add("\n$>.replace($S, $T.valueOf($N))$<", ":" + parameter
.name(),
266 String
.class, parameter
.name());
268 builder
.add("\n$>.replace($S, $N == null ? $S : $N.toString())$<",
269 ":" + parameter
.name(), parameter
.name(), "null",
274 builder
.add(logging
.executingQuery());
275 builder
.endControlFlow();
277 return builder
.build();
281 public CodeBlock
logExecutedBatchQuery(final SqlConfiguration sqlConfiguration
) {
282 final var builder
= CodeBlock
.builder();
283 if (LoggingApis
.NONE
!= runtimeConfiguration
.api().loggingApi()) {
284 builder
.beginControlFlow("if ($L)", logging
.shouldLog());
285 builder
.add(variables
.inline(String
.class, names
.executedQuery(), "$N", names
.rawQuery()));
286 Stream
.ofNullable(sqlConfiguration
.parameters())
287 .flatMap(Collection
::stream
)
288 .forEach(parameter
-> {
289 if (TypeGuesser
.guessTypeName(parameter
.type()).isPrimitive()) {
290 builder
.add("\n$>.replace($S, $T.toString($N))$<", ":" + parameter
.name(),
291 Arrays
.class, parameter
.name());
293 builder
.add("\n$>.replace($S, $N == null ? $S : $T.toString($N))$<",
294 ":" + parameter
.name(), parameter
.name(), "null",
295 Arrays
.class, parameter
.name());
299 builder
.add(logging
.executingQuery());
300 builder
.endControlFlow();
302 return builder
.build();
305 private CodeBlock
.Builder
prepareReturnList(final ParameterizedTypeName listOfResults
, final String converterAlias
) {
306 final var java
= runtimeConfiguration
.java();
308 if (!java
.useGenerics()) {
309 template
= CodeBlock
.of("new $T()", ArrayList
.class);
310 } else if (!java
.useDiamondOperator() || java
.useVar()) {
311 template
= CodeBlock
.of("new $T()", ParameterizedTypeName
.get(
312 ClassName
.get(ArrayList
.class), listOfResults
.typeArguments
.get(0)));
314 template
= CodeBlock
.of("new $T<>()", ArrayList
.class);
316 return CodeBlock
.builder()
317 .addStatement(variables
.inline(listOfResults
, names
.list(), template
))
318 .add(controlFlows
.whileHasNext())
319 .addStatement("$N.add($N.$N($N))",
322 converterMethod(converterAlias
),
327 private String
converterMethod(final String converterAlias
) {
328 return runtimeConfiguration
.converter().rowConverters().stream()
329 .filter(converter
-> converterAlias
.equalsIgnoreCase(converter
.alias()))
331 .or(runtimeConfiguration
.converter()::defaultConverter
)
332 .map(ResultRowConverter
::methodName
)
337 public CodeBlock
returnAsList(final ParameterizedTypeName listOfResults
, final String converterAlias
) {
338 return prepareReturnList(listOfResults
, converterAlias
)
339 .addStatement("return $N", names
.list())
344 public CodeBlock
returnAsFirst(final TypeName resultType
, final String converterAlias
) {
345 return prepareReturnList(TypicalTypes
.listOf(resultType
), converterAlias
)
346 .addStatement("return $N.size() > 0 ? $N.get(0) : null", names
.list(), names
.list())
351 public CodeBlock
returnAsOne(final TypeName resultType
, final String converterAlias
) {
352 return prepareReturnList(TypicalTypes
.listOf(resultType
), converterAlias
)
353 .beginControlFlow("if ($N.size() != 1)", names
.list())
354 .addStatement("throw new IllegalStateException()")
356 .addStatement("return $N.get(0)", names
.list())
361 public CodeBlock
returnAsStream(final ParameterizedTypeName listOfResults
, final String converterAlias
) {
362 return prepareReturnList(listOfResults
, converterAlias
)
363 .addStatement("return $N.stream()", names
.list())
368 public CodeBlock
returnAsMulti(final ParameterizedTypeName listOfResults
, final String converterAlias
) {
369 return prepareReturnList(listOfResults
, converterAlias
)
370 .addStatement("return $T.createFrom().iterable($N)", Multi
.class, names
.list())
375 public CodeBlock
returnAsFlowable(final ParameterizedTypeName listOfResults
, final String converterAlias
) {
376 return prepareReturnList(listOfResults
, converterAlias
)
377 .addStatement("return $T.fromIterable($N)", Flowable
.class, names
.list())
382 public CodeBlock
returnAsFlux(final ParameterizedTypeName listOfResults
, final String converterAlias
) {
383 return prepareReturnList(listOfResults
, converterAlias
)
384 .addStatement("return $T.fromIterable($N)", Flux
.class, names
.list())
389 public CodeBlock
streamStateful(final TypeSpec spliterator
, final TypeSpec closer
) {
390 return CodeBlock
.builder()
391 .addStatement("return $T.stream($L, false).onClose($L)",
399 public CodeBlock
createResultState() {
400 return variables
.statement(runtimeConfiguration
.converter().resultStateClass(), names
.state(),
401 code("new $T($N, $N, $N)",
402 runtimeConfiguration
.converter().resultStateClass(),
404 names
.resultSetMetaData(),
405 names
.columnCount()));
409 public CodeBlock
returnNewFlowState() {
410 return CodeBlock
.builder()
411 .addStatement("return new $T($N, $N, $N, $N, $N)", runtimeConfiguration
.converter().flowStateClass(),
415 names
.resultSetMetaData(),
421 public CodeBlock
newFlowable(final TypeSpec initialState
, final TypeSpec generator
, final TypeSpec disposer
) {
422 return CodeBlock
.builder()
423 .addStatement("return $T.generate($L, $L, $L)",
431 private CodeBlock
parameterAssignment(
432 final SqlConfiguration config
,
433 final String codeStatement
,
434 final Function
<String
, Object
[]> parameterSetter
) {
435 final var builder
= CodeBlock
.builder();
436 final var parameters
= config
.parameters();
438 if (parameters
!= null && !parameters
.isEmpty()) {
439 for (final var parameter
: config
.parameters()) {
440 builder
.add(controlFlows
.forLoop(
441 code("final int $N : $N.get($S)",
442 names
.jdbcIndexVariable(),
443 names
.indexVariable(),
446 .addStatement(codeStatement
, parameterSetter
.apply(parameter
.name()))
451 return builder
.build();
455 public CodeBlock
setParameters(final SqlConfiguration config
) {
456 return parameterAssignment(config
, "$N.setObject($N, $N)",
457 parameterName
-> new String
[]{
459 names
.jdbcIndexVariable(),
464 public CodeBlock
setBatchParameters(final SqlConfiguration config
) {
465 return parameterAssignment(config
, "$N.setObject($N, $N[$N])",
466 parameterName
-> new String
[]{
468 names
.jdbcIndexVariable(),