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

io.questdb.cairo.TableConverter 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.cairo;

import io.questdb.cairo.sql.TableRecordMetadata;
import io.questdb.cairo.vm.Vm;
import io.questdb.cairo.vm.api.MemoryCMR;
import io.questdb.cairo.vm.api.MemoryMARW;
import io.questdb.cairo.wal.WalUtils;
import io.questdb.cairo.wal.seq.TableSequencerAPI;
import io.questdb.log.Log;
import io.questdb.log.LogFactory;
import io.questdb.std.*;
import io.questdb.std.str.Path;
import io.questdb.std.str.StringSink;

import static io.questdb.cairo.TableUtils.*;
import static io.questdb.cairo.wal.WalUtils.CONVERT_FILE_NAME;

public class TableConverter {
    private static final Log LOG = LogFactory.getLog(TableConverter.class);

    public static ObjList convertTables(CairoConfiguration configuration, TableSequencerAPI tableSequencerAPI) {
        final ObjList convertedTables = new ObjList<>();
        if (!configuration.isTableTypeConversionEnabled()) {
            LOG.info().$("Table type conversion is disabled").$();
            return null;
        }
        if (configuration.isReadOnlyInstance()) {
            LOG.info().$("Read only instance is not allowed to perform table type conversion").$();
            return null;
        }

        final Path path = Path.getThreadLocal(configuration.getRoot());
        final int rootLen = path.length();
        final StringSink sink = Misc.getThreadLocalBuilder();
        final FilesFacade ff = configuration.getFilesFacade();
        final long findPtr = ff.findFirst(path.$());
        try {
            do {
                if (ff.isDirOrSoftLinkDirNoDots(path, rootLen, ff.findName(findPtr), ff.findType(findPtr), sink)) {
                    if (!ff.exists(path.concat(WalUtils.CONVERT_FILE_NAME).$())) {
                        continue;
                    }
                    try {
                        final String dirName = sink.toString();
                        final boolean walEnabled = readWalEnabled(path, ff);
                        LOG.info().$("Converting table [dirName=").utf8(dirName).$(", walEnabled=").$(walEnabled).I$();

                        path.trimTo(rootLen).concat(dirName);
                        try (final MemoryMARW metaMem = Vm.getMARWInstance()) {
                            openSmallFile(ff, path, rootLen, metaMem, META_FILE_NAME, MemoryTag.MMAP_SEQUENCER_METADATA);
                            if (metaMem.getBool(TableUtils.META_OFFSET_WAL_ENABLED) == walEnabled) {
                                LOG.info().$("Skipping conversion, table already has the expected type [dirName=").utf8(dirName).$(", walEnabled=").$(walEnabled).I$();
                            } else {
                                final String tableName;
                                try (final MemoryCMR mem = Vm.getCMRInstance()) {
                                    final String name = TableUtils.readTableName(path.of(configuration.getRoot()).concat(dirName), rootLen, mem, ff);
                                    tableName = name != null ? name : dirName;
                                }

                                final int tableId = metaMem.getInt(TableUtils.META_OFFSET_TABLE_ID);
                                final TableToken token = new TableToken(tableName, dirName, tableId, walEnabled);

                                if (walEnabled) {
                                    try (TableReaderMetadata metadata = new TableReaderMetadata(configuration, token)) {
                                        metadata.load();
                                        tableSequencerAPI.registerTable(tableId, new TableDescriptorImpl(metadata), token);
                                    }
                                } else {
                                    tableSequencerAPI.deregisterTable(token);
                                    removeWalPersistence(path, rootLen, ff, dirName);
                                }
                                metaMem.putBool(TableUtils.META_OFFSET_WAL_ENABLED, walEnabled);
                                convertedTables.add(token);
                            }

                            path.trimTo(rootLen).concat(dirName).concat(CONVERT_FILE_NAME).$();
                            if (!ff.remove(path)) {
                                LOG.error().$("Could not remove _convert file [path=").utf8(path).I$();
                            }
                        }
                    } catch (Exception e) {
                        LOG.error().$("Table conversion failed [path=").utf8(path).$(", e=").$(e).I$();
                    }
                }
            } while (ff.findNext(findPtr) > 0);
        } finally {
            ff.findClose(findPtr);
        }
        return convertedTables;
    }

    private static boolean readWalEnabled(Path path, FilesFacade ff) {
        int fd = -1;
        try {
            fd = ff.openRO(path);
            if (fd < 1) {
                throw CairoException.critical(ff.errno()).put("Could not open file [path=").put(path).put(']');
            }

            final byte walType = ff.readNonNegativeByte(fd, 0);
            switch (walType) {
                case TABLE_TYPE_WAL:
                    return true;
                case TABLE_TYPE_NON_WAL:
                    return false;
                default:
                    throw CairoException.critical(ff.errno()).put("Could not read walType from file [path=").put(path).put(']');
            }
        } finally {
            ff.close(fd);
        }
    }

    private static void removeWalPersistence(Path path, int rootLen, FilesFacade ff, String dirName) {
        path.trimTo(rootLen).concat(dirName).concat(WalUtils.SEQ_DIR).$();
        if (ff.rmdir(path) != 0) {
            LOG.error()
                    .$("Could not remove sequencer dir [errno=").$(ff.errno())
                    .$(", path=").$(path)
                    .I$();
        }

        path.trimTo(rootLen).concat(dirName).$();
        int plen = path.length();
        final long pFind = ff.findFirst(path);
        if (pFind > 0) {
            try {
                do {
                    long name = ff.findName(pFind);
                    int type = ff.findType(pFind);
                    if (CairoKeywords.isWal(name)) {
                        path.trimTo(plen).concat(name).$();
                        if (type == Files.DT_FILE && CairoKeywords.isLock(name)) {
                            if (!ff.remove(path)) {
                                LOG.error()
                                        .$("Could not remove wal lock file [errno=").$(ff.errno())
                                        .$(", path=").$(path)
                                        .I$();
                            }
                        } else {
                            if (ff.rmdir(path) != 0) {
                                LOG.error()
                                        .$("Could not remove wal dir [errno=").$(ff.errno())
                                        .$(", path=").$(path)
                                        .I$();
                            }
                        }
                    }
                } while (ff.findNext(pFind) > 0);
            } finally {
                ff.findClose(pFind);
            }
        }
    }

    private static class TableDescriptorImpl implements TableDescriptor {
        private final ObjList columnNames = new ObjList<>();
        private final IntList columnTypes = new IntList();
        private final int timestampIndex;

        private TableDescriptorImpl(TableRecordMetadata metadata) {
            timestampIndex = metadata.getTimestampIndex();
            for (int i = 0, n = metadata.getColumnCount(); i < n; i++) {
                columnNames.add(metadata.getColumnName(i));
                columnTypes.add(metadata.getColumnType(i));
            }
        }

        @Override
        public int getColumnCount() {
            return columnNames.size();
        }

        @Override
        public CharSequence getColumnName(int columnIndex) {
            return columnNames.get(columnIndex);
        }

        @Override
        public int getColumnType(int columnIndex) {
            return columnTypes.get(columnIndex);
        }

        @Override
        public int getTimestampIndex() {
            return timestampIndex;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy