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

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

The newest version!
/*******************************************************************************
 *     ___                  _   ____  ____
 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
 *   | | | | | | |/ _ \/ __| __| | | |  _ \
 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *    \__\_\\__,_|\___||___/\__|____/|____/
 *
 *  Copyright (c) 2014-2019 Appsicle
 *  Copyright (c) 2019-2024 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.sql.TableMetadata;
import io.questdb.cairo.vm.Vm;
import io.questdb.cairo.vm.api.MemoryCARW;
import io.questdb.cairo.vm.api.MemoryMA;
import io.questdb.cairo.vm.api.MemoryMR;
import io.questdb.cairo.vm.api.MemoryR;
import io.questdb.std.Chars;
import io.questdb.std.Files;
import io.questdb.std.FilesFacade;
import io.questdb.std.IntList;
import io.questdb.std.LowerCaseCharSequenceIntHashMap;
import io.questdb.std.MemoryTag;
import io.questdb.std.Misc;
import io.questdb.std.Mutable;
import io.questdb.std.datetime.millitime.MillisecondClock;
import io.questdb.std.str.LPSZ;
import io.questdb.std.str.Path;

import static io.questdb.cairo.TableUtils.validationException;

public class TableReaderMetadata extends AbstractRecordMetadata implements TableMetadata, Mutable {
    protected final CairoConfiguration configuration;
    private final IntList columnOrderList = new IntList();
    private final FilesFacade ff;
    private final LowerCaseCharSequenceIntHashMap tmpValidationMap = new LowerCaseCharSequenceIntHashMap();
    private boolean isCopy;
    private boolean isSoftLink;
    private int matViewRefreshLimitHoursOrMonths;
    private int matViewTimerInterval;
    private char matViewTimerIntervalUnit;
    private long matViewTimerStart;
    private int maxUncommittedRows;
    private MemoryCARW metaCopyMem; // used when loadFrom() called
    private MemoryMR metaMem;
    private long metadataVersion;
    private long o3MaxLag;
    private int partitionBy;
    private Path path;
    private int plen;
    private int tableId;
    private TableToken tableToken;
    private TableReaderMetadataTransitionIndex transitionIndex;
    private MemoryMR transitionMeta;
    private int ttlHoursOrMonths;
    private boolean walEnabled;

    public TableReaderMetadata(CairoConfiguration configuration, TableToken tableToken) {
        try {
            this.configuration = configuration;
            this.ff = configuration.getFilesFacade();
            this.tableToken = tableToken;
            this.path = new Path();
            this.path.of(configuration.getDbRoot()).concat(tableToken.getDirName());
            this.plen = path.size();
            this.isSoftLink = Files.isSoftLink(path.$());
            this.metaMem = Vm.getCMRInstance();
            this.metaCopyMem = Vm.getCARWInstance(ff.getPageSize(), Integer.MAX_VALUE, MemoryTag.NATIVE_METADATA_READER);
        } catch (Throwable th) {
            close();
            throw th;
        }
    }

    // This constructor used to read random metadata files.
    public TableReaderMetadata(CairoConfiguration configuration) {
        this.configuration = configuration;
        this.ff = configuration.getFilesFacade();
        this.tableToken = null;
        this.metaMem = Vm.getCMRInstance();
    }

    public TableReaderMetadataTransitionIndex applyTransition() {
        // Swap meta and transitionMeta. It's fine if we're dealing with
        // a metadata copy and metaMem wasn't initialized.
        MemoryMR temp = this.metaMem;
        this.metaMem = this.transitionMeta;
        transitionMeta = temp;
        isCopy = false;
        Misc.free(transitionMeta); // memory is safe to double close, do not assign null to transitionMeta
        Misc.free(metaCopyMem); // close copy memory in case if it was in-use

        return applyTransition0(metaMem, columnCount);
    }

    public TableReaderMetadataTransitionIndex applyTransitionFrom(TableReaderMetadata srcMeta) {
        copyMemFrom(srcMeta);
        isCopy = true;
        Misc.free(metaMem);
        Misc.free(transitionMeta);

        return applyTransition0(metaCopyMem, columnCount);
    }

    @Override
    public void clear() {
        super.clear();
        Misc.free(metaMem);
        Misc.free(metaCopyMem);
        Misc.free(transitionMeta);
        isCopy = false;
    }

    @Override
    public void close() {
        metaMem = Misc.free(metaMem);
        metaCopyMem = Misc.free(metaCopyMem);
        transitionMeta = Misc.free(transitionMeta);
        path = Misc.free(path);
        isCopy = false;
    }

    public void dumpTo(MemoryMA mem) {
        // This may be mmapped _meta file or its copy.
        final MemoryR metaMem = getMetaMem();
        // 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 = metaMem.size();
        for (long p = 0; p < len; p++) {
            mem.putByte(metaMem.getByte(p));
        }
    }

    public int getDenseSymbolIndex(int columnIndex) {
        return ((TableReaderMetadataColumn) columnMetadata.getQuick(columnIndex)).getDenseSymbolIndex();
    }

    @Override
    public int getIndexBlockCapacity(int columnIndex) {
        return getColumnMetadata(columnIndex).getIndexValueBlockCapacity();
    }

    @Override
    public int getMatViewRefreshLimitHoursOrMonths() {
        return matViewRefreshLimitHoursOrMonths;
    }

    public int getMatViewTimerInterval() {
        return matViewTimerInterval;
    }

    public char getMatViewTimerIntervalUnit() {
        return matViewTimerIntervalUnit;
    }

    public long getMatViewTimerStart() {
        return matViewTimerStart;
    }

    @Override
    public int getMaxUncommittedRows() {
        return maxUncommittedRows;
    }

    @Override
    public long getMetadataVersion() {
        return metadataVersion;
    }

    @Override
    public long getO3MaxLag() {
        return o3MaxLag;
    }

    @Override
    public int getPartitionBy() {
        return partitionBy;
    }

    @Override
    public boolean getSymbolCacheFlag(int columnIndex) {
        return getColumnMetadata(columnIndex).isSymbolIndexFlag();
    }

    @Override
    public int getSymbolCapacity(int columnIndex) {
        return getColumnMetadata(columnIndex).getSymbolCapacity();
    }

    @Override
    public int getTableId() {
        return tableId;
    }

    @Override
    public CharSequence getTableName() {
        return tableToken.getTableName();
    }

    @Override
    public TableToken getTableToken() {
        return tableToken;
    }

    @Override
    public int getTtlHoursOrMonths() {
        return ttlHoursOrMonths;
    }

    @Override
    public boolean isIndexed(int columnIndex) {
        return getColumnMetadata(columnIndex).isSymbolIndexFlag();
    }

    public boolean isSoftLink() {
        return isSoftLink;
    }

    @Override
    public boolean isWalEnabled() {
        return walEnabled;
    }

    public void load(LPSZ path) {
        try {
            isCopy = false;
            Misc.free(metaCopyMem);
            metaMem.smallFile(ff, path, MemoryTag.NATIVE_TABLE_READER);
            TableUtils.validateMeta(metaMem, null, ColumnType.VERSION);
            readFromMem(metaMem);
        } catch (Throwable e) {
            clear();
            throw e;
        }
    }

    public void load() {
        final long spinLockTimeout = configuration.getSpinLockTimeout();
        final MillisecondClock millisecondClock = configuration.getMillisecondClock();
        long deadline = configuration.getMillisecondClock().getTicks() + spinLockTimeout;
        path.trimTo(plen).concat(TableUtils.META_FILE_NAME);
        boolean existenceChecked = false;
        while (true) {
            try {
                load(path.$());
                return;
            } catch (CairoException ex) {
                if (!existenceChecked) {
                    path.trimTo(plen).slash();
                    if (!ff.exists(path.$())) {
                        throw CairoException.tableDoesNotExist(tableToken.getTableName());
                    }
                    path.trimTo(plen).concat(TableUtils.META_FILE_NAME).$();
                }
                existenceChecked = true;
                TableUtils.handleMetadataLoadException(tableToken.getTableName(), deadline, ex, millisecondClock, spinLockTimeout);
            }
        }
    }

    public void loadFrom(TableReaderMetadata srcMeta) {
        assert tableToken.equals(srcMeta.tableToken);
        // Copy src meta memory.
        copyMemFrom(srcMeta);
        // Now, read it.
        try {
            isCopy = true;
            Misc.free(metaMem);
            readFromMem(metaCopyMem);
        } catch (Throwable e) {
            clear();
            throw e;
        }
    }

    public boolean prepareTransition(long txnMetadataVersion) {
        if (transitionMeta == null) {
            transitionMeta = Vm.getCMRInstance();
        }

        path.trimTo(plen).concat(TableUtils.META_FILE_NAME);
        transitionMeta.smallFile(ff, path.$(), MemoryTag.NATIVE_TABLE_READER);
        if (transitionMeta.size() >= TableUtils.META_OFFSET_METADATA_VERSION + 8
                && txnMetadataVersion != transitionMeta.getLong(TableUtils.META_OFFSET_METADATA_VERSION)) {
            // No match
            return false;
        }

        tmpValidationMap.clear();
        TableUtils.validateMeta(transitionMeta, tmpValidationMap, ColumnType.VERSION);
        return true;
    }

    public void readFromMem(MemoryR mem) {
        int columnCount = mem.getInt(TableUtils.META_OFFSET_COUNT);
        int timestampIndex = mem.getInt(TableUtils.META_OFFSET_TIMESTAMP_INDEX);
        this.partitionBy = mem.getInt(TableUtils.META_OFFSET_PARTITION_BY);
        this.tableId = mem.getInt(TableUtils.META_OFFSET_TABLE_ID);
        this.maxUncommittedRows = mem.getInt(TableUtils.META_OFFSET_MAX_UNCOMMITTED_ROWS);
        this.o3MaxLag = mem.getLong(TableUtils.META_OFFSET_O3_MAX_LAG);
        this.metadataVersion = mem.getLong(TableUtils.META_OFFSET_METADATA_VERSION);
        this.walEnabled = mem.getBool(TableUtils.META_OFFSET_WAL_ENABLED);
        this.ttlHoursOrMonths = TableUtils.getTtlHoursOrMonths(mem);
        this.matViewRefreshLimitHoursOrMonths = TableUtils.getMatViewRefreshLimitHoursOrMonths(mem);
        this.matViewTimerStart = TableUtils.getMatViewTimerStart(mem);
        this.matViewTimerInterval = TableUtils.getMatViewTimerInterval(mem);
        this.matViewTimerIntervalUnit = TableUtils.getMatViewTimerIntervalUnit(mem);
        this.columnMetadata.clear();
        this.timestampIndex = -1;

        TableUtils.buildColumnListFromMetadataFile(mem, columnCount, columnOrderList);
        this.columnNameIndexMap.clear();

        for (int i = 0, n = columnOrderList.size(); i < n; i += 3) {
            int writerIndex = columnOrderList.get(i);
            if (writerIndex < 0) {
                continue;
            }
            int stableIndex = i / 3;
            CharSequence name = mem.getStrA(columnOrderList.get(i + 1));
            int denseSymbolIndex = columnOrderList.get(i + 2);

            assert name != null;
            int columnType = TableUtils.getColumnType(mem, writerIndex);

            if (columnType > -1) {
                String colName = Chars.toString(name);
                columnMetadata.add(
                        new TableReaderMetadataColumn(
                                colName,
                                columnType,
                                TableUtils.isColumnIndexed(mem, writerIndex),
                                TableUtils.getIndexBlockCapacity(mem, writerIndex),
                                true,
                                null,
                                writerIndex,
                                TableUtils.isColumnDedupKey(mem, writerIndex),
                                denseSymbolIndex,
                                stableIndex,
                                TableUtils.isSymbolCached(mem, writerIndex),
                                TableUtils.getSymbolCapacity(mem, writerIndex)
                        )
                );
                int denseIndex = columnMetadata.size() - 1;
                if (!columnNameIndexMap.put(colName, denseIndex)) {
                    throw validationException(mem).put("Duplicate column [name=").put(name).put("] at ").put(i);
                }
                if (writerIndex == timestampIndex) {
                    this.timestampIndex = denseIndex;
                }
            }
        }
        this.columnCount = columnMetadata.size();
    }

    public void updateTableToken(TableToken tableToken) {
        this.tableToken = tableToken;
    }

    private TableReaderMetadataTransitionIndex applyTransition0(MemoryR newMetaMem, int existingColumnCount) {
        columnNameIndexMap.clear();

        int columnCount = newMetaMem.getInt(TableUtils.META_OFFSET_COUNT);
        assert columnCount >= existingColumnCount;
        columnMetadata.setPos(columnCount);
        int timestampIndex = newMetaMem.getInt(TableUtils.META_OFFSET_TIMESTAMP_INDEX);
        this.tableId = newMetaMem.getInt(TableUtils.META_OFFSET_TABLE_ID);
        this.metadataVersion = newMetaMem.getLong(TableUtils.META_OFFSET_METADATA_VERSION);
        this.maxUncommittedRows = newMetaMem.getInt(TableUtils.META_OFFSET_MAX_UNCOMMITTED_ROWS);
        this.o3MaxLag = newMetaMem.getLong(TableUtils.META_OFFSET_O3_MAX_LAG);
        this.walEnabled = newMetaMem.getBool(TableUtils.META_OFFSET_WAL_ENABLED);
        this.ttlHoursOrMonths = TableUtils.getTtlHoursOrMonths(newMetaMem);
        this.matViewRefreshLimitHoursOrMonths = TableUtils.getMatViewRefreshLimitHoursOrMonths(newMetaMem);
        this.matViewTimerStart = TableUtils.getMatViewTimerStart(newMetaMem);
        this.matViewTimerInterval = TableUtils.getMatViewTimerInterval(newMetaMem);
        this.matViewTimerIntervalUnit = TableUtils.getMatViewTimerIntervalUnit(newMetaMem);

        int shiftLeft = 0, existingIndex = 0;
        TableUtils.buildColumnListFromMetadataFile(newMetaMem, columnCount, columnOrderList);
        int newColumnCount = newMetaMem.getInt(TableUtils.META_OFFSET_COUNT);

        if (transitionIndex == null) {
            transitionIndex = new TableReaderMetadataTransitionIndex();
        } else {
            transitionIndex.clear();
        }

        TableUtils.buildColumnListFromMetadataFile(newMetaMem, newColumnCount, columnOrderList);
        for (int i = 0, n = columnOrderList.size(); i < n; i += 3) {
            int stableIndex = i / 3;
            int writerIndex = columnOrderList.get(i);
            if (writerIndex < 0) {
                continue;
            }
            CharSequence name = newMetaMem.getStrA(columnOrderList.get(i + 1));
            assert name != null;
            int denseSymbolIndex = columnOrderList.get(i + 2);
            int newColumnType = TableUtils.getColumnType(newMetaMem, writerIndex);
            int columnType = TableUtils.getColumnType(newMetaMem, writerIndex);
            boolean isIndexed = TableUtils.isColumnIndexed(newMetaMem, writerIndex);
            boolean isDedupKey = TableUtils.isColumnDedupKey(newMetaMem, writerIndex);
            int indexBlockCapacity = TableUtils.getIndexBlockCapacity(newMetaMem, writerIndex);
            boolean symbolIsCached = TableUtils.isSymbolCached(newMetaMem, writerIndex);
            int symbolCapacity = TableUtils.getSymbolCapacity(newMetaMem, writerIndex);
            TableReaderMetadataColumn existing = null;
            String newName;

            if (existingIndex < existingColumnCount) {
                existing = (TableReaderMetadataColumn) columnMetadata.getQuick(existingIndex);
                int existingStableIndex = existing.getStableIndex();
                if (existingStableIndex > stableIndex && columnType < 0) {
                    // This column must be deleted so existing dense columns do not contain it
                    continue;
                }
            }

            // index structure is
            // [action: deleted | reused, copy from:int index]
            // "copy from" >= 0 indicates that column is to be copied from old position
            // "copy from" < 0  indicates that column is new and should be taken from updated metadata position
            // "copy from" == Integer.MIN_VALUE  indicates that column is deleted for good and should not be re-added from any source

            int outIndex = existingIndex - shiftLeft;
            if (newColumnType < 0) {
                shiftLeft++; // Deleted in new
                if (existing != null) {
                    transitionIndex.markDeleted(existingIndex);
                }
            } else {
                // existing column
                boolean rename = existing != null && !Chars.equals(existing.getColumnName(), name);
                newName = rename || existing == null ? Chars.toString(name) : existing.getColumnName();

                if (rename
                        || existing == null
                        || existing.getWriterIndex() != writerIndex
                        || existing.isSymbolIndexFlag() != isIndexed
                        || existing.getIndexValueBlockCapacity() != indexBlockCapacity
                        || existing.isDedupKeyFlag() != isDedupKey
                        || existing.getDenseSymbolIndex() != denseSymbolIndex
                        || existing.getStableIndex() != stableIndex
                ) {
                    // new
                    columnMetadata.setQuick(
                            outIndex,
                            new TableReaderMetadataColumn(
                                    newName,
                                    columnType,
                                    isIndexed,
                                    indexBlockCapacity,
                                    true,
                                    null,
                                    writerIndex,
                                    isDedupKey,
                                    denseSymbolIndex,
                                    stableIndex,
                                    symbolIsCached,
                                    symbolCapacity
                            )
                    );
                    if (existing != null) {
                        // column deleted at existingIndex
                        transitionIndex.markDeleted(existingIndex);
                    }
                    transitionIndex.markCopyFrom(outIndex, writerIndex);
                } else {
                    // reuse
                    columnMetadata.setQuick(outIndex, existing);
                    transitionIndex.markReusedAction(outIndex, existingIndex);
                    if (existingIndex > outIndex) {
                        // mark to do nothing with existing column, this may be overwritten later
                        transitionIndex.markReplaced(existingIndex);
                    }
                }
                columnNameIndexMap.put(newName, outIndex);
                if (timestampIndex == writerIndex) {
                    this.timestampIndex = outIndex;
                }
            }
            existingIndex++;
        }

        columnMetadata.setPos(existingIndex - shiftLeft);
        this.columnCount = columnMetadata.size();
        if (timestampIndex < 0) {
            this.timestampIndex = timestampIndex;
        }

        return transitionIndex;
    }

    private void copyMemFrom(TableReaderMetadata srcMeta) {
        final MemoryR srcMetaMem = srcMeta.getMetaMem();
        long len = srcMetaMem.size();
        metaCopyMem.jumpTo(0);
        for (long p = 0; p < len; p++) {
            metaCopyMem.putByte(srcMetaMem.getByte(p));
        }
    }

    private MemoryR getMetaMem() {
        return !isCopy ? metaMem : metaCopyMem;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy