001package org.itsallcode.jdbc.batch;
002
003import java.sql.PreparedStatement;
004import java.util.*;
005import java.util.function.Function;
006import java.util.stream.Stream;
007
008import org.itsallcode.jdbc.*;
009import org.itsallcode.jdbc.identifier.Identifier;
010
011/**
012 * Builder for {@link PreparedStatement} batch jobs for a {@link Stream} or
013 * {@link Iterable} of row objects using e.g. {@code INSERT} or {@code UPDATE}
014 * statements.
015 * <p>
016 * Create a new instance using
017 * {@link DbOperations#preparedStatementBatch(Class)}.
018 * 
019 * @param <T> row type
020 */
021public class RowPreparedStatementBatchBuilder<T> {
022    private final PreparedStatementBatchBuilder baseBuilder;
023    private RowPreparedStatementSetter<T> mapper;
024    private Iterator<T> rows;
025
026    /**
027     * Create a new instance.
028     * 
029     * @param statementFactory factory for creating {@link SimplePreparedStatement}
030     */
031    public RowPreparedStatementBatchBuilder(final Function<String, SimplePreparedStatement> statementFactory) {
032        this(new PreparedStatementBatchBuilder(statementFactory));
033    }
034
035    RowPreparedStatementBatchBuilder(final PreparedStatementBatchBuilder baseBuilder) {
036        this.baseBuilder = baseBuilder;
037    }
038
039    /**
040     * Define the SQL statement to be used for the batch job, e.g. {@code INSERT} or
041     * {@code UPDATE}.
042     * 
043     * @param sql SQL statement
044     * @return {@code this} for fluent programming
045     */
046    public RowPreparedStatementBatchBuilder<T> sql(final String sql) {
047        this.baseBuilder.sql(sql);
048        return this;
049    }
050
051    /**
052     * Define table and column names used for generating the {@code INSERT}
053     * statement.
054     * 
055     * @param tableName   table name
056     * @param columnNames column names
057     * @return {@code this} for fluent programming
058     */
059    @SuppressWarnings("java:S3242") // Using List instead of Collection to preserve column order
060    public RowPreparedStatementBatchBuilder<T> into(final Identifier tableName, final List<Identifier> columnNames) {
061        this.baseBuilder.into(tableName, columnNames);
062        return this;
063    }
064
065    /**
066     * Define table and column names used for generating the {@code INSERT}
067     * statement.
068     * 
069     * @param tableName   table name
070     * @param columnNames column names
071     * @return {@code this} for fluent programming
072     */
073    @SuppressWarnings("java:S3242") // Using List instead of Collection to preserve column order
074    public RowPreparedStatementBatchBuilder<T> into(final String tableName, final List<String> columnNames) {
075        return into(Identifier.simple(tableName), columnNames.stream().map(Identifier::simple).toList());
076    }
077
078    /**
079     * Define {@link Stream} of rows to insert.
080     * 
081     * @param rows rows to insert
082     * @return {@code this} for fluent programming
083     */
084    public RowPreparedStatementBatchBuilder<T> rows(final Stream<T> rows) {
085        return rows(rows.iterator());
086    }
087
088    /**
089     * Define {@link Iterator} of rows to insert.
090     * 
091     * @param rows rows to insert
092     * @return {@code this} for fluent programming
093     */
094    public RowPreparedStatementBatchBuilder<T> rows(final Iterator<T> rows) {
095        this.rows = rows;
096        return this;
097    }
098
099    /**
100     * Define mapping how rows are converted to {@code Object[]} for inserting.
101     * 
102     * @param rowMapper row mapper
103     * @return {@code this} for fluent programming
104     */
105    public RowPreparedStatementBatchBuilder<T> mapping(final ParamConverter<T> rowMapper) {
106        final RowPreparedStatementSetter<Object[]> setter = new ObjectArrayPreparedStatementSetter();
107        return mapping(
108                (final T row, final PreparedStatement preparedStatement) -> setter.setValues(rowMapper.map(row),
109                        preparedStatement));
110    }
111
112    /**
113     * Define {@link RowPreparedStatementSetter} that sets values of a
114     * {@link PreparedStatement} for each row.
115     * 
116     * @param preparedStatementSetter prepared statement setter
117     * @return {@code this} for fluent programming
118     */
119    public RowPreparedStatementBatchBuilder<T> mapping(final RowPreparedStatementSetter<T> preparedStatementSetter) {
120        this.mapper = preparedStatementSetter;
121        return this;
122    }
123
124    /**
125     * Define maximum batch size, using
126     * {@link PreparedStatementBatchBuilder#DEFAULT_MAX_BATCH_SIZE} as default.
127     * 
128     * @param maxBatchSize maximum batch size
129     * @return {@code this} for fluent programming
130     */
131    public RowPreparedStatementBatchBuilder<T> maxBatchSize(final int maxBatchSize) {
132        this.baseBuilder.maxBatchSize(maxBatchSize);
133        return this;
134    }
135
136    /**
137     * Start the batch insert process using the given rows.
138     */
139    public void start() {
140        Objects.requireNonNull(this.mapper, "mapper");
141        Objects.requireNonNull(this.rows, "rows");
142        try (PreparedStatementBatch batchInsert = baseBuilder.build();
143                RowPreparedStatementBatch<T> rowBatchInsert = new RowPreparedStatementBatch<>(batchInsert,
144                        this.mapper)) {
145            while (rows.hasNext()) {
146                rowBatchInsert.add(rows.next());
147            }
148        }
149    }
150}