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

io.questdb.griffin.engine.ops.CopyFactory 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.griffin.engine.ops;

import io.questdb.MessageBus;
import io.questdb.cairo.AbstractRecordCursorFactory;
import io.questdb.cairo.ColumnType;
import io.questdb.cairo.GenericRecordMetadata;
import io.questdb.cairo.TableColumnMetadata;
import io.questdb.cairo.sql.Record;
import io.questdb.cairo.sql.RecordCursor;
import io.questdb.cutlass.text.AtomicBooleanCircuitBreaker;
import io.questdb.cutlass.text.TextImportExecutionContext;
import io.questdb.cutlass.text.TextImportRequestTask;
import io.questdb.griffin.PlanSink;
import io.questdb.griffin.SqlException;
import io.questdb.griffin.SqlExecutionContext;
import io.questdb.griffin.engine.SingleValueRecordCursor;
import io.questdb.griffin.model.CopyModel;
import io.questdb.mp.MPSequence;
import io.questdb.mp.RingQueue;
import io.questdb.std.Chars;
import io.questdb.std.Numbers;
import io.questdb.std.str.StringSink;

/**
 * Executes COPY statement lazily, i.e. on record cursor initialization, to play
 * nicely with server-side statements in PG Wire and query caching in general.
 */
public class CopyFactory extends AbstractRecordCursorFactory {

    private final static GenericRecordMetadata METADATA = new GenericRecordMetadata();
    private final int atomicity;
    private final byte delimiter;
    private final String fileName;
    private final boolean headerFlag;
    private final StringSink importIdSink = new StringSink();
    private final MessageBus messageBus;
    private final int partitionBy;
    private final ImportIdRecord record = new ImportIdRecord();
    private final SingleValueRecordCursor cursor = new SingleValueRecordCursor(record);
    private final String tableName;
    private final TextImportExecutionContext textImportExecutionContext;
    private final String timestampColumn;
    private final String timestampFormat;

    public CopyFactory(
            MessageBus messageBus,
            TextImportExecutionContext textImportExecutionContext,
            String tableName,
            String fileName,
            CopyModel model
    ) {
        super(METADATA);
        this.messageBus = messageBus;
        this.textImportExecutionContext = textImportExecutionContext;
        this.tableName = tableName;
        this.fileName = fileName;
        this.headerFlag = model.isHeader();
        this.timestampColumn = Chars.toString(model.getTimestampColumnName());
        this.timestampFormat = Chars.toString(model.getTimestampFormat());
        this.delimiter = model.getDelimiter();
        this.partitionBy = model.getPartitionBy();
        this.atomicity = model.getAtomicity();
    }

    @Override
    public RecordCursor getCursor(SqlExecutionContext executionContext) throws SqlException {
        final RingQueue textImportRequestQueue = messageBus.getTextImportRequestQueue();
        final MPSequence textImportRequestPubSeq = messageBus.getTextImportRequestPubSeq();
        final AtomicBooleanCircuitBreaker circuitBreaker = textImportExecutionContext.getCircuitBreaker();

        long inProgressImportId = textImportExecutionContext.getActiveImportId();
        if (inProgressImportId == TextImportExecutionContext.INACTIVE) {
            long processingCursor = textImportRequestPubSeq.next();
            if (processingCursor > -1) {
                final TextImportRequestTask task = textImportRequestQueue.get(processingCursor);

                long importId = textImportExecutionContext.assignActiveImportId();
                task.of(
                        executionContext.getCairoSecurityContext(),
                        importId,
                        tableName,
                        fileName,
                        headerFlag,
                        timestampColumn,
                        delimiter,
                        timestampFormat,
                        partitionBy,
                        atomicity
                );

                circuitBreaker.reset();
                textImportRequestPubSeq.done(processingCursor);

                importIdSink.clear();
                Numbers.appendHex(importIdSink, importId, true);
                record.setValue(importIdSink);
                cursor.toTop();
                return cursor;
            } else {
                throw SqlException.$(0, "Unable to process the import request. Another import request may be in progress.");
            }
        }

        importIdSink.clear();
        Numbers.appendHex(importIdSink, inProgressImportId, true);
        throw SqlException.$(0, "Another import request is in progress. ")
                .put("[activeImportId=")
                .put(importIdSink)
                .put(']');
    }

    @Override
    public boolean recordCursorSupportsRandomAccess() {
        return false;
    }

    @Override
    public void toPlan(PlanSink sink) {
        sink.type("Copy");
    }

    private static class ImportIdRecord implements Record {
        private CharSequence value;

        @Override
        public CharSequence getStr(int col) {
            return value;
        }

        @Override
        public CharSequence getStrB(int col) {
            // the sink is immutable
            return getStr(col);
        }

        @Override
        public int getStrLen(int col) {
            return value.length();
        }

        public void setValue(CharSequence value) {
            this.value = value;
        }
    }

    static {
        METADATA.add(new TableColumnMetadata("id", ColumnType.STRING));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy