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

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

import io.questdb.cairo.*;
import io.questdb.cairo.sql.Record;
import io.questdb.cairo.sql.RecordCursor;
import io.questdb.std.*;
import io.questdb.std.bytes.Bytes;
import io.questdb.std.str.Utf8Sequence;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * Unordered2Map is a look-up table (not a hash table) with keys up to 2 bytes in size used
 * to store intermediate data of group by, sample by queries. It provides {@link MapKey} and
 * {@link MapValue}, as well as {@link RecordCursor} interfaces for data access and modification.
 * The preferred way to create an Unordered2Map is {@link MapFactory}.
 * 

* Map iteration provided by {@link RecordCursor} does not preserve the key insertion order, hence * the unordered map name. * Important! * Key and value structures must match the ones provided via lists of columns ({@link ColumnTypes}) * to the map constructor. Later put* calls made on {@link MapKey} and {@link MapValue} must match * the declared column types to guarantee memory access safety. */ public class Unordered2Map implements Map, Reopenable { static final long KEY_SIZE = Short.BYTES; private static final int TABLE_SIZE = Short.toUnsignedInt((short) -1) + 1; private final Unordered2MapCursor cursor; private final long entrySize; private final Key key; private final int memoryTag; private final Unordered2MapRecord record; private final Unordered2MapValue value; private final Unordered2MapValue value2; private final Unordered2MapValue value3; private boolean hasZero; private long keyMemStart; // Key look-up memory start pointer. private long memLimit; // Look-up table memory limit pointer. private long memStart; // Look-up table memory start pointer. private int size = 0; public Unordered2Map( @Transient @NotNull ColumnTypes keyTypes, @Transient @Nullable ColumnTypes valueTypes ) { this(keyTypes, valueTypes, MemoryTag.NATIVE_UNORDERED_MAP); } Unordered2Map( @NotNull @Transient ColumnTypes keyTypes, @Nullable @Transient ColumnTypes valueTypes, int memoryTag ) { try { this.memoryTag = memoryTag; final int keyColumnCount = keyTypes.getColumnCount(); long keySize = 0; for (int i = 0; i < keyColumnCount; i++) { final int columnType = keyTypes.getColumnType(i); final int size = ColumnType.sizeOf(columnType); if (size > 0) { keySize += size; } else { keySize = -1; break; } } if (keySize <= 0 || keySize > KEY_SIZE) { throw CairoException.nonCritical().put("unexpected key size: ").put(keySize); } long valueOffset = 0; long[] valueOffsets = null; long valueSize = 0; if (valueTypes != null) { int valueColumnCount = valueTypes.getColumnCount(); valueOffsets = new long[valueColumnCount]; for (int i = 0; i < valueColumnCount; i++) { valueOffsets[i] = valueOffset; final int columnType = valueTypes.getColumnType(i); final int size = ColumnType.sizeOf(columnType); if (size <= 0) { throw CairoException.nonCritical().put("value type is not supported: ").put(ColumnType.nameOf(columnType)); } valueOffset += size; valueSize += size; } } this.entrySize = Bytes.align2b(KEY_SIZE + valueSize); final long sizeBytes = entrySize * TABLE_SIZE; memStart = Unsafe.malloc(sizeBytes, memoryTag); Vect.memset(memStart, sizeBytes, 0); memLimit = memStart + sizeBytes; keyMemStart = Unsafe.malloc(KEY_SIZE, memoryTag); Unsafe.getUnsafe().putShort(keyMemStart, (short) 0); value = new Unordered2MapValue(valueSize, valueOffsets); value2 = new Unordered2MapValue(valueSize, valueOffsets); value3 = new Unordered2MapValue(valueSize, valueOffsets); record = new Unordered2MapRecord(valueSize, valueOffsets, value, keyTypes, valueTypes); cursor = new Unordered2MapCursor(record, this); key = new Key(); } catch (Throwable th) { close(); throw th; } } @Override public void clear() { size = 0; hasZero = false; Vect.memset(memStart, entrySize * TABLE_SIZE, 0); Unsafe.getUnsafe().putShort(keyMemStart, (short) 0); } @Override public void close() { if (memStart != 0) { memStart = memLimit = Unsafe.free(memStart, entrySize * TABLE_SIZE, memoryTag); keyMemStart = Unsafe.free(keyMemStart, KEY_SIZE, memoryTag); size = 0; hasZero = false; } } @Override public MapRecordCursor getCursor() { return cursor.init(memStart, memLimit, hasZero, size); } @Override public int getKeyCapacity() { return TABLE_SIZE; } @Override public MapRecord getRecord() { return record; } @Override public boolean isOpen() { return memStart != 0; } @Override public void merge(Map srcMap, MapValueMergeFunction mergeFunc) { assert this != srcMap; long srcSize = srcMap.size(); if (srcSize == 0) { return; } Unordered2Map src2Map = (Unordered2Map) srcMap; // First, we handle zero key. if (src2Map.hasZero) { if (hasZero) { mergeFunc.merge( valueAt(memStart), src2Map.valueAt(src2Map.memStart) ); } else { Vect.memcpy(memStart, src2Map.memStart, entrySize); hasZero = true; size++; } // Check if zero was the only element in the source map. if (srcSize == 1) { return; } } // Then we handle all non-zero keys. long destAddr = memStart + entrySize; long srcAddr = src2Map.memStart + entrySize; for (int i = 1; i < TABLE_SIZE; i++, destAddr += entrySize, srcAddr += entrySize) { short srcKey = Unsafe.getUnsafe().getShort(srcAddr); if (srcKey == 0) { continue; } short destKey = Unsafe.getUnsafe().getShort(destAddr); if (destKey != 0) { // Match found, merge values. mergeFunc.merge( valueAt(destAddr), src2Map.valueAt(srcAddr) ); } else { // Not present in destination table, so we can simply copy it. Vect.memcpy(destAddr, srcAddr, entrySize); size++; } } } @Override public void reopen(int keyCapacity, long heapSize) { reopen(); } public void reopen() { if (memStart == 0) { restoreInitialCapacity(); } } @Override public void restoreInitialCapacity() { if (memStart == 0) { final long sizeBytes = entrySize * TABLE_SIZE; memStart = Unsafe.malloc(sizeBytes, memoryTag); memLimit = memStart + sizeBytes; } if (keyMemStart == 0) { keyMemStart = Unsafe.malloc(KEY_SIZE, memoryTag); } clear(); } @Override public void setKeyCapacity(int newKeyCapacity) { // no-op } @Override public long size() { return size; } @Override public MapValue valueAt(long startAddress) { return valueOf(startAddress, false, value); } @Override public MapKey withKey() { return key.init(); } private long getStartAddress(short key) { return memStart + entrySize * Short.toUnsignedInt(key); } private Unordered2MapValue valueOf(long startAddress, boolean newValue, Unordered2MapValue value) { return value.of(startAddress, memLimit, newValue); } long entrySize() { return entrySize; } boolean isZeroKey(long startAddress) { return Unsafe.getUnsafe().getShort(startAddress) == 0; } class Key implements MapKey { protected long appendAddress; @Override public long commit() { assert appendAddress <= keyMemStart + KEY_SIZE; return KEY_SIZE; // we don't need to track the actual key size } @Override public void copyFrom(MapKey srcKey) { Key src2Key = (Key) srcKey; copyFromRawKey(src2Key.startAddress()); } @Override public MapValue createValue() { short key = Unsafe.getUnsafe().getShort(keyMemStart); if (key != 0) { long startAddress = getStartAddress(key); short k = Unsafe.getUnsafe().getShort(startAddress); size += (k == 0) ? 1 : 0; Unsafe.getUnsafe().putShort(startAddress, key); return valueOf(startAddress, k == 0, value); } if (hasZero) { return valueOf(memStart, false, value); } size++; hasZero = true; return valueOf(memStart, true, value); } @Override public MapValue createValue(long hashCode) { return createValue(); } @Override public MapValue findValue() { return findValue(value); } @Override public MapValue findValue2() { return findValue(value2); } @Override public MapValue findValue3() { return findValue(value3); } @Override public long hash() { return 0; // no-op } public Key init() { appendAddress = keyMemStart; return this; } @Override public void put(Record record, RecordSink sink) { sink.copy(record, this); } @Override public void putBin(BinarySequence value) { throw new UnsupportedOperationException(); } @Override public void putBool(boolean value) { Unsafe.getUnsafe().putByte(appendAddress, (byte) (value ? 1 : 0)); appendAddress += 1L; } @Override public void putByte(byte value) { Unsafe.getUnsafe().putByte(appendAddress, value); appendAddress += 1L; } @Override public void putChar(char value) { Unsafe.getUnsafe().putChar(appendAddress, value); appendAddress += 2L; } @Override public void putDate(long value) { throw new UnsupportedOperationException(); } @Override public void putDouble(double value) { throw new UnsupportedOperationException(); } @Override public void putFloat(float value) { throw new UnsupportedOperationException(); } @Override public void putIPv4(int value) { putInt(value); } @Override public void putInt(int value) { throw new UnsupportedOperationException(); } @Override public void putInterval(Interval interval) { throw new UnsupportedOperationException(); } @Override public void putLong(long value) { throw new UnsupportedOperationException(); } @Override public void putLong128(long lo, long hi) { throw new UnsupportedOperationException(); } @Override public void putLong256(Long256 value) { throw new UnsupportedOperationException(); } @Override public void putLong256(long l0, long l1, long l2, long l3) { throw new UnsupportedOperationException(); } @Override public void putRecord(Record value) { // no-op } @Override public void putShort(short value) { Unsafe.getUnsafe().putShort(appendAddress, value); appendAddress += 2L; } @Override public void putStr(CharSequence value) { throw new UnsupportedOperationException(); } @Override public void putStr(CharSequence value, int lo, int hi) { throw new UnsupportedOperationException(); } @Override public void putTimestamp(long value) { throw new UnsupportedOperationException(); } @Override public void putVarchar(Utf8Sequence value) { throw new UnsupportedOperationException(); } @Override public void skip(int bytes) { appendAddress += bytes; } private MapValue findValue(Unordered2MapValue value) { short key = Unsafe.getUnsafe().getShort(keyMemStart); if (key != 0) { long startAddress = getStartAddress(key); short k = Unsafe.getUnsafe().getShort(startAddress); return k != 0 ? valueOf(startAddress, false, value) : null; } return hasZero ? valueOf(memStart, false, value) : null; } void copyFromRawKey(long srcPtr) { short srcKey = Unsafe.getUnsafe().getShort(srcPtr); Unsafe.getUnsafe().putShort(appendAddress, srcKey); appendAddress += KEY_SIZE; } long startAddress() { return keyMemStart; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy