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

io.yupiik.batch.runtime.component.DiffExecutor Maven / Gradle / Ivy

/*
 * Copyright (c) 2021 - Yupiik SAS - https://www.yupiik.com
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package io.yupiik.batch.runtime.component;

import io.yupiik.batch.runtime.component.diff.Diff;
import io.yupiik.batch.runtime.documentation.Component;
import io.yupiik.batch.runtime.sql.SQLBiConsumer;
import io.yupiik.batch.runtime.sql.SQLSupplier;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;

import static io.yupiik.batch.runtime.sql.SQLBiConsumer.noop;

@Component("""
        Enables to apply a `Diff` - from a `DatasetDiffComputer`.
        
        It will apply it in a database represented by the `connectionSupplier` with the provided `commitInterval`.
        The statements are creating using the related factories - `insertFactory`, `updateFactory`, `deleteFactory`.
        
        Finally, `dryRun` toggle enables to simulate the processing without issuing any modification in the database.""")
public class DiffExecutor implements Consumer> {
    private final Logger logger = Logger.getLogger(getClass().getName());
    private final SQLSupplier connectionSupplier;
    private final int commitInterval;
    private final boolean dryRun;
    private final Supplier> insertFactory;
    private final Supplier> updateFactory;
    private final Supplier> deleteFactory;

    public DiffExecutor(final SQLSupplier connectionSupplier,
                        final int commitInterval, final boolean dryRun,
                        final Supplier> insertFactory,
                        final Supplier> updateFactory,
                        final Supplier> deleteFactory) {
        this.connectionSupplier = connectionSupplier;
        this.commitInterval = commitInterval;
        this.dryRun = dryRun;
        this.insertFactory = insertFactory;
        this.updateFactory = updateFactory;
        this.deleteFactory = deleteFactory;
    }

    @Override
    public void accept(final Diff referenceRowDiff) {
        logger.info(() -> "" +
                "Diff summary:\n" +
                "     To Add: " + referenceRowDiff.added().size() + "\n" +
                "  To Remove: " + referenceRowDiff.deleted().size() + "\n" +
                "  To Update: " + referenceRowDiff.updated().size());
        final var prefix = dryRun ? "[d]" : "";
        withCommitInterval(
                referenceRowDiff.added().iterator(), prefix + "[A] Adding ",
                dryRun ? noop() : newInsert());
        withCommitInterval(
                referenceRowDiff.updated().iterator(), prefix + "[U] Updating ",
                dryRun ? noop() : newUpdate());
        withCommitInterval(
                referenceRowDiff.deleted().iterator(), prefix + "[D] Deleting ",
                dryRun ? noop() : newDelete());
    }

    protected SQLBiConsumer newDelete() {
        return deleteFactory.get();
    }

    protected SQLBiConsumer newUpdate() {
        return updateFactory.get();
    }

    protected SQLBiConsumer newInsert() {
        return insertFactory.get();
    }

    private void withCommitInterval(final Iterator rows,
                                    final String logPrefix,
                                    final SQLBiConsumer onRow) {
        if (!rows.hasNext()) {
            return;
        }
        try (final var connection = connectionSupplier.get()) { // todo: retry if connection fails
            while (rows.hasNext()) {
                final boolean autoCommit = connection.getAutoCommit();
                connection.setAutoCommit(false);
                logger.info("[C][S] Starting transaction");
                try {
                    for (int i = 0; i < commitInterval && rows.hasNext(); i++) {
                        final var row = rows.next();
                        logger.info(() -> logPrefix + row);
                        if (onRow != null) {
                            onRow.accept(connection, row);
                        }
                    }
                    if (AutoCloseable.class.isInstance(onRow)) {
                        AutoCloseable.class.cast(onRow).close();
                    }
                    connection.commit();
                } catch (final RuntimeException | SQLException ex) {
                    onException(connection, ex);
                    throw ex;
                } catch (final Exception ex) {
                    onException(connection, ex);
                    throw new IllegalStateException(ex);
                } finally {
                    logger.info("[C][E] Finished transaction");
                    connection.setAutoCommit(autoCommit);
                }
            }
        } catch (final RuntimeException | SQLException sqle) {
            throw new IllegalStateException(sqle);
        }
    }

    private void onException(final Connection connection, final Exception ex) throws SQLException {
        connection.rollback();
        logger.log(Level.SEVERE, ex.getMessage(), ex);
    }

    public static  DiffExecutor applyDiff(final SQLSupplier connectionSupplier,
                                                final int commitInterval, final boolean dryRun,
                                                final Supplier> insertFactory,
                                                final Supplier> updateFactory,
                                                final Supplier> deleteFactory) {
        return new DiffExecutor<>(connectionSupplier, commitInterval, dryRun, insertFactory, updateFactory, deleteFactory);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy