SqlBack.java

package space.sunqian.fs.utils.jdbc;

import space.sunqian.annotation.Immutable;
import space.sunqian.annotation.Nonnull;
import space.sunqian.annotation.Nullable;
import space.sunqian.annotation.RetainedParam;
import space.sunqian.fs.collect.ListKit;

import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

final class SqlBack {

    static @Nonnull PreparedStatement createPreparedStatement(
        @Nonnull PreparedSql preparedSql, @Nonnull Connection connection
    ) throws SQLException {
        PreparedStatement statement =
            connection.prepareStatement(preparedSql.preparedSql(), Statement.RETURN_GENERATED_KEYS);
        List<Object> parameters = preparedSql.parameters();
        for (int i = 0; i < parameters.size(); i++) {
            statement.setObject(i + 1, parameters.get(i));
        }
        return statement;
    }

    static @Nonnull PreparedStatement createPreparedStatement(
        @Nonnull PreparedBatchSql preparedBatchSql, @Nonnull Connection connection
    ) throws SQLException {
        PreparedStatement statement =
            connection.prepareStatement(preparedBatchSql.preparedSql(), Statement.RETURN_GENERATED_KEYS);
        statement.clearBatch();
        List<List<Object>> batchParameters = preparedBatchSql.batchParameters();
        for (List<Object> batchParameter : batchParameters) {
            for (int i = 0; i < batchParameter.size(); i++) {
                statement.setObject(i + 1, batchParameter.get(i));
            }
            statement.addBatch();
        }
        return statement;
    }

    static @Nonnull SqlBuilder newBuilder() {
        return new SqlBuilderImpl();
    }

    static <T> @Nonnull SqlQuery<T> newQuery(
        @Nonnull Statement statement,
        @Nonnull Type type
    ) {
        return new SqlQueryImpl<>(statement, type);
    }

    static @Nonnull SqlUpdate newUpdate(@Nonnull Statement statement) {
        return new SqlUpdateImpl(statement);
    }

    static @Nonnull SqlInsert newInsert(
        @Nonnull Statement statement
    ) {
        return new SqlInsertImpl(statement);
    }

    static @Nonnull SqlBatch newBatchResult(
        @Nonnull Statement statement
    ) {
        return new SqlBatchImpl(statement);
    }

    private static final class SqlBuilderImpl implements SqlBuilder {

        private final @Nonnull StringBuilder sqlBuilder = new StringBuilder();
        private @Nullable List<Object> parameters;

        @Override
        public @Nonnull SqlBuilder append(@Nonnull String sql) {
            sqlBuilder.append(sql);
            return this;
        }

        @Override
        public @Nonnull SqlBuilder append(@Nonnull String sql, @Nullable Object param) {
            sqlBuilder.append(sql);
            if (param instanceof Collection<?>) {
                @SuppressWarnings("PatternVariableCanBeUsed")
                Collection<?> collection = (Collection<?>) param;
                parameters().addAll(collection);
                sqlBuilder.append(join(collection));
            } else if (param instanceof Iterable<?>) {
                @SuppressWarnings("PatternVariableCanBeUsed")
                Iterable<?> iterable = (Iterable<?>) param;
                Collection<?> collection = ListKit.toList(iterable);
                parameters().addAll(collection);
                sqlBuilder.append(join(collection));
            } else {
                // Handle single parameter
                sqlBuilder.append("?");
                parameters().add(param);
            }
            return this;
        }

        private @Nonnull List<Object> parameters() {
            if (parameters == null) {
                parameters = new ArrayList<>();
            }
            return parameters;
        }

        private @Nonnull String join(Collection<?> collection) {
            if (collection.isEmpty()) {
                return "";
            }
            int size = collection.size();
            char[] chars = new char[size * 2 - 1];
            chars[0] = '?';
            for (int i = 1; i < chars.length; i += 2) {
                chars[i] = ',';
                chars[i + 1] = '?';
            }
            return new String(chars);
        }

        @Override
        public @Nonnull PreparedSql build() {
            return new PreparedSqlImpl(
                sqlBuilder.toString(),
                parameters == null ? Collections.emptyList() : parameters
            );
        }

        @Override
        public @Nonnull PreparedBatchSql buildBatch() {
            return new PreparedBatchSqlImpl(sqlBuilder.toString());
        }
    }

    private static final class PreparedSqlImpl implements PreparedSql {

        private final @Nonnull String preparedSql;
        private final @Nonnull List<Object> parameters;

        private PreparedSqlImpl(@Nonnull String preparedSql, @Nonnull @RetainedParam List<Object> parameters) {
            this.preparedSql = preparedSql;
            this.parameters = parameters;
        }

        @Override
        public @Nonnull String preparedSql() {
            return preparedSql;
        }

        @Override
        public @Nonnull @Immutable List<Object> parameters() {
            return parameters;
        }
    }

    private static final class PreparedBatchSqlImpl implements PreparedBatchSql {

        private final @Nonnull String preparedSql;
        private final @Nonnull List<List<Object>> batchedParameters = new ArrayList<>();

        private @Nullable Connection connection;

        private PreparedBatchSqlImpl(@Nonnull String preparedSql) {
            this.preparedSql = preparedSql;
        }

        @Override
        public @Nonnull String preparedSql() {
            return preparedSql;
        }

        @Override
        public @Nonnull @Immutable List<@Nonnull List<Object>> batchParameters() {
            return Collections.unmodifiableList(batchedParameters);
        }

        @Override
        public @Nonnull PreparedBatchSql batchParameters(@Nonnull List<@Nonnull List<Object>> batchParameters) {
            batchedParameters.addAll(batchParameters);
            return this;
        }

        @Override
        public @Nonnull PreparedBatchSql parameters(@Nonnull List<Object> parameters) {
            batchedParameters.add(parameters);
            return this;
        }
    }

    private static final class SqlQueryImpl<T> implements SqlQuery<T> {

        private final @Nonnull Statement statement;
        private final @Nonnull Type type;

        private SqlQueryImpl(
            @Nonnull Statement statement,
            @Nonnull Type type
        ) {
            this.statement = statement;
            this.type = type;
        }

        @Override
        public @Nonnull Type type() {
            return type;
        }

        @Override
        public @Nonnull Statement statement() {
            return statement;
        }
    }

    private static final class SqlUpdateImpl implements SqlUpdate {

        private final @Nonnull Statement statement;

        private SqlUpdateImpl(@Nonnull Statement statement) {
            this.statement = statement;
        }

        @Override
        public @Nonnull Statement statement() {
            return statement;
        }
    }

    private static final class SqlInsertImpl implements SqlInsert {

        private final @Nonnull Statement statement;
        private volatile @Nullable List<@Nonnull Object> autoGeneratedKeys;

        private SqlInsertImpl(@Nonnull Statement statement) {
            this.statement = statement;
        }

        @Override
        public @Nonnull Statement statement() {
            return statement;
        }

        @Override
        public @Nonnull List<@Nonnull Object> autoGeneratedKeys() throws SqlRuntimeException {
            List<@Nonnull Object> keys = autoGeneratedKeys;
            if (keys == null) {
                keys = SqlInsert.super.autoGeneratedKeys();
                autoGeneratedKeys = keys;
            }
            return keys;
        }
    }

    private static final class SqlBatchImpl implements SqlBatch {

        private final @Nonnull Statement statement;

        private SqlBatchImpl(@Nonnull Statement statement) {
            this.statement = statement;
        }

        @Override
        public @Nonnull Statement statement() {
            return statement;
        }
    }

    private SqlBack() {
    }
}