001package org.itsallcode.jdbc.batch;
002
003import static java.util.stream.Collectors.joining;
004
005import java.util.List;
006import java.util.Objects;
007import java.util.function.Function;
008import java.util.logging.Logger;
009
010import org.itsallcode.jdbc.SimpleConnection;
011import org.itsallcode.jdbc.SimplePreparedStatement;
012import org.itsallcode.jdbc.identifier.Identifier;
013
014/**
015 * Builder for {@link PreparedStatementBatch}. Create a new builder instance
016 * using {@link SimpleConnection#preparedStatementBatch()}.
017 */
018public class PreparedStatementBatchBuilder {
019    private static final Logger LOG = Logger.getLogger(PreparedStatementBatchBuilder.class.getName());
020    /** Default maximum batch size. */
021    public static final int DEFAULT_MAX_BATCH_SIZE = 200_000;
022    private final Function<String, SimplePreparedStatement> statementFactory;
023    private String sql;
024    private int maxBatchSize = DEFAULT_MAX_BATCH_SIZE;
025
026    /**
027     * Create a new instance.
028     * 
029     * @param statementFactory factory for creating {@link SimplePreparedStatement}.
030     */
031    public PreparedStatementBatchBuilder(final Function<String, SimplePreparedStatement> statementFactory) {
032        this.statementFactory = statementFactory;
033    }
034
035    /**
036     * Define the SQL statement to be used for the batch job, e.g. {@code INSERT} or
037     * {@code UPDATE}.
038     * 
039     * @param sql SQL statement
040     * @return {@code this} for fluent programming
041     */
042    public PreparedStatementBatchBuilder sql(final String sql) {
043        this.sql = sql;
044        return this;
045    }
046
047    /**
048     * Define table and column names used for generating the {@code INSERT}
049     * statement.
050     * 
051     * @param tableName   table name
052     * @param columnNames column names
053     * @return {@code this} for fluent programming
054     */
055    @SuppressWarnings("java:S3242") // Using List instead of Collection to preserve column order
056    public PreparedStatementBatchBuilder into(final Identifier tableName, final List<Identifier> columnNames) {
057        this.sql = createInsertStatement(tableName, columnNames);
058        return this;
059    }
060
061    /**
062     * Define table and column names used for generating the {@code INSERT}
063     * statement.
064     * 
065     * @param tableName   table name
066     * @param columnNames column names
067     * @return {@code this} for fluent programming
068     */
069    @SuppressWarnings("java:S3242") // Using List instead of Collection to preserve column order
070    public PreparedStatementBatchBuilder into(final String tableName, final List<String> columnNames) {
071        return into(Identifier.simple(tableName), columnNames.stream().map(Identifier::simple).toList());
072    }
073
074    /**
075     * Define maximum batch size, using {@link #DEFAULT_MAX_BATCH_SIZE} as default.
076     * 
077     * @param maxBatchSize maximum batch size
078     * @return {@code this} for fluent programming
079     */
080    public PreparedStatementBatchBuilder maxBatchSize(final int maxBatchSize) {
081        this.maxBatchSize = maxBatchSize;
082        return this;
083    }
084
085    private static String createInsertStatement(final Identifier table, final List<Identifier> columnNames) {
086        final String columns = columnNames.stream().map(Identifier::quote).collect(joining(","));
087        final String placeholders = columnNames.stream().map(n -> "?").collect(joining(","));
088        return "insert into " + table.quote() + " (" + columns + ") values (" + placeholders + ")";
089    }
090
091    /**
092     * Build the batch inserter.
093     * 
094     * @return the batch inserter
095     */
096    public PreparedStatementBatch build() {
097        Objects.requireNonNull(this.sql, "sql");
098        LOG.finest(() -> "Running insert statement '" + sql + "'...");
099        final SimplePreparedStatement statement = statementFactory.apply(sql);
100        return new PreparedStatementBatch(statement, this.maxBatchSize);
101    }
102}