fix build
[yosql.git] / yosql-dao / yosql-dao-jdbc / src / main / java / wtf / metio / yosql / dao / jdbc / DefaultJdbcBlocks.java
bloba6cacff35a67c41d2848de179400e30904e4217e
1 /*
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
5 * in the LICENSE file.
6 */
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;
28 import java.sql.*;
29 import java.util.*;
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,
52 final Fields fields,
53 final JdbcMethods jdbcMethods,
54 final LoggingGenerator logging) {
55 this.runtimeConfiguration = runtimeConfiguration;
56 this.blocks = blocks;
57 this.names = runtimeConfiguration.names();
58 this.variables = variables;
59 this.controlFlows = controlFlows;
60 this.fields = fields;
61 this.jdbcMethods = jdbcMethods;
62 this.logging = logging;
65 @Override
66 public CodeBlock connectionVariable() {
67 return variables.inline(Connection.class, names.connection(), jdbcMethods.dataSource().getConnection());
70 @Override
71 public CodeBlock statementVariable() {
72 return variables.inline(PreparedStatement.class, names.statement(),
73 jdbcMethods.connection().prepareStatement());
76 @Override
77 public CodeBlock callableVariable() {
78 return variables.inline(CallableStatement.class, names.statement(),
79 jdbcMethods.connection().prepareCallable());
82 @Override
83 public CodeBlock readMetaData() {
84 return variables.statement(ResultSetMetaData.class, names.resultSetMetaData(),
85 jdbcMethods.resultSet().getMetaData());
88 @Override
89 public CodeBlock readColumnCount() {
90 return variables.statement(int.class, names.columnCount(),
91 jdbcMethods.resultSetMetaData().getColumnCount());
94 @Override
95 public CodeBlock resultSetVariable() {
96 return variables.inline(ResultSet.class, names.resultSet(),
97 jdbcMethods.statement().executeQuery());
100 @Override
101 public CodeBlock getResultSet() {
102 return controlFlows.tryWithResource(variables.inline(ResultSet.class, names.resultSet(),
103 jdbcMethods.statement().getResultSet()));
106 @Override
107 public CodeBlock resultSetVariableStatement() {
108 return variables.statement(ResultSet.class, names.resultSet(),
109 jdbcMethods.statement().executeQuery());
112 @Override
113 public CodeBlock returnExecuteUpdate() {
114 return blocks.returnValue(jdbcMethods.statement().executeUpdate());
117 @Override
118 public CodeBlock executeForReturning() {
119 return jdbcMethods.statement().execute();
122 @Override
123 public CodeBlock executeBatch() {
124 return blocks.returnValue(jdbcMethods.statement().executeBatch());
127 @Override
128 public CodeBlock closeResultSet() {
129 return blocks.close(names.resultSet());
132 @Override
133 public CodeBlock closePrepareStatement() {
134 return blocks.close(names.statement());
137 @Override
138 public CodeBlock closeConnection() {
139 return blocks.close(names.connection());
142 @Override
143 public CodeBlock closeState() {
144 return blocks.close(names.state());
147 @Override
148 public CodeBlock executeStatement() {
149 return controlFlows.tryWithResource(resultSetVariable());
152 @Override
153 public CodeBlock openConnection() {
154 return controlFlows.tryWithResource(connectionVariable());
157 @Override
158 public CodeBlock tryPrepareCallable() {
159 return controlFlows.tryWithResource(callableVariable());
162 @Override
163 public CodeBlock createStatement() {
164 return controlFlows.tryWithResource(statementVariable());
167 @Override
168 public CodeBlock prepareBatch(final SqlConfiguration config) {
169 return controlFlows.forLoop(
170 code("int $N = 0; $N < $N.length; $N++",
171 names.batch(),
172 names.batch(),
173 config.parameters().get(0).name(),
174 names.batch()),
175 CodeBlock.builder()
176 .add(setBatchParameters(config))
177 .addStatement(jdbcMethods.statement().addBatch())
178 .build());
181 @Override
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()))
199 .forEach(config -> {
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()))
209 .findFirst();
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);
217 } else {
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();
223 } else {
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$<");
255 @Override
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());
267 } else {
268 builder.add("\n$>.replace($S, $N == null ? $S : $N.toString())$<",
269 ":" + parameter.name(), parameter.name(), "null",
270 parameter.name());
273 builder.add(";\n");
274 builder.add(logging.executingQuery());
275 builder.endControlFlow();
277 return builder.build();
280 @Override
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());
292 } else {
293 builder.add("\n$>.replace($S, $N == null ? $S : $T.toString($N))$<",
294 ":" + parameter.name(), parameter.name(), "null",
295 Arrays.class, parameter.name());
298 builder.add(";\n");
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();
307 CodeBlock template;
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)));
313 } else {
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))",
320 names.list(),
321 converterAlias,
322 converterMethod(converterAlias),
323 names.state())
324 .endControlFlow();
327 private String converterMethod(final String converterAlias) {
328 return runtimeConfiguration.converter().rowConverters().stream()
329 .filter(converter -> converterAlias.equalsIgnoreCase(converter.alias()))
330 .findFirst()
331 .or(runtimeConfiguration.converter()::defaultConverter)
332 .map(ResultRowConverter::methodName)
333 .orElse("apply");
336 @Override
337 public CodeBlock returnAsList(final ParameterizedTypeName listOfResults, final String converterAlias) {
338 return prepareReturnList(listOfResults, converterAlias)
339 .addStatement("return $N", names.list())
340 .build();
343 @Override
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())
347 .build();
350 @Override
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()")
355 .endControlFlow()
356 .addStatement("return $N.get(0)", names.list())
357 .build();
360 @Override
361 public CodeBlock returnAsStream(final ParameterizedTypeName listOfResults, final String converterAlias) {
362 return prepareReturnList(listOfResults, converterAlias)
363 .addStatement("return $N.stream()", names.list())
364 .build();
367 @Override
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())
371 .build();
374 @Override
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())
378 .build();
381 @Override
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())
385 .build();
388 @Override
389 public CodeBlock streamStateful(final TypeSpec spliterator, final TypeSpec closer) {
390 return CodeBlock.builder()
391 .addStatement("return $T.stream($L, false).onClose($L)",
392 StreamSupport.class,
393 spliterator,
394 closer)
395 .build();
398 @Override
399 public CodeBlock createResultState() {
400 return variables.statement(runtimeConfiguration.converter().resultStateClass(), names.state(),
401 code("new $T($N, $N, $N)",
402 runtimeConfiguration.converter().resultStateClass(),
403 names.resultSet(),
404 names.resultSetMetaData(),
405 names.columnCount()));
408 @Override
409 public CodeBlock returnNewFlowState() {
410 return CodeBlock.builder()
411 .addStatement("return new $T($N, $N, $N, $N, $N)", runtimeConfiguration.converter().flowStateClass(),
412 names.connection(),
413 names.statement(),
414 names.resultSet(),
415 names.resultSetMetaData(),
416 names.columnCount())
417 .build();
420 @Override
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)",
424 Flowable.class,
425 initialState,
426 generator,
427 disposer)
428 .build();
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(),
444 parameter.name()),
445 CodeBlock.builder()
446 .addStatement(codeStatement, parameterSetter.apply(parameter.name()))
447 .build()));
451 return builder.build();
454 @Override
455 public CodeBlock setParameters(final SqlConfiguration config) {
456 return parameterAssignment(config, "$N.setObject($N, $N)",
457 parameterName -> new String[]{
458 names.statement(),
459 names.jdbcIndexVariable(),
460 parameterName});
463 @Override
464 public CodeBlock setBatchParameters(final SqlConfiguration config) {
465 return parameterAssignment(config, "$N.setObject($N, $N[$N])",
466 parameterName -> new String[]{
467 names.statement(),
468 names.jdbcIndexVariable(),
469 parameterName,
470 names.batch()});