implement Mutiny read method for JDBC
[yosql.git] / yosql-dao / yosql-dao-jdbc / src / main / java / wtf / metio / yosql / dao / jdbc / DefaultJdbcBlocks.java
blob482be925bcd28583ad655a9d590d1cfa6f00f950
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 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;
23 import java.sql.*;
24 import java.util.*;
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;
54 this.blocks = blocks;
55 this.names = names;
56 this.variables = variables;
57 this.controlFlows = controlFlows;
58 this.config = config;
59 this.jdbcFields = jdbcFields;
60 this.jdbcMethods = jdbcMethods;
61 this.logging = logging;
64 @Override
65 public CodeBlock connectionVariable() {
66 return variables.inline(Connection.class, names.connection(), jdbcMethods.dataSource().getConnection());
69 @Override
70 public CodeBlock statementVariable() {
71 return variables.inline(PreparedStatement.class, names.statement(),
72 jdbcMethods.connection().prepareStatement());
75 @Override
76 public CodeBlock callableVariable() {
77 return variables.inline(CallableStatement.class, names.statement(),
78 jdbcMethods.connection().prepareCallable());
81 @Override
82 public CodeBlock readMetaData() {
83 return variables.statement(ResultSetMetaData.class, names.resultSetMetaData(),
84 jdbcMethods.resultSet().getMetaData());
87 @Override
88 public CodeBlock readColumnCount() {
89 return variables.statement(int.class, names.columnCount(),
90 jdbcMethods.resultSetMetaData().getColumnCount());
93 @Override
94 public CodeBlock resultSetVariable() {
95 return variables.inline(ResultSet.class, names.resultSet(),
96 jdbcMethods.statement().executeQuery());
99 @Override
100 public CodeBlock getResultSet() {
101 return controlFlows.tryWithResource(variables.inline(ResultSet.class, names.resultSet(),
102 jdbcMethods.statement().getResultSet()));
105 @Override
106 public CodeBlock resultSetVariableStatement() {
107 return variables.statement(ResultSet.class, names.resultSet(),
108 jdbcMethods.statement().executeQuery());
111 @Override
112 public CodeBlock returnExecuteUpdate() {
113 return blocks.returnValue(jdbcMethods.statement().executeUpdate());
116 @Override
117 public CodeBlock executeForReturning() {
118 return jdbcMethods.statement().execute();
121 @Override
122 public CodeBlock executeBatch() {
123 return blocks.returnValue(jdbcMethods.statement().executeBatch());
126 @Override
127 public CodeBlock closeResultSet() {
128 return blocks.close(names.resultSet());
131 @Override
132 public CodeBlock closePrepareStatement() {
133 return blocks.close(names.statement());
136 @Override
137 public CodeBlock closeConnection() {
138 return blocks.close(names.connection());
141 @Override
142 public CodeBlock closeState() {
143 return blocks.close(names.state());
146 @Override
147 public CodeBlock executeStatement() {
148 return controlFlows.tryWithResource(resultSetVariable());
151 @Override
152 public CodeBlock openConnection() {
153 return controlFlows.tryWithResource(connectionVariable());
156 @Override
157 public CodeBlock tryPrepareCallable() {
158 return controlFlows.tryWithResource(callableVariable());
161 @Override
162 public CodeBlock createStatement() {
163 return controlFlows.tryWithResource(statementVariable());
166 @Override
167 public CodeBlock prepareBatch(final SqlConfiguration config) {
168 return controlFlows.forLoop(
169 code("int $N = 0; $N < $N.length; $N++",
170 names.batch(),
171 names.batch(),
172 config.parameters().get(0).name(),
173 names.batch()),
174 CodeBlock.builder()
175 .add(setBatchParameters(config))
176 .addStatement(jdbcMethods.statement().addBatch())
177 .build());
180 @Override
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()))
198 .forEach(config -> {
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()))
208 .findFirst();
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);
216 } else {
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();
222 } else {
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$<");
254 @Override
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()) {
264 builder
265 .add("\n$>.replace($S, $T.valueOf($N))$<", ":" + parameter.name(),
266 String.class, parameter.name());
267 } else {
268 builder
269 .add("\n$>.replace($S, $N == null ? $S : $N.toString())$<",
270 ":" + parameter.name(), parameter.name(), "null",
271 parameter.name());
274 builder.add(";\n");
275 builder.add(logging.executingQuery());
276 builder.endControlFlow();
278 return builder.build();
281 @Override
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()) {
291 builder
292 .add("\n$>.replace($S, $T.toString($N))$<", ":" + parameter.name(),
293 Arrays.class, parameter.name());
294 } else {
295 builder
296 .add("\n$>.replace($S, $N == null ? $S : $T.toString($N))$<",
297 ":" + parameter.name(), parameter.name(), "null",
298 Arrays.class, parameter.name());
301 builder.add(";\n");
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();
310 CodeBlock template;
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)));
316 } else {
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))",
323 names.list(),
324 converterAlias,
325 converterMethod(converterAlias),
326 names.state())
327 .endControlFlow();
330 private String converterMethod(final String converterAlias) {
331 return config.rowConverters().stream()
332 .filter(converter -> converterAlias.equalsIgnoreCase(converter.alias()))
333 .findFirst()
334 .or(config::defaultConverter)
335 .map(ResultRowConverter::methodName)
336 .orElse("apply");
339 @Override
340 public CodeBlock returnAsList(final ParameterizedTypeName listOfResults, final String converterAlias) {
341 return prepareReturnList(listOfResults, converterAlias)
342 .addStatement("return $N", names.list())
343 .build();
346 @Override
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())
350 .build();
353 @Override
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()")
358 .endControlFlow()
359 .addStatement("return $N.get(0)", names.list())
360 .build();
363 @Override
364 public CodeBlock returnAsStream(final ParameterizedTypeName listOfResults, final String converterAlias) {
365 return prepareReturnList(listOfResults, converterAlias)
366 .addStatement("return $N.stream()", names.list())
367 .build();
370 @Override
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())
374 .build();
377 @Override
378 public CodeBlock streamStateful(final TypeSpec spliterator, final TypeSpec closer) {
379 return CodeBlock.builder()
380 .addStatement("return $T.stream($L, false).onClose($L)",
381 StreamSupport.class,
382 spliterator,
383 closer)
384 .build();
387 @Override
388 public CodeBlock createResultState() {
389 return variables.statement(config.resultStateClass(), names.state(),
390 code("new $T($N, $N, $N)",
391 config.resultStateClass(),
392 names.resultSet(),
393 names.resultSetMetaData(),
394 names.columnCount()));
397 @Override
398 public CodeBlock returnNewFlowState() {
399 return CodeBlock.builder()
400 .addStatement("return new $T($N, $N, $N, $N, $N)", config.flowStateClass(),
401 names.connection(),
402 names.statement(),
403 names.resultSet(),
404 names.resultSetMetaData(),
405 names.columnCount())
406 .build();
409 @Override
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)",
413 Flowable.class,
414 initialState,
415 generator,
416 disposer)
417 .build();
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(),
433 parameter.name()),
434 CodeBlock.builder()
435 .addStatement(codeStatement, parameterSetter.apply(parameter.name()))
436 .build()));
440 return builder.build();
443 @Override
444 public CodeBlock setParameters(final SqlConfiguration config) {
445 return parameterAssignment(config, "$N.setObject($N, $N)",
446 parameterName -> new String[]{
447 names.statement(),
448 names.jdbcIndexVariable(),
449 parameterName});
452 @Override
453 public CodeBlock setBatchParameters(final SqlConfiguration config) {
454 return parameterAssignment(config, "$N.setObject($N, $N[$N])",
455 parameterName -> new String[]{
456 names.statement(),
457 names.jdbcIndexVariable(),
458 parameterName,
459 names.batch()});