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

io.questdb.griffin.engine.orderby.LongTreeChain Maven / Gradle / Ivy

/*******************************************************************************
 *     ___                  _   ____  ____
 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
 *   | | | | | | |/ _ \/ __| __| | | |  _ \
 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *    \__\_\\__,_|\___||___/\__|____/|____/
 *
 *  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.griffin.engine.orderby;

import io.questdb.cairo.Reopenable;
import io.questdb.cairo.sql.Record;
import io.questdb.cairo.sql.RecordCursor;
import io.questdb.griffin.engine.AbstractRedBlackTree;
import io.questdb.griffin.engine.LimitOverflowException;
import io.questdb.griffin.engine.RecordComparator;
import io.questdb.std.MemoryTag;
import io.questdb.std.Unsafe;

/**
 * Values are stored on a heap. Value chain addresses are 4-byte aligned.
 */
public class LongTreeChain extends AbstractRedBlackTree implements Reopenable {
    private static final long CHAIN_VALUE_SIZE = 12;
    private static final long MAX_VALUE_HEAP_SIZE_LIMIT = (Integer.toUnsignedLong(-1) - 1) << 2;
    private final TreeCursor cursor = new TreeCursor();
    private final long initialValueHeapSize;
    private final long maxValueHeapSize;
    private long valueHeapLimit;
    private long valueHeapPos;
    private long valueHeapSize;
    private long valueHeapStart;

    public LongTreeChain(long keyPageSize, int keyMaxPages, long valuePageSize, int valueMaxPages) {
        super(keyPageSize, keyMaxPages);
        try {
            valueHeapSize = initialValueHeapSize = valuePageSize;
            valueHeapStart = valueHeapPos = Unsafe.malloc(valueHeapSize, MemoryTag.NATIVE_TREE_CHAIN);
            valueHeapLimit = valueHeapStart + valueHeapSize;
            maxValueHeapSize = Math.min(valuePageSize * valueMaxPages, MAX_VALUE_HEAP_SIZE_LIMIT);
        } catch (Throwable th) {
            close();
            throw th;
        }
    }

    @Override
    public void clear() {
        super.clear();
        valueHeapPos = valueHeapStart;
    }

    @Override
    public void close() {
        super.close();
        cursor.clear();
        if (valueHeapStart != 0) {
            valueHeapStart = Unsafe.free(valueHeapStart, valueHeapSize, MemoryTag.NATIVE_TREE_CHAIN);
            valueHeapLimit = valueHeapPos = 0;
            valueHeapSize = 0;
        }
    }

    public TreeCursor getCursor() {
        cursor.toTop();
        return cursor;
    }

    public void put(
            Record leftRecord,
            RecordCursor sourceCursor,
            Record rightRecord,
            RecordComparator comparator
    ) {
        if (root == -1) {
            putParent(leftRecord.getRowId());
            return;
        }

        comparator.setLeft(leftRecord);

        int offset = root;
        int parent;
        int cmp;
        do {
            parent = offset;
            final int ref = refOf(offset);
            sourceCursor.recordAt(rightRecord, rowId(ref));
            cmp = comparator.compare(rightRecord);
            if (cmp < 0) {
                offset = leftOf(offset);
            } else if (cmp > 0) {
                offset = rightOf(offset);
            } else {
                final int oldChainEnd = lastRefOf(offset);
                final int newChainEnd = appendNewValue(leftRecord.getRowId());
                setNextValueOffset(oldChainEnd, newChainEnd);
                setLastRef(offset, newChainEnd);
                return;
            }
        } while (offset > -1);

        offset = allocateBlock();
        setParent(offset, parent);

        final int chainStart = appendNewValue(leftRecord.getRowId());
        setRef(offset, chainStart);
        setLastRef(offset, chainStart);

        if (cmp < 0) {
            setLeft(parent, offset);
        } else {
            setRight(parent, offset);
        }
        fixInsert(offset);
    }

    @Override
    public void reopen() {
        super.reopen();
        if (valueHeapStart == 0) {
            valueHeapSize = initialValueHeapSize;
            valueHeapStart = valueHeapPos = Unsafe.malloc(valueHeapSize, MemoryTag.NATIVE_TREE_CHAIN);
            valueHeapLimit = valueHeapStart + valueHeapSize;
        }
    }

    private static int compressValueOffset(long rawOffset) {
        return (int) (rawOffset >> 2);
    }

    private static long uncompressValueOffset(int offset) {
        return ((long) offset) << 2;
    }

    private int appendNewValue(long rowId) {
        checkValueCapacity();
        final int offset = compressValueOffset(valueHeapPos - valueHeapStart);
        Unsafe.getUnsafe().putLong(valueHeapPos, rowId);
        Unsafe.getUnsafe().putInt(valueHeapPos + 8, -1);
        valueHeapPos += CHAIN_VALUE_SIZE;
        return offset;
    }

    private void checkValueCapacity() {
        if (valueHeapPos + CHAIN_VALUE_SIZE > valueHeapLimit) {
            final long newHeapSize = valueHeapSize << 1;
            if (newHeapSize > maxValueHeapSize) {
                throw LimitOverflowException.instance().put("limit of ").put(maxValueHeapSize).put(" memory exceeded in LongTreeChain");
            }
            long newHeapPos = Unsafe.realloc(valueHeapStart, valueHeapSize, newHeapSize, MemoryTag.NATIVE_TREE_CHAIN);

            valueHeapSize = newHeapSize;
            long delta = newHeapPos - valueHeapStart;
            valueHeapPos += delta;

            this.valueHeapStart = newHeapPos;
            this.valueHeapLimit = newHeapPos + newHeapSize;
        }
    }

    private int nextValueOffset(int valueOffset) {
        return Unsafe.getUnsafe().getInt(valueHeapStart + uncompressValueOffset(valueOffset) + 8);
    }

    private void putParent(long rowId) {
        root = allocateBlock();
        final int chainStart = appendNewValue(rowId);
        setRef(root, chainStart);
        setLastRef(root, chainStart);
        setParent(root, -1);
    }

    private long rowId(int valueOffset) {
        return Unsafe.getUnsafe().getLong(valueHeapStart + uncompressValueOffset(valueOffset));
    }

    private void setNextValueOffset(int valueOffset, int nextValueOffset) {
        Unsafe.getUnsafe().putInt(valueHeapStart + uncompressValueOffset(valueOffset) + 8, nextValueOffset);
    }

    public class TreeCursor {
        private int chainCurrent;
        private int treeCurrent;

        public void clear() {
            treeCurrent = -1;
            chainCurrent = -1;
        }

        public boolean hasNext() {
            if (chainCurrent != -1) {
                return true;
            }

            treeCurrent = successor(treeCurrent);
            if (treeCurrent == -1) {
                return false;
            }

            chainCurrent = refOf(treeCurrent);
            return true;
        }

        public long next() {
            int result = chainCurrent;
            chainCurrent = nextValueOffset(chainCurrent);
            return rowId(result);
        }

        public void toTop() {
            setup();
        }

        private void setup() {
            int p = root;
            if (p != -1) {
                while (leftOf(p) != -1) {
                    p = leftOf(p);
                }
            }
            chainCurrent = refOf(treeCurrent = p);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy