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

io.questdb.cutlass.text.CopyRequestJob Maven / Gradle / Ivy

/*******************************************************************************
 *     ___                  _   ____  ____
 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
 *   | | | | | | |/ _ \/ __| __| | | |  _ \
 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *    \__\_\\__,_|\___||___/\__|____/|____/
 *
 *  Copyright (c) 2014-2019 Appsicle
 *  Copyright (c) 2019-2023 QuestDB
 *
 *  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.questdb.cutlass.text;

import io.questdb.cairo.*;
import io.questdb.griffin.FunctionFactoryCache;
import io.questdb.griffin.SqlCompiler;
import io.questdb.griffin.SqlException;
import io.questdb.griffin.SqlExecutionContextImpl;
import io.questdb.log.Log;
import io.questdb.log.LogFactory;
import io.questdb.mp.RingQueue;
import io.questdb.mp.Sequence;
import io.questdb.mp.SynchronizedJob;
import io.questdb.std.LongList;
import io.questdb.std.Misc;
import io.questdb.std.Numbers;
import io.questdb.std.datetime.microtime.MicrosecondClock;
import io.questdb.std.str.Path;
import io.questdb.std.str.StringSink;
import org.jetbrains.annotations.Nullable;

import java.io.Closeable;

import static io.questdb.cutlass.text.CopyTask.getPhaseName;
import static io.questdb.cutlass.text.CopyTask.getStatusName;

public class CopyRequestJob extends SynchronizedJob implements Closeable {
    private static final Log LOG = LogFactory.getLog(CopyRequestJob.class);
    private final MicrosecondClock clock;
    private final CopyContext copyContext;
    private final CairoEngine engine;
    private final int logRetentionDays;
    private final LongList partitionsToRemove = new LongList();
    private final RingQueue requestQueue;
    private final Sequence requestSubSeq;
    private final TableToken statusTableToken;
    private final StringSink stringSink = new StringSink();
    private ParallelCsvFileImporter parallelImporter;
    private Path path;
    private SerialCsvFileImporter serialImporter;
    private SqlCompiler sqlCompiler;
    private SqlExecutionContextImpl sqlExecutionContext;
    private CopyRequestTask task;
    private TableWriter writer;
    private final ParallelCsvFileImporter.PhaseStatusReporter updateStatusRef = this::updateStatus;

    public CopyRequestJob(
            final CairoEngine engine,
            int workerCount,
            @Nullable FunctionFactoryCache functionFactoryCache
    ) throws SqlException {
        this.requestQueue = engine.getMessageBus().getTextImportRequestQueue();
        this.requestSubSeq = engine.getMessageBus().getTextImportRequestSubSeq();
        this.parallelImporter = new ParallelCsvFileImporter(engine, workerCount);
        this.serialImporter = new SerialCsvFileImporter(engine);

        CairoConfiguration configuration = engine.getConfiguration();
        this.clock = configuration.getMicrosecondClock();

        this.sqlCompiler = configuration.getFactoryProvider().getSqlCompilerFactory().getInstance(engine, functionFactoryCache, null);
        this.sqlExecutionContext = new SqlExecutionContextImpl(engine, 1);
        this.sqlExecutionContext.with(configuration.getFactoryProvider().getSecurityContextFactory().getRootContext(), null, null);
        final String statusTableName = configuration.getSystemTableNamePrefix() + "text_import_log";
        this.statusTableToken = this.sqlCompiler.query()
                .$("CREATE TABLE IF NOT EXISTS \"")
                .$(statusTableName)
                .$("\" (" +
                        "ts timestamp, " + // 0
                        "id string, " + // 1
                        "table symbol, " + // 2
                        "file symbol, " + // 3
                        "phase symbol, " + // 4
                        "status symbol, " + // 5
                        "message string," + // 6
                        "rows_handled long," + // 7
                        "rows_imported long," + // 8
                        "errors long" + // 9
                        ") timestamp(ts) partition by DAY BYPASS WAL"
                )
                .compile(sqlExecutionContext)
                .getTableToken();

        this.writer = engine.getWriter(statusTableToken, "QuestDB system");
        this.logRetentionDays = configuration.getSqlCopyLogRetentionDays();
        this.copyContext = engine.getCopyContext();
        this.path = new Path();
        this.engine = engine;
        enforceLogRetention();
    }

    @Override
    public void close() {
        this.parallelImporter = Misc.free(parallelImporter);
        this.serialImporter = Misc.free(serialImporter);
        this.writer = Misc.free(this.writer);
        this.sqlCompiler = Misc.free(sqlCompiler);
        this.sqlExecutionContext = Misc.free(sqlExecutionContext);
        this.path = Misc.free(path);
    }

    private void updateStatus(
            byte phase,
            byte status,
            final CharSequence msg,
            long rowsHandled,
            long rowsImported,
            long errors
    ) {
        if (writer != null) {
            stringSink.clear();
            Numbers.appendHex(stringSink, task.getCopyID(), true);
            try {
                TableWriter.Row row = writer.newRow(clock.getTicks());
                row.putStr(1, stringSink);
                row.putSym(2, task.getTableName());
                row.putSym(3, task.getFileName());
                row.putSym(4, CopyTask.getPhaseName(phase));
                row.putSym(5, CopyTask.getStatusName(status));
                row.putStr(6, msg);
                row.putLong(7, rowsHandled);
                row.putLong(8, rowsImported);
                row.putLong(9, errors);
                row.append();
                writer.commit();
            } catch (Throwable th) {
                LOG.error()
                        .$("could not update status table [importId=").$hexPadded(task.getCopyID())
                        .$(", statusTableName=").$(statusTableToken)
                        .$(", tableName=").$(task.getTableName())
                        .$(", fileName=").$(task.getFileName())
                        .$(", phase=").$(getPhaseName(phase))
                        .$(", status=").$(getStatusName(phase))
                        .$(", msg=").$(msg)
                        .$(", rowsHandled=").$(rowsHandled)
                        .$(", rowsImported=").$(rowsImported)
                        .$(", errors=").$(errors)
                        .$(", error=`").$(th).$('`')
                        .I$();
                writer = Misc.free(writer);
            }

            // if we closed the writer, we need to reopen it again
            if (writer == null) {
                try {
                    writer = engine.getWriter(statusTableToken, "QuestDB system");
                } catch (Throwable e) {
                    LOG.error()
                            .$("could not re-open writer [table=").$(statusTableToken)
                            .$(", error=`").$(e).$('`')
                            .I$();
                }
            }
        }
    }

    private boolean useParallelImport() {
        TableToken tableToken = engine.getTableTokenIfExists(task.getTableName());
        if (engine.getTableStatus(path, tableToken) != TableUtils.TABLE_EXISTS) {
            return task.getPartitionBy() >= 0 && task.getPartitionBy() != PartitionBy.NONE;
        }
        try (TableReader reader = engine.getReader(tableToken)) {
            return PartitionBy.isPartitioned(reader.getPartitionedBy());
        }
    }

    void enforceLogRetention() {
        if (writer != null) {
            if (logRetentionDays < 1) {
                writer.truncate();
                return;
            }
            if (writer.getPartitionCount() > 0) {
                partitionsToRemove.clear();
                for (int i = writer.getPartitionCount() - logRetentionDays - 1; i > -1; i--) {
                    partitionsToRemove.add(writer.getPartitionTimestamp(i));
                }

                for (int i = 0, sz = partitionsToRemove.size(); i < sz; i++) {
                    writer.removePartition(partitionsToRemove.getQuick(i));
                }
            }
        }
    }

    @Override
    protected boolean runSerially() {
        long cursor = requestSubSeq.next();
        if (cursor > -1) {
            task = requestQueue.get(cursor);
            try {
                if (useParallelImport()) {
                    parallelImporter.of(
                            task.getTableName(),
                            task.getFileName(),
                            task.getCopyID(),
                            task.getPartitionBy(),
                            task.getDelimiter(),
                            task.getTimestampColumnName(),
                            task.getTimestampFormat(),
                            task.isHeaderFlag(),
                            copyContext.getCircuitBreaker(),
                            task.getAtomicity()
                    );
                    parallelImporter.setStatusReporter(updateStatusRef);
                    parallelImporter.process();
                } else {
                    serialImporter.of(
                            task.getTableName(),
                            task.getFileName(),
                            task.getCopyID(),
                            task.getDelimiter(),
                            task.getTimestampColumnName(),
                            task.getTimestampFormat(),
                            task.isHeaderFlag(),
                            copyContext.getCircuitBreaker(),
                            task.getAtomicity()
                    );
                    serialImporter.setStatusReporter(updateStatusRef);
                    serialImporter.process(task.getSecurityContext());
                }
            } catch (TextImportException e) {
                updateStatus(
                        CopyTask.NO_PHASE,
                        e.isCancelled() ? CopyTask.STATUS_CANCELLED : CopyTask.STATUS_FAILED,
                        e.getMessage(),
                        0,
                        0,
                        0
                );
            } finally {
                requestSubSeq.done(cursor);
                copyContext.clear();
            }
            enforceLogRetention();
            return true;
        }
        return false;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy