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

io.questdb.cairo.TableReaderMetadata Maven / Gradle / Ivy

/*******************************************************************************
 *     ___                  _   ____  ____
 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
 *   | | | | | | |/ _ \/ __| __| | | |  _ \
 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *    \__\_\\__,_|\___||___/\__|____/|____/
 *
 *  Copyright (c) 2014-2019 Appsicle
 *  Copyright (c) 2019-2022 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.cairo;

import io.questdb.cairo.vm.Vm;
import io.questdb.cairo.vm.api.MemoryMA;
import io.questdb.cairo.vm.api.MemoryMR;
import io.questdb.std.*;
import io.questdb.std.str.Path;

import java.io.Closeable;

public class TableReaderMetadata extends BaseRecordMetadata implements Closeable {
    private final Path path;
    private final FilesFacade ff;
    private final LowerCaseCharSequenceIntHashMap tmpValidationMap = new LowerCaseCharSequenceIntHashMap();
    private MemoryMR metaMem;
    private int partitionBy;
    private int version;
    private int tableId;
    private int maxUncommittedRows;
    private long commitLag;
    private long structureVersion;
    private MemoryMR transitionMeta;

    public TableReaderMetadata(FilesFacade ff) {
        this.path = new Path();
        this.ff = ff;
        this.metaMem = Vm.getMRInstance();
        this.columnMetadata = new ObjList<>(columnCount);
        this.columnNameIndexMap = new LowerCaseCharSequenceIntHashMap();
    }

    public TableReaderMetadata(FilesFacade ff, Path path) {
        this(ff);
        of(path, ColumnType.VERSION);
    }

    public void applyTransitionIndex() {
        // swap meta and transitionMeta
        MemoryMR temp = this.metaMem;
        this.metaMem = this.transitionMeta;
        transitionMeta = temp;
        transitionMeta.close(); // Memory is safe to double close, do not assign null to transitionMeta
        this.columnNameIndexMap.clear();
        int existingColumnCount = this.columnCount;

        int columnCount = metaMem.getInt(TableUtils.META_OFFSET_COUNT);
        assert columnCount >= existingColumnCount;
        columnMetadata.setPos(columnCount);
        int timestampIndex = metaMem.getInt(TableUtils.META_OFFSET_TIMESTAMP_INDEX);
        this.tableId = metaMem.getInt(TableUtils.META_OFFSET_TABLE_ID);
        this.structureVersion = metaMem.getLong(TableUtils.META_OFFSET_STRUCTURE_VERSION);
        this.maxUncommittedRows = metaMem.getInt(TableUtils.META_OFFSET_MAX_UNCOMMITTED_ROWS);
        this.commitLag = metaMem.getLong(TableUtils.META_OFFSET_COMMIT_LAG);
        long offset = TableUtils.getColumnNameOffset(columnCount);

        int shiftLeft = 0, existingIndex = 0;
        for (int metaIndex = 0; metaIndex < columnCount; metaIndex++) {
            CharSequence name = metaMem.getStr(offset);
            offset += Vm.getStorageLength(name);
            assert name != null;
            int columnType = TableUtils.getColumnType(metaMem, metaIndex);
            boolean isIndexed = TableUtils.isColumnIndexed(metaMem, metaIndex);
            int indexBlockCapacity = TableUtils.getIndexBlockCapacity(metaMem, metaIndex);
            TableColumnMetadata existing = null;
            String newName;

            if (existingIndex < existingColumnCount) {
                existing = columnMetadata.getQuick(existingIndex);

                if (existing.getWriterIndex() > metaIndex) {
                    // This column must be deleted so existing dense columns do not contain it
                    assert columnType < 0;
                    continue;
                }
            }
            assert existing == null || existing.getWriterIndex() == metaIndex; // Same column

            // exiting column
            if (columnType < 0) {
                shiftLeft++;
            } else {
                boolean rename = existing != null && !Chars.equals(existing.getName(), name);
                newName = rename || existing == null ? Chars.toString(name) : existing.getName();
                if (rename
                        || existing == null
                        || existing.isIndexed() != isIndexed
                        || existing.getIndexValueBlockCapacity() != indexBlockCapacity
                ) {
                    columnMetadata.setQuick(existingIndex - shiftLeft,
                            new TableColumnMetadata(
                                    newName,
                                    TableUtils.getColumnHash(metaMem, metaIndex),
                                    columnType,
                                    isIndexed,
                                    indexBlockCapacity,
                                    true,
                                    null,
                                    metaIndex
                            )
                    );
                } else if (shiftLeft > 0) {
                    columnMetadata.setQuick(existingIndex - shiftLeft, existing);
                }
                this.columnNameIndexMap.put(newName, existingIndex - shiftLeft);
                if (timestampIndex == metaIndex) {
                    this.timestampIndex = existingIndex - shiftLeft;
                }
            }
            existingIndex++;
        }
        columnMetadata.setPos(existingIndex - shiftLeft);
        this.columnCount = columnMetadata.size();
        if (timestampIndex < 0) {
            this.timestampIndex = timestampIndex;
        }
    }

    public void dumpTo(MemoryMA mem) {
        // Since _meta files are immutable and get updated with a single atomic rename
        // operation replacing the old file with the new one, it's ok to clone the metadata
        // by copying metaMem's contents. Even if _meta file was already replaced, the file
        // should be still kept on disk until inode's ref counter is above zero.
        long len = ff.length(metaMem.getFd());
        for (long p = 0; p < len; p++) {
            mem.putByte(metaMem.getByte(p));
        }
    }

    @Override
    public void close() {
        // TableReaderMetadata is re-usable after close, don't assign nulls
        Misc.free(metaMem);
        Misc.free(path);
        Misc.free(transitionMeta);
    }

    public long createTransitionIndex(long txnStructureVersion) {
        if (transitionMeta == null) {
            transitionMeta = Vm.getMRInstance();
        }

        transitionMeta.smallFile(ff, path, MemoryTag.MMAP_DEFAULT);
        if (transitionMeta.size() >= TableUtils.META_OFFSET_STRUCTURE_VERSION + 8
                && txnStructureVersion != transitionMeta.getLong(TableUtils.META_OFFSET_STRUCTURE_VERSION)) {
            // No match
            return -1;
        }

        tmpValidationMap.clear();
        TableUtils.validate(transitionMeta, tmpValidationMap, ColumnType.VERSION);
        return TableUtils.createTransitionIndex(transitionMeta, this);
    }

    @Override
    public int getColumnCount() {
        return columnCount;
    }

    public long getCommitLag() {
        return commitLag;
    }

    public int getId() {
        return tableId;
    }

    public int getMaxUncommittedRows() {
        return maxUncommittedRows;
    }

    public int getPartitionBy() {
        return partitionBy;
    }

    public long getStructureVersion() {
        return structureVersion;
    }

    public int getVersion() {
        return version;
    }

    public TableReaderMetadata of(Path path, int expectedVersion) {
        this.path.of(path).$();
        try {
            this.metaMem.smallFile(ff, path, MemoryTag.MMAP_DEFAULT);
            this.columnNameIndexMap.clear();
            TableUtils.validate(metaMem, this.columnNameIndexMap, expectedVersion);
            int columnCount = metaMem.getInt(TableUtils.META_OFFSET_COUNT);
            int timestampIndex = metaMem.getInt(TableUtils.META_OFFSET_TIMESTAMP_INDEX);
            this.partitionBy = metaMem.getInt(TableUtils.META_OFFSET_PARTITION_BY);
            this.version = metaMem.getInt(TableUtils.META_OFFSET_VERSION);
            this.tableId = metaMem.getInt(TableUtils.META_OFFSET_TABLE_ID);
            this.maxUncommittedRows = metaMem.getInt(TableUtils.META_OFFSET_MAX_UNCOMMITTED_ROWS);
            this.commitLag = metaMem.getLong(TableUtils.META_OFFSET_COMMIT_LAG);
            this.structureVersion = metaMem.getLong(TableUtils.META_OFFSET_STRUCTURE_VERSION);
            this.columnMetadata.clear();
            long offset = TableUtils.getColumnNameOffset(columnCount);
            this.timestampIndex = -1;

            // don't create strings in this loop, we already have them in columnNameIndexMap
            for (int i = 0; i < columnCount; i++) {
                CharSequence name = metaMem.getStr(offset);
                assert name != null;
                int columnType = TableUtils.getColumnType(metaMem, i);
                if (columnType > 0) {
                    columnMetadata.add(
                            new TableColumnMetadata(
                                    Chars.toString(name),
                                    TableUtils.getColumnHash(metaMem, i),
                                    columnType,
                                    TableUtils.isColumnIndexed(metaMem, i),
                                    TableUtils.getIndexBlockCapacity(metaMem, i),
                                    true,
                                    null,
                                    i
                            )
                    );
                    if (i == timestampIndex) {
                        this.timestampIndex = columnMetadata.size() - 1;
                    }
                }
                offset += Vm.getStorageLength(name);
            }
            this.columnCount = columnMetadata.size();
        } catch (Throwable e) {
            close();
            throw e;
        }
        return this;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy