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

io.questdb.cairo.map.UnorderedVarcharMap 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.griffin.engine.LimitOverflowException;
import io.questdb.griffin.engine.groupby.FastGroupByAllocator;
import io.questdb.griffin.engine.groupby.GroupByAllocator;
import io.questdb.std.*;
import io.questdb.std.bytes.Bytes;
import io.questdb.std.bytes.DirectByteSink;
import io.questdb.std.str.Utf8Sequence;
import org.jetbrains.annotations.Nullable;

/**
 * UnorderedVarcharMap is an off-heap hash table with single varchar key 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 UnorderedVarcharMap 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. *

* Key must be single varchar. Only insertions and updates operations are * supported meaning that a key can't be removed from the map once it was inserted. *

* The hash table is organized in off-heap memory for key-value pairs. It's a hash table with * open addressing. The hash table uses linear probing. *

* Key-value pairs stored in the hash table may have the following layout: *

 * | Hash code 32 LSBs |    Size    | Varchar pointer | Value columns 0..V |
 * +-------------------+------------+-----------------+--------------------+
 * |       4 bytes     |  4 bytes   |     8 bytes     |         -          |
 * +-------------------+------------+-----------------+--------------------+
 * 
* Length is a 30-bit integer, the top 2 bits are reserved for flags. The flags are: *
    *
  • Bit 31 (the highest bit, indexing from zero): ascii flag, this is also set for empty and null sequences
  • *
  • Bit 30: null bit
  • *
* The highest bit of the varchar pointer is set when the pointer is unstable, i.e. it points to our own arena * memory. The lower 63 bits are the actual pointer value. */ public class UnorderedVarcharMap implements Map, Reopenable { static final byte FLAG_IS_ASCII = (byte) (1 << 7); static final byte FLAG_IS_NULL = (byte) (1 << 6); static final long KEY_SIZE = 2 * Long.BYTES; static final int MASK_FLAGS_FROM_SIZE = 0x3FFFFFFF; // clear top 2 bits: ascii and null static final long PTR_UNSTABLE_MASK = 0x8000000000000000L; // 63 bits static final long PTR_MASK = ~PTR_UNSTABLE_MASK; static final int SIZE_IS_NULL = 1 << 30; private static final int KEY_SINK_INITIAL_CAPACITY = 16; private static final long MAX_SAFE_INT_POW_2 = 1L << 31; private static final int MIN_KEY_CAPACITY = 16; private final GroupByAllocator allocator; private final UnorderedVarcharMapCursor cursor; private final long entrySize; private final Key key; private final DirectByteSink keySink; // Used to copy keys with unstable pointers. private final double loadFactor; private final int maxResizes; private final int memoryTag; private final UnorderedVarcharMapRecord record; private final UnorderedVarcharMapValue value; private final UnorderedVarcharMapValue value2; private final UnorderedVarcharMapValue value3; private int free; private int initialKeyCapacity; private int keyCapacity; private int mapSize = 0; private long mask; private long memLimit; // Hash table memory limit pointer. private long memStart; // Hash table memory start pointer. private int nResizes; public UnorderedVarcharMap( @Transient @Nullable ColumnTypes valueTypes, int keyCapacity, double loadFactor, int maxResizes, long allocatorDefaultChunkSize, long allocatorMaxChunkSize ) { this(valueTypes, keyCapacity, loadFactor, maxResizes, MemoryTag.NATIVE_UNORDERED_MAP, allocatorDefaultChunkSize, allocatorMaxChunkSize); } UnorderedVarcharMap( @Nullable @Transient ColumnTypes valueTypes, int keyCapacity, double loadFactor, int maxResizes, int memoryTag, long allocatorDefaultChunkSize, long allocatorMaxChunkSize ) { assert loadFactor > 0 && loadFactor < 1d; try { this.memoryTag = memoryTag; this.loadFactor = loadFactor; this.keyCapacity = (int) (keyCapacity / loadFactor); this.keyCapacity = this.initialKeyCapacity = Math.max(Numbers.ceilPow2(this.keyCapacity), MIN_KEY_CAPACITY); this.maxResizes = maxResizes; mask = this.keyCapacity - 1; free = (int) (this.keyCapacity * loadFactor); nResizes = 0; 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.align8b(KEY_SIZE + valueSize); final long sizeBytes = entrySize * this.keyCapacity; memStart = Unsafe.malloc(sizeBytes, memoryTag); Vect.memset(memStart, sizeBytes, 0); memLimit = memStart + sizeBytes; keySink = new DirectByteSink(KEY_SINK_INITIAL_CAPACITY); value = new UnorderedVarcharMapValue(valueSize, valueOffsets); value2 = new UnorderedVarcharMapValue(valueSize, valueOffsets); value3 = new UnorderedVarcharMapValue(valueSize, valueOffsets); record = new UnorderedVarcharMapRecord(valueSize, valueOffsets, value, valueTypes); cursor = new UnorderedVarcharMapCursor(record, this); key = new Key(); allocator = new FastGroupByAllocator(allocatorDefaultChunkSize, allocatorMaxChunkSize, false); } catch (Throwable th) { close(); throw th; } } // public for testing public static long packHashSizeFlags(long hash, int size, byte flags) { // size must be within 30 bits assert size < (1 << 30); // flags must have lower 6 bits clear assert (flags & 0x3f) == 0; int sizeAndFlags = size | (flags << 24); return ((long) sizeAndFlags << 32) | (hash & 0xffffffffL); } // public for testing public static long unpackHash(long hashSizeFlags) { return hashSizeFlags & 0xffffffffL; } @Override public void clear() { free = (int) (keyCapacity * loadFactor); mapSize = 0; nResizes = 0; Vect.memset(memStart, memLimit - memStart, 0); Misc.free(allocator); // free all memory, but allocator remains usable for further allocations } @Override public void close() { if (memStart != 0) { memLimit = memStart = Unsafe.free(memStart, memLimit - memStart, memoryTag); free = 0; mapSize = 0; } Misc.free(keySink); Misc.free(allocator); } @Override public MapRecordCursor getCursor() { return cursor.init(memStart, memLimit, mapSize); } @Override public long getHeapSize() { return memLimit - memStart + allocator.allocated(); } @Override public int getKeyCapacity() { return keyCapacity; } @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 srcMapSize = srcMap.size(); if (srcMapSize == 0) { return; } UnorderedVarcharMap srcVarcharMap = (UnorderedVarcharMap) srcMap; OUTER: for (long srcAddr = srcVarcharMap.memStart; srcAddr < srcVarcharMap.memLimit; srcAddr += entrySize) { long srcHashSizeFlags = Unsafe.getUnsafe().getLong(srcAddr); if (srcHashSizeFlags == 0) { continue; } long srcPtrWithUnstableFlags = Unsafe.getUnsafe().getLong(srcAddr + 8); int srcSize = unpackSize(srcHashSizeFlags); // mask is guaranteed to have 32 or fewer bits sets -> we can use the stored 32 LSBs of the // hash since the higher bits will be masked out and won't contribute to destAddr calculation long srcHash = unpackHash(srcHashSizeFlags); long destAddr = getStartAddress(srcHash & mask); for (; ; ) { long dstHashSizeFlags = Unsafe.getUnsafe().getLong(destAddr); if (dstHashSizeFlags == 0) { break; } else if (dstHashSizeFlags == srcHashSizeFlags) { // lower 32 bits of hash, size, and flags match, let's compare keys. long dstPtrWithUnstableFlags = Unsafe.getUnsafe().getLong(destAddr + 8); if (Vect.memeq(srcPtrWithUnstableFlags & PTR_MASK, dstPtrWithUnstableFlags & PTR_MASK, srcSize)) { // Match found, merge values. mergeFunc.merge( valueAt(destAddr), srcVarcharMap.valueAt(srcAddr) ); continue OUTER; } } destAddr = getNextAddress(destAddr); } // dst key not found, insert src key-value pair if ((srcPtrWithUnstableFlags & PTR_UNSTABLE_MASK) == 0) { // stable pointer Vect.memcpy(destAddr, srcAddr, entrySize); } else { // unstable pointer, copy key to our memory long arenaPtr = allocator.malloc(srcSize); Vect.memcpy(arenaPtr, srcPtrWithUnstableFlags & PTR_MASK, srcSize); long arenaPtrWithUnstableFlags = arenaPtr | PTR_UNSTABLE_MASK; Unsafe.getUnsafe().putLong(destAddr, srcHashSizeFlags); Unsafe.getUnsafe().putLong(destAddr + 8, arenaPtrWithUnstableFlags); // copy value Vect.memcpy(destAddr + KEY_SIZE, srcAddr + KEY_SIZE, entrySize - KEY_SIZE); } mapSize++; if (--free == 0) { rehash(); } } } @Override public void reopen(int keyCapacity, long heapSize) { if (memStart == 0) { keyCapacity = (int) (keyCapacity / loadFactor); initialKeyCapacity = Math.max(Numbers.ceilPow2(keyCapacity), MIN_KEY_CAPACITY); restoreInitialCapacity(); } } public void reopen() { if (memStart == 0) { // handles both mem and offsets restoreInitialCapacity(); } } @Override public void restoreInitialCapacity() { if (memStart == 0 || keyCapacity != initialKeyCapacity) { keyCapacity = initialKeyCapacity; mask = keyCapacity - 1; final long sizeBytes = entrySize * keyCapacity; if (memStart == 0) { memStart = Unsafe.malloc(sizeBytes, memoryTag); } else { memStart = Unsafe.realloc(memStart, memLimit - memStart, sizeBytes, memoryTag); } memLimit = memStart + sizeBytes; keySink.reopen(); } clear(); } @Override public void setKeyCapacity(int newKeyCapacity) { long requiredCapacity = (long) (newKeyCapacity / loadFactor); if (requiredCapacity > MAX_SAFE_INT_POW_2) { throw CairoException.nonCritical().put("map capacity overflow"); } rehash(Numbers.ceilPow2((int) requiredCapacity)); } @Override public long size() { return mapSize; } @Override public MapValue valueAt(long startAddress) { return valueOf(startAddress, false, value); } @Override public MapKey withKey() { return key.init(); } private UnorderedVarcharMapValue asNew( long startAddress, long hash, long keyPtrWithUnstableFlag, int keySize, long keyHashSizeFlags, UnorderedVarcharMapValue value ) { Unsafe.getUnsafe().putLong(startAddress, keyHashSizeFlags); if ((keyPtrWithUnstableFlag & PTR_UNSTABLE_MASK) == 0) { // stable pointer Unsafe.getUnsafe().putLong(startAddress + 8L, keyPtrWithUnstableFlag); } else { // unstable pointer, copy key to our memory long arenaPtr = allocator.malloc(keySize); Vect.memcpy(arenaPtr, keyPtrWithUnstableFlag & PTR_MASK, keySize); long arenaPtrWithUnstableFlags = arenaPtr | PTR_UNSTABLE_MASK; Unsafe.getUnsafe().putLong(startAddress + 8, arenaPtrWithUnstableFlags); } if (--free == 0) { rehash(); // Index may have changed after rehash, so we need to find the key. startAddress = getStartAddress(hash & mask); long ptr = keyPtrWithUnstableFlag & PTR_MASK; for (; ; ) { long loadedHashSizeFlags = Unsafe.getUnsafe().getLong(startAddress); if (loadedHashSizeFlags == keyHashSizeFlags) { long entryPtrWithUnstableFlag = Unsafe.getUnsafe().getLong(startAddress + 8); if (Vect.memeq(entryPtrWithUnstableFlag & PTR_MASK, ptr, keySize)) { break; } } startAddress = getNextAddress(startAddress); } } mapSize++; return valueOf(startAddress, true, value); } // Advance through the map data structure sequentially, // avoiding multiplication and pseudo-random access. private long getNextAddress(long entryAddress) { entryAddress += entrySize; if (entryAddress < memLimit) { return entryAddress; } return memStart; } private long getStartAddress(long memStart, long index) { return memStart + entrySize * index; } private long getStartAddress(long index) { return getStartAddress(memStart, index); } private UnorderedVarcharMapValue probe0( long startAddress, long hash, long ptrWithUnstableFlag, int size, long packedHashSizeFlagsToFind, UnorderedVarcharMapValue value ) { long ptr = ptrWithUnstableFlag & PTR_MASK; for (; ; ) { startAddress = getNextAddress(startAddress); long loadedHashSizeFlags = Unsafe.getUnsafe().getLong(startAddress); if (loadedHashSizeFlags == 0) { return asNew(startAddress, hash, ptrWithUnstableFlag, size, packedHashSizeFlagsToFind, value); } if (loadedHashSizeFlags == packedHashSizeFlagsToFind) { long currentEntryPtr = Unsafe.getUnsafe().getLong(startAddress + 8) & PTR_MASK; if (Vect.memeq(currentEntryPtr, ptr, size)) { return valueOf(startAddress, false, value); } } } } private UnorderedVarcharMapValue probeReadOnly(long startAddress, long ptr, long size, long packedHashSizeFlagsToFind, UnorderedVarcharMapValue value) { for (; ; ) { startAddress = getNextAddress(startAddress); long loadedHashSizeFlags = Unsafe.getUnsafe().getLong(startAddress); if (loadedHashSizeFlags == 0) { return null; } if (loadedHashSizeFlags == packedHashSizeFlagsToFind) { long currentEntryPtr = Unsafe.getUnsafe().getLong(startAddress + 8) & PTR_MASK; if (Vect.memeq(currentEntryPtr, ptr, size)) { return valueOf(startAddress, false, value); } } } } private void rehash() { rehash((long) keyCapacity << 1); } private void rehash(long newKeyCapacity) { if (nResizes == maxResizes) { throw LimitOverflowException.instance().put("limit of ").put(maxResizes).put(" resizes exceeded in unordered map"); } if (newKeyCapacity > MAX_SAFE_INT_POW_2) { throw CairoException.nonCritical().put("map capacity overflow"); } if (newKeyCapacity <= keyCapacity) { return; } final long newSizeBytes = entrySize * newKeyCapacity; final long newMemStart = Unsafe.malloc(newSizeBytes, memoryTag); final long newMemLimit = newMemStart + newSizeBytes; Vect.memset(newMemStart, newSizeBytes, 0); final int newMask = (int) newKeyCapacity - 1; for (long addr = memStart; addr < memLimit; addr += entrySize) { long packedHashSizeFlags = Unsafe.getUnsafe().getLong(addr); if (packedHashSizeFlags == 0) { continue; } int hash = Unsafe.getUnsafe().getInt(addr); long newAddr = getStartAddress(newMemStart, hash & newMask); while (Unsafe.getUnsafe().getLong(newAddr) != 0) { newAddr += entrySize; if (newAddr >= newMemLimit) { newAddr = newMemStart; } } Vect.memcpy(newAddr, addr, entrySize); } Unsafe.free(memStart, memLimit - memStart, memoryTag); memStart = newMemStart; memLimit = newMemStart + newSizeBytes; mask = newMask; free += (int) ((newKeyCapacity - keyCapacity) * loadFactor); keyCapacity = (int) newKeyCapacity; nResizes++; } private UnorderedVarcharMapValue valueOf(long startAddress, boolean newValue, UnorderedVarcharMapValue value) { return value.of(startAddress, memLimit, newValue); } static boolean isAscii(byte flags) { return (flags & FLAG_IS_ASCII) != 0; } static boolean isNull(byte flags) { return (flags & FLAG_IS_NULL) != 0; } static boolean isSizeNull(int sizeAndFlags) { return (sizeAndFlags & SIZE_IS_NULL) != 0; } static byte unpackFlags(long packedHashSizeFlags) { return (byte) (packedHashSizeFlags >>> 56); } static int unpackSize(long packedHashSizeFlags) { int sizeAndFlags = (int) (packedHashSizeFlags >>> 32); // clear top 2 bits return sizeAndFlags & MASK_FLAGS_FROM_SIZE; } long entrySize() { return entrySize; } boolean isZeroKey(long startAddress) { long packedHashAndSize = Unsafe.getUnsafe().getLong(startAddress); return packedHashAndSize == 0; } class Key implements MapKey { private byte flags; private long ptrWithUnstableFlag; private int size; @Override public long commit() { return KEY_SIZE; // we don't need to track the actual key size } @Override public void copyFrom(MapKey srcKey) { Key srcVarcharKey = (Key) srcKey; size = srcVarcharKey.size; flags = srcVarcharKey.flags; if ((srcVarcharKey.ptrWithUnstableFlag & PTR_UNSTABLE_MASK) == 0) { // stable pointer ptrWithUnstableFlag = srcVarcharKey.ptrWithUnstableFlag; } else { // unstable pointer, copy key to key memory keySink.clear(); long srcPtr = srcVarcharKey.ptrWithUnstableFlag & PTR_MASK; keySink.put(srcPtr, srcPtr + size); long ptr = keySink.ptr(); ptrWithUnstableFlag = ptr | PTR_UNSTABLE_MASK; } } @Override public MapValue createValue() { long hash = Hash.hashMem64(ptrWithUnstableFlag & PTR_MASK, size); return createValue(hash); } @Override public MapValue createValue(long hashCode) { long index = hashCode & mask; long startAddress = getStartAddress(index); long loadedHashSizeFlags = Unsafe.getUnsafe().getLong(startAddress); long currentHashSizeFlags = packHashSizeFlags(hashCode, size, flags); if (loadedHashSizeFlags == 0) { return asNew(startAddress, hashCode, ptrWithUnstableFlag, size, currentHashSizeFlags, value); } if (loadedHashSizeFlags == currentHashSizeFlags) { long currentPtr = Unsafe.getUnsafe().getLong(startAddress + 8) & PTR_MASK; if (Vect.memeq(currentPtr, ptrWithUnstableFlag & PTR_MASK, size)) { return valueOf(startAddress, false, value); } } return probe0(startAddress, hashCode, ptrWithUnstableFlag, size, currentHashSizeFlags, value); } @Override public MapValue findValue() { return findValue(ptrWithUnstableFlag, size, flags, value); } @Override public MapValue findValue2() { return findValue(ptrWithUnstableFlag, size, flags, value2); } @Override public MapValue findValue3() { return findValue(ptrWithUnstableFlag, size, flags, value3); } @Override public long hash() { return Hash.hashMem64(ptrWithUnstableFlag & PTR_MASK, size); } public Key init() { // no-op since all fields are set in putVarchar(). // Q: What if a user does not call putVarchar()? // A: It's illegal to use an uninitialized key and will result in undefined behavior. 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) { throw new UnsupportedOperationException(); } @Override public void putByte(byte value) { throw new UnsupportedOperationException(); } @Override public void putChar(char value) { throw new UnsupportedOperationException(); } @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) { throw new UnsupportedOperationException(); } @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) { throw new UnsupportedOperationException(); } @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) { if (value == null) { size = 0; ptrWithUnstableFlag = 0; // set the 2 top bits to indicate null value (+ascii flag) flags = FLAG_IS_NULL | FLAG_IS_ASCII; } else { size = value.size(); if (value.isStable()) { ptrWithUnstableFlag = value.ptr(); } else { // unstable pointer, copy key to key memory keySink.clear(); // Try to use Vect#memcpy() if possible. if (value.ptr() != -1) { keySink.put(value.ptr(), value.ptr() + size); } else { keySink.put(value); } long ptr = keySink.ptr(); ptrWithUnstableFlag = ptr | PTR_UNSTABLE_MASK; } flags = value.isAscii() ? FLAG_IS_ASCII : 0; } // Empty string must have the ascii flag set to true, otherwise we won't be able to differentiate // between a missing entry and an empty string. // The flags are encoded into the high bits in the size field so an empty string will still have a non-zero size stored. assert size > 0 || (flags & FLAG_IS_ASCII) != 0; } @Override public void skip(int bytes) { // no-op } private MapValue findValue(long ptrWithUnstableFlag, int size, byte flags, UnorderedVarcharMapValue value) { long ptr = ptrWithUnstableFlag & PTR_MASK; long hash = Hash.hashMem64(ptr, size); long index = hash & mask; long startAddress = getStartAddress(index); long loadedHashSizeFlags = Unsafe.getUnsafe().getLong(startAddress); if (loadedHashSizeFlags == 0) { return null; } long packedHashSizeFlags = packHashSizeFlags(hash, size, flags); if (loadedHashSizeFlags == packedHashSizeFlags) { long currentPtr = Unsafe.getUnsafe().getLong(startAddress + 8) & PTR_MASK; if (Vect.memeq(currentPtr, ptr, size)) { return valueOf(startAddress, false, value); } } return probeReadOnly(startAddress, ptr, size, packedHashSizeFlags, value); } void copyFromStartAddress(long address) { long srcPackedHashAndSize = Unsafe.getUnsafe().getLong(address); byte srcFlags = UnorderedVarcharMap.unpackFlags(srcPackedHashAndSize); int srcSize = UnorderedVarcharMap.unpackSize(srcPackedHashAndSize); long srcPtrWithUnstableFlag = Unsafe.getUnsafe().getLong(address + Long.BYTES); if ((srcPtrWithUnstableFlag & PTR_UNSTABLE_MASK) == 0) { // stable pointer ptrWithUnstableFlag = srcPtrWithUnstableFlag; } else { // unstable pointer, copy key to key memory keySink.clear(); long srcPtr = srcPtrWithUnstableFlag & PTR_MASK; keySink.put(srcPtr, srcPtr + srcSize); long ptr = keySink.ptr(); ptrWithUnstableFlag = ptr | PTR_UNSTABLE_MASK; } size = srcSize; flags = srcFlags; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy