All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.zero88.jooqx.LegacySQLImpl Maven / Gradle / Ivy

package io.zero88.jooqx;

import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.jooq.Configuration;
import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.Param;
import org.jooq.Query;
import org.jooq.conf.ParamType;

import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonArray;
import io.vertx.ext.sql.ResultSet;
import io.vertx.ext.sql.SQLClient;
import io.vertx.ext.sql.SQLConnection;
import io.vertx.ext.sql.SQLOperations;
import io.zero88.jooqx.MiscImpl.BatchResultImpl;
import io.zero88.jooqx.SQLImpl.SQLEI;
import io.zero88.jooqx.SQLImpl.SQLPQ;
import io.zero88.jooqx.adapter.RowConverterStrategy;
import io.zero88.jooqx.adapter.SQLResultAdapter;
import io.zero88.jooqx.adapter.SelectStrategy;
import io.zero88.jooqx.datatype.DataTypeMapperRegistry;

import lombok.Getter;
import lombok.NonNull;
import lombok.experimental.Accessors;

final class LegacySQLImpl {

    interface LegacyInternal
        extends SQLExecutor {

        @Override
        @NonNull LegacySQLPreparedQuery preparedQuery();

        @Override
        @NonNull LegacySQLCollector resultCollector();

    }


    static final class LegacySQLPQ extends SQLPQ implements LegacySQLPreparedQuery {

        @Override
        protected JsonArray doConvert(Map> params, DataTypeMapperRegistry registry,
                                      BiFunction, ?> queryValue) {
            JsonArray array = new JsonArray();
            params.entrySet()
                  .stream()
                  .filter(entry -> !entry.getValue().isInline())
                  .forEachOrdered(etr -> array.add(registry.toDatabaseType(etr.getKey(), etr.getValue(), queryValue)));
            return array;
        }

    }


    static final class LegacySQLRC implements LegacySQLCollector {

        @NonNull
        @Override
        public  List collect(@NonNull ResultSet resultSet, @NonNull RowConverterStrategy strategy) {
            final Map, Integer> map = getColumnMap(resultSet, strategy::lookupField);
            final List results = resultSet.getResults();
            if (strategy.strategy() == SelectStrategy.MANY) {
                return results.stream().map(row -> toRecord(strategy, map, row)).collect(Collectors.toList());
            }
            warnManyResult(results.size() > 1, strategy.strategy());
            return results.stream()
                          .findFirst()
                          .map(row -> toRecord(strategy, map, row))
                          .map(Collections::singletonList)
                          .orElse(new ArrayList<>());
        }

        private  R toRecord(RowConverterStrategy strategy, Map, Integer> map, JsonArray row) {
            return map.keySet().stream().collect(strategy.createCollector(f -> row.getValue(map.get(f))));
        }

        private Map, Integer> getColumnMap(ResultSet rs, Function> lookupField) {
            return IntStream.range(0, rs.getNumColumns())
                            .boxed()
                            .map(i -> Optional.ofNullable(lookupField.apply(rs.getColumnNames().get(i)))
                                              .map(f -> new SimpleEntry<>(f, i))
                                              .orElse(null))
                            .filter(Objects::nonNull)
                            .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
        }

        @Override
        public int batchResultSize(List batchResult) {
            return batchResult.size();
        }

    }


    @Getter
    @Accessors(fluent = true)
    abstract static class LegacySQLEI
        extends SQLEI
        implements LegacyInternal {

        LegacySQLEI(Vertx vertx, DSLContext dsl, S sqlClient, LegacySQLPreparedQuery preparedQuery,
                    LegacySQLCollector resultCollector, SQLErrorConverter errorConverter,
                    DataTypeMapperRegistry typeMapperRegistry) {
            super(vertx, dsl, sqlClient, preparedQuery, resultCollector, errorConverter, typeMapperRegistry);
        }

        @Override
        public final  Future execute(@NonNull Query query, @NonNull SQLResultAdapter adapter) {
            final Promise promise = Promise.promise();
            sqlClient().queryWithParams(preparedQuery().sql(dsl().configuration(), query),
                                        preparedQuery().bindValues(query, typeMapperRegistry()), promise);
            return promise.future()
                          .map(rs -> adapter.collect(rs, resultCollector(), dsl(), typeMapperRegistry()))
                          .otherwise(errorConverter()::reThrowError);
        }

        @Override
        public final Future batch(@NonNull Query query, @NonNull BindBatchValues bindBatchValues) {
            final Promise> promise = Promise.promise();
            openConn().map(c -> c.batchWithParams(preparedQuery().sql(dsl().configuration(), query),
                                                  preparedQuery().bindValues(query, bindBatchValues,
                                                                             typeMapperRegistry()), promise));
            return promise.future()
                          .map(r -> new LegacySQLRC().batchResultSize(r))
                          .map(s -> BatchResultImpl.create(bindBatchValues.size(), s))
                          .otherwise(errorConverter()::reThrowError);
        }

        protected abstract Future openConn();

        @Override
        protected final LegacySQLPreparedQuery defPrepareQuery() {
            return new LegacySQLPQ();
        }

        @Override
        protected final LegacySQLCollector defResultCollector() {
            return new LegacySQLRC();
        }

    }


    @Getter
    @Accessors(fluent = true)
    static final class LegacyJooqxImpl extends LegacySQLEI implements LegacyJooqx {

        LegacyJooqxImpl(Vertx vertx, DSLContext dsl, SQLClient sqlClient, LegacySQLPreparedQuery preparedQuery,
                        LegacySQLCollector resultCollector, SQLErrorConverter errorConverter,
                        DataTypeMapperRegistry typeMapperRegistry) {
            super(vertx, dsl, sqlClient, preparedQuery, resultCollector, errorConverter, typeMapperRegistry);
        }

        @Override
        @SuppressWarnings("unchecked")
        public @NonNull LegacyJooqxTx transaction() {
            return new LegacyJooqTxImpl(this);
        }

        @Override
        protected LegacyJooqxImpl withSqlClient(@NonNull SQLClient sqlClient) {
            throw new UnsupportedOperationException("No need");
        }

        protected Future openConn() {
            final Promise promise = Promise.promise();
            sqlClient().getConnection(ar -> {
                if (ar.failed()) {
                    promise.fail(transientConnFailed("Unable open SQL connection", ar.cause()));
                } else {
                    promise.complete(ar.result());
                }
            });
            return promise.future();
        }

    }


    @Getter
    @Accessors(fluent = true)
    static final class LegacyJooqTxImpl extends LegacySQLEI implements LegacyJooqxTx {

        private final LegacySQLEI delegate;

        LegacyJooqTxImpl(Vertx vertx, DSLContext dsl, SQLConnection sqlClient, LegacySQLPreparedQuery preparedQuery,
                         LegacySQLCollector resultCollector, SQLErrorConverter errorConverter,
                         DataTypeMapperRegistry typeMapperRegistry) {
            super(vertx, dsl, sqlClient, preparedQuery, resultCollector, errorConverter, typeMapperRegistry);
            this.delegate = null;
        }

        LegacyJooqTxImpl(@NonNull LegacySQLEI delegate) {
            super(delegate.vertx(), delegate.dsl(), null, delegate.preparedQuery(), delegate.resultCollector(),
                  delegate.errorConverter(), delegate.typeMapperRegistry());
            this.delegate = delegate;
        }

        @Override
        public  Future run(@NonNull Function> block) {
            final Promise promise = Promise.promise();
            delegate.openConn().map(conn -> conn.setAutoCommit(false, committable -> {
                if (committable.failed()) {
                    failed(conn, promise, transientConnFailed("Unable begin transaction", committable.cause()));
                } else {
                    block.apply(withSqlClient(conn))
                         .onSuccess(r -> commit(conn, promise, r))
                         .onFailure(t -> rollback(conn, promise, t));
                }
            }));
            return promise.future();
        }

        @Override
        protected Future openConn() {
            return Future.succeededFuture(sqlClient());
        }

        @Override
        protected LegacyJooqTxImpl withSqlClient(@NonNull SQLConnection sqlConn) {
            return new LegacyJooqTxImpl(vertx(), dsl(), sqlConn, preparedQuery(), resultCollector(), errorConverter(),
                                        typeMapperRegistry());
        }

        private  void commit(@NonNull SQLConnection conn, @NonNull Promise promise, X output) {
            conn.commit(v -> {
                if (v.succeeded()) {
                    promise.complete(output);
                    conn.close();
                } else {
                    rollback(conn, promise, errorConverter().handle(v.cause()));
                }
            });
        }

        private  void rollback(@NonNull SQLConnection conn, @NonNull Promise promise, @NonNull Throwable t) {
            conn.rollback(rb -> {
                if (!rb.succeeded()) {
                    t.addSuppressed(rb.cause());
                }
                failed(conn, promise, t);
            });
        }

        private  void failed(@NonNull SQLConnection conn, @NonNull Promise promise, @NonNull Throwable t) {
            promise.fail(t);
            conn.close();
        }

    }

}