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 wtf
.metio
.yosql
.codegen
.api
.ControlFlows
;
14 import wtf
.metio
.yosql
.codegen
.api
.Variables
;
15 import wtf
.metio
.yosql
.codegen
.blocks
.GenericBlocks
;
16 import wtf
.metio
.yosql
.internals
.javapoet
.TypicalTypes
;
17 import wtf
.metio
.yosql
.internals
.jdk
.Buckets
;
18 import wtf
.metio
.yosql
.logging
.api
.LoggingGenerator
;
19 import wtf
.metio
.yosql
.models
.constants
.api
.LoggingApis
;
20 import wtf
.metio
.yosql
.models
.immutables
.*;
21 import wtf
.metio
.yosql
.models
.sql
.ResultRowConverter
;
25 import java
.util
.function
.Function
;
26 import java
.util
.stream
.Stream
;
27 import java
.util
.stream
.StreamSupport
;
29 import static wtf
.metio
.yosql
.codegen
.blocks
.CodeBlocks
.code
;
31 public final class DefaultJdbcBlocks
implements JdbcBlocks
{
33 private final RuntimeConfiguration runtimeConfiguration
;
34 private final GenericBlocks blocks
;
35 private final ControlFlows controlFlows
;
36 private final NamesConfiguration names
;
37 private final Variables variables
;
38 private final JdbcConfiguration config
;
39 private final JdbcFields jdbcFields
;
40 private final JdbcMethods jdbcMethods
;
41 private final LoggingGenerator logging
;
43 public DefaultJdbcBlocks(
44 final RuntimeConfiguration runtimeConfiguration
,
45 final GenericBlocks blocks
,
46 final ControlFlows controlFlows
,
47 final NamesConfiguration names
,
48 final Variables variables
,
49 final JdbcConfiguration config
,
50 final JdbcFields jdbcFields
,
51 final JdbcMethods jdbcMethods
,
52 final LoggingGenerator logging
) {
53 this.runtimeConfiguration
= runtimeConfiguration
;
56 this.variables
= variables
;
57 this.controlFlows
= controlFlows
;
59 this.jdbcFields
= jdbcFields
;
60 this.jdbcMethods
= jdbcMethods
;
61 this.logging
= logging
;
65 public CodeBlock
connectionVariable() {
66 return variables
.inline(Connection
.class, names
.connection(), jdbcMethods
.dataSource().getConnection());
70 public CodeBlock
statementVariable() {
71 return variables
.inline(PreparedStatement
.class, names
.statement(),
72 jdbcMethods
.connection().prepareStatement());
76 public CodeBlock
callableVariable() {
77 return variables
.inline(CallableStatement
.class, names
.statement(),
78 jdbcMethods
.connection().prepareCallable());
82 public CodeBlock
readMetaData() {
83 return variables
.statement(ResultSetMetaData
.class, names
.resultSetMetaData(),
84 jdbcMethods
.resultSet().getMetaData());
88 public CodeBlock
readColumnCount() {
89 return variables
.statement(int.class, names
.columnCount(),
90 jdbcMethods
.resultSetMetaData().getColumnCount());
94 public CodeBlock
resultSetVariable() {
95 return variables
.inline(ResultSet
.class, names
.resultSet(),
96 jdbcMethods
.statement().executeQuery());
100 public CodeBlock
getResultSet() {
101 return controlFlows
.tryWithResource(variables
.inline(ResultSet
.class, names
.resultSet(),
102 jdbcMethods
.statement().getResultSet()));
106 public CodeBlock
resultSetVariableStatement() {
107 return variables
.statement(ResultSet
.class, names
.resultSet(),
108 jdbcMethods
.statement().executeQuery());
112 public CodeBlock
returnExecuteUpdate() {
113 return blocks
.returnValue(jdbcMethods
.statement().executeUpdate());
117 public CodeBlock
executeForReturning() {
118 return jdbcMethods
.statement().execute();
122 public CodeBlock
executeBatch() {
123 return blocks
.returnValue(jdbcMethods
.statement().executeBatch());
127 public CodeBlock
closeResultSet() {
128 return blocks
.close(names
.resultSet());
132 public CodeBlock
closePrepareStatement() {
133 return blocks
.close(names
.statement());
137 public CodeBlock
closeConnection() {
138 return blocks
.close(names
.connection());
142 public CodeBlock
closeState() {
143 return blocks
.close(names
.state());
147 public CodeBlock
executeStatement() {
148 return controlFlows
.tryWithResource(resultSetVariable());
152 public CodeBlock
openConnection() {
153 return controlFlows
.tryWithResource(connectionVariable());
157 public CodeBlock
tryPrepareCallable() {
158 return controlFlows
.tryWithResource(callableVariable());
162 public CodeBlock
createStatement() {
163 return controlFlows
.tryWithResource(statementVariable());
167 public CodeBlock
prepareBatch(final SqlConfiguration config
) {
168 return controlFlows
.forLoop(
169 code("int $N = 0; $N < $N.length; $N++",
172 config
.parameters().get(0).name(),
175 .add(setBatchParameters(config
))
176 .addStatement(jdbcMethods
.statement().addBatch())
181 public CodeBlock
pickVendorQuery(final List
<SqlStatement
> sqlStatements
) {
182 final var builder
= CodeBlock
.builder();
183 if (sqlStatements
.size() > 1) {
184 builder
.addStatement(variables
.inline(DatabaseMetaData
.class, names
.databaseMetaData(),
185 jdbcMethods
.connection().getMetaData()));
186 builder
.addStatement(variables
.inline(String
.class, names
.databaseProductName(),
187 jdbcMethods
.databaseMetaData().getDatabaseProductName()))
188 .add(logging
.vendorDetected());
189 if (logging
.isEnabled()) {
190 builder
.addStatement("$T $N = null", String
.class, names
.rawQuery());
192 builder
.addStatement("$T $N = null", String
.class, names
.query())
193 .addStatement("$T $N = null", TypicalTypes
.MAP_OF_STRING_AND_ARRAY_OF_INTS
, names
.indexVariable())
194 .beginControlFlow("switch ($N)", names
.databaseProductName());
195 sqlStatements
.stream()
196 .map(SqlStatement
::getConfiguration
)
197 .filter(config
-> Objects
.nonNull(config
.vendor()))
199 final var query
= jdbcFields
.constantSqlStatementFieldName(config
);
200 builder
.add("case $S:\n", config
.vendor())
201 .addStatement("$>$N = $N", names
.query(), query
)
202 .add(logging
.vendorQueryPicked(query
));
203 finalizeCase(builder
, config
);
205 final var firstConfigWithoutVendor
= sqlStatements
.stream()
206 .map(SqlStatement
::getConfiguration
)
207 .filter(config
-> Objects
.isNull(config
.vendor()))
209 if (firstConfigWithoutVendor
.isPresent()) {
210 final var config
= firstConfigWithoutVendor
.get();
211 final var query
= jdbcFields
.constantSqlStatementFieldName(config
);
212 builder
.add("default:\n")
213 .addStatement("$>$N = $N", names
.query(), query
)
214 .add(logging
.vendorQueryPicked(query
));
215 finalizeCase(builder
, config
);
217 builder
.add("default:\n")
218 .addStatement("$>throw new $T($T.format($S, $N))$<", IllegalStateException
.class, String
.class,
219 "No suitable query defined for vendor [%s]", names
.databaseProductName());
221 builder
.endControlFlow();
223 final var config
= sqlStatements
.get(0).getConfiguration();
224 final var query
= jdbcFields
.constantSqlStatementFieldName(config
);
225 builder
.addStatement(variables
.inline(String
.class, names
.query(), "$N", query
))
226 .add(logging
.queryPicked(query
));
227 if (logging
.isEnabled()) {
228 final var rawQuery
= jdbcFields
.constantRawSqlStatementFieldName(config
);
229 builder
.addStatement(variables
.inline(String
.class, names
.rawQuery(), "$N", rawQuery
));
231 if (Buckets
.hasEntries(config
.parameters())) {
232 final var indexFieldName
= jdbcFields
.constantSqlStatementParameterIndexFieldName(config
);
233 builder
.addStatement(variables
.inline(TypicalTypes
.MAP_OF_STRING_AND_ARRAY_OF_INTS
,
234 names
.indexVariable(),"$N", indexFieldName
))
235 .add(logging
.indexPicked(indexFieldName
));
238 return builder
.build();
241 private void finalizeCase(final CodeBlock
.Builder builder
, final SqlConfiguration config
) {
242 if (logging
.isEnabled()) {
243 final var rawQuery
= jdbcFields
.constantRawSqlStatementFieldName(config
);
244 builder
.addStatement("$N = $N", names
.rawQuery(), rawQuery
);
246 if (Buckets
.hasEntries(config
.parameters())) {
247 final var indexName
= jdbcFields
.constantSqlStatementParameterIndexFieldName(config
);
248 builder
.addStatement("$N = $N", names
.indexVariable(), indexName
)
249 .add(logging
.vendorIndexPicked(indexName
));
251 builder
.addStatement("break$<");
255 public CodeBlock
logExecutedQuery(final SqlConfiguration sqlConfiguration
) {
256 final var builder
= CodeBlock
.builder();
257 if (LoggingApis
.NONE
!= runtimeConfiguration
.api().loggingApi()) {
258 builder
.beginControlFlow("if ($L)", logging
.shouldLog());
259 builder
.add(variables
.inline(String
.class, names
.executedQuery(), "$N", names
.rawQuery()));
260 Stream
.ofNullable(sqlConfiguration
.parameters())
261 .flatMap(Collection
::stream
)
262 .forEach(parameter
-> {
263 if (TypeGuesser
.guessTypeName(parameter
.type()).isPrimitive()) {
265 .add("\n$>.replace($S, $T.valueOf($N))$<", ":" + parameter
.name(),
266 String
.class, parameter
.name());
269 .add("\n$>.replace($S, $N == null ? $S : $N.toString())$<",
270 ":" + parameter
.name(), parameter
.name(), "null",
275 builder
.add(logging
.executingQuery());
276 builder
.endControlFlow();
278 return builder
.build();
282 public CodeBlock
logExecutedBatchQuery(final SqlConfiguration sqlConfiguration
) {
283 final var builder
= CodeBlock
.builder();
284 if (LoggingApis
.NONE
!= runtimeConfiguration
.api().loggingApi()) {
285 builder
.beginControlFlow("if ($L)", logging
.shouldLog());
286 builder
.add(variables
.inline(String
.class, names
.executedQuery(), "$N", names
.rawQuery()));
287 Stream
.ofNullable(sqlConfiguration
.parameters())
288 .flatMap(Collection
::stream
)
289 .forEach(parameter
-> {
290 if (TypeGuesser
.guessTypeName(parameter
.type()).isPrimitive()) {
292 .add("\n$>.replace($S, $T.toString($N))$<", ":" + parameter
.name(),
293 Arrays
.class, parameter
.name());
296 .add("\n$>.replace($S, $N == null ? $S : $T.toString($N))$<",
297 ":" + parameter
.name(), parameter
.name(), "null",
298 Arrays
.class, parameter
.name());
302 builder
.add(logging
.executingQuery());
303 builder
.endControlFlow();
305 return builder
.build();
308 private CodeBlock
.Builder
prepareReturnList(final ParameterizedTypeName listOfResults
, final String converterAlias
) {
309 final var java
= runtimeConfiguration
.java();
311 if (!java
.useGenerics()) {
312 template
= CodeBlock
.of("new $T()", ArrayList
.class);
313 } else if (!java
.useDiamondOperator() || java
.useVar()) {
314 template
= CodeBlock
.of("new $T()", ParameterizedTypeName
.get(
315 ClassName
.get(ArrayList
.class), listOfResults
.typeArguments
.get(0)));
317 template
= CodeBlock
.of("new $T<>()", ArrayList
.class);
319 return CodeBlock
.builder()
320 .addStatement(variables
.inline(listOfResults
, names
.list(), template
))
321 .add(controlFlows
.whileHasNext())
322 .addStatement("$N.add($N.$N($N))",
325 converterMethod(converterAlias
),
330 private String
converterMethod(final String converterAlias
) {
331 return config
.rowConverters().stream()
332 .filter(converter
-> converterAlias
.equalsIgnoreCase(converter
.alias()))
334 .or(config
::defaultConverter
)
335 .map(ResultRowConverter
::methodName
)
340 public CodeBlock
returnAsList(final ParameterizedTypeName listOfResults
, final String converterAlias
) {
341 return prepareReturnList(listOfResults
, converterAlias
)
342 .addStatement("return $N", names
.list())
347 public CodeBlock
returnAsFirst(final TypeName resultType
, final String converterAlias
) {
348 return prepareReturnList(TypicalTypes
.listOf(resultType
), converterAlias
)
349 .addStatement("return $N.size() > 0 ? $N.get(0) : null", names
.list(), names
.list())
354 public CodeBlock
returnAsOne(final TypeName resultType
, final String converterAlias
) {
355 return prepareReturnList(TypicalTypes
.listOf(resultType
), converterAlias
)
356 .beginControlFlow("if ($N.size() != 1)", names
.list())
357 .addStatement("throw new IllegalStateException()")
359 .addStatement("return $N.get(0)", names
.list())
364 public CodeBlock
returnAsStream(final ParameterizedTypeName listOfResults
, final String converterAlias
) {
365 return prepareReturnList(listOfResults
, converterAlias
)
366 .addStatement("return $N.stream()", names
.list())
371 public CodeBlock
returnAsMulti(final ParameterizedTypeName listOfResults
, final String converterAlias
) {
372 return prepareReturnList(listOfResults
, converterAlias
)
373 .addStatement("return $T.createFrom().iterable($N)", Multi
.class, names
.list())
378 public CodeBlock
streamStateful(final TypeSpec spliterator
, final TypeSpec closer
) {
379 return CodeBlock
.builder()
380 .addStatement("return $T.stream($L, false).onClose($L)",
388 public CodeBlock
createResultState() {
389 return variables
.statement(config
.resultStateClass(), names
.state(),
390 code("new $T($N, $N, $N)",
391 config
.resultStateClass(),
393 names
.resultSetMetaData(),
394 names
.columnCount()));
398 public CodeBlock
returnNewFlowState() {
399 return CodeBlock
.builder()
400 .addStatement("return new $T($N, $N, $N, $N, $N)", config
.flowStateClass(),
404 names
.resultSetMetaData(),
410 public CodeBlock
newFlowable(final TypeSpec initialState
, final TypeSpec generator
, final TypeSpec disposer
) {
411 return CodeBlock
.builder()
412 .addStatement("return $T.generate($L, $L, $L)",
420 private CodeBlock
parameterAssignment(
421 final SqlConfiguration config
,
422 final String codeStatement
,
423 final Function
<String
, Object
[]> parameterSetter
) {
424 final var builder
= CodeBlock
.builder();
425 final var parameters
= config
.parameters();
427 if (parameters
!= null && !parameters
.isEmpty()) {
428 for (final var parameter
: config
.parameters()) {
429 builder
.add(controlFlows
.forLoop(
430 code("final int $N : $N.get($S)",
431 names
.jdbcIndexVariable(),
432 names
.indexVariable(),
435 .addStatement(codeStatement
, parameterSetter
.apply(parameter
.name()))
440 return builder
.build();
444 public CodeBlock
setParameters(final SqlConfiguration config
) {
445 return parameterAssignment(config
, "$N.setObject($N, $N)",
446 parameterName
-> new String
[]{
448 names
.jdbcIndexVariable(),
453 public CodeBlock
setBatchParameters(final SqlConfiguration config
) {
454 return parameterAssignment(config
, "$N.setObject($N, $N[$N])",
455 parameterName
-> new String
[]{
457 names
.jdbcIndexVariable(),