diff --git a/pom.xml b/pom.xml index efc324c..e3122bd 100644 --- a/pom.xml +++ b/pom.xml @@ -29,6 +29,11 @@ argparse4j 0.9.0 + + it.unimi.dsi + fastutil-core + 8.5.12 + com.github.seancfoley ipaddress diff --git a/src/main/java/it/cavallium/rockserver/core/Main.java b/src/main/java/it/cavallium/rockserver/core/Main.java index 39bae4c..dcf1985 100644 --- a/src/main/java/it/cavallium/rockserver/core/Main.java +++ b/src/main/java/it/cavallium/rockserver/core/Main.java @@ -81,7 +81,7 @@ public class Main { switch (url.getScheme()) { case "unix" -> clientBuilder.setUnixSocket(UnixDomainSocketAddress.of(Path.of(url.getPath()))); - case "file" -> clientBuilder.setEmbeddedPath(Path.of(url.getPath())); + case "file" -> clientBuilder.setEmbeddedPath(Path.of((url.getAuthority() != null ? url.getAuthority() : "") + url.getPath()).normalize()); case "memory" -> clientBuilder.setEmbeddedInMemory(true); case "rocksdb" -> clientBuilder.setAddress(new HostName(url.getHost()).asInetSocketAddress()); default -> throw new IllegalArgumentException("Invalid scheme: " + url.getScheme()); diff --git a/src/main/java/it/cavallium/rockserver/core/common/ColumnHashType.java b/src/main/java/it/cavallium/rockserver/core/common/ColumnHashType.java new file mode 100644 index 0000000..9e9fc99 --- /dev/null +++ b/src/main/java/it/cavallium/rockserver/core/common/ColumnHashType.java @@ -0,0 +1,34 @@ +package it.cavallium.rockserver.core.common; + +import it.cavallium.rockserver.core.impl.ColumnInstance; +import it.cavallium.rockserver.core.impl.XXHash32; +import java.lang.foreign.MemorySegment; + +public enum ColumnHashType implements HashFunction { + XXHASH32(Integer.BYTES, (inputData, hashResult) -> XXHash32.getInstance() + .hash(inputData, 0, Math.toIntExact(inputData.byteSize()), 0, hashResult)), + XXHASH8(Byte.BYTES, (inputData, hashResult) -> { + var xxHash = XXHash32.getInstance().hash(Utils.toByteArray(inputData), 0, Math.toIntExact(inputData.byteSize()), 0); + hashResult.set(ColumnInstance.BIG_ENDIAN_BYTES, 0, (byte) xxHash); + }), + ALLSAME8(Byte.BYTES, (inputData, hashResult) -> { + hashResult.set(ColumnInstance.BIG_ENDIAN_BYTES, 0, (byte) 0); + }); + + private final int bytes; + private final HashFunction hashFunction; + + ColumnHashType(int bytes, HashFunction hashFunction) { + this.bytes = bytes; + this.hashFunction = hashFunction; + } + + public int bytesSize() { + return bytes; + } + + public void hash(MemorySegment inputData, MemorySegment hashResult) { + hashFunction.hash(inputData, hashResult); + } + +} diff --git a/src/main/java/it/cavallium/rockserver/core/common/ColumnSchema.java b/src/main/java/it/cavallium/rockserver/core/common/ColumnSchema.java index 18938bd..42ef56c 100644 --- a/src/main/java/it/cavallium/rockserver/core/common/ColumnSchema.java +++ b/src/main/java/it/cavallium/rockserver/core/common/ColumnSchema.java @@ -1,18 +1,43 @@ package it.cavallium.rockserver.core.common; -public record ColumnSchema(int[] keys, int variableLengthKeysCount, boolean hasValue) { - public ColumnSchema { - if (variableLengthKeysCount > keys.length) { - throw new IllegalArgumentException("variable length keys count must be less or equal keysCount"); +import it.cavallium.rockserver.core.common.RocksDBException.RocksDBErrorType; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.lang.foreign.MemorySegment; + +public record ColumnSchema(IntList keys, ObjectList variableTailKeys, boolean hasValue) { + + public static ColumnSchema of(IntList fixedKeys, ObjectList variableTailKeys, boolean hasValue) { + IntList keys; + if (!variableTailKeys.isEmpty()) { + keys = new IntArrayList(fixedKeys.size() + variableTailKeys.size()); + keys.addAll(fixedKeys); + for (ColumnHashType variableTailKey : variableTailKeys) { + keys.add(variableTailKey.bytesSize()); + } + } else { + keys = fixedKeys; } - for (int i = 0; i < keys.length - variableLengthKeysCount; i++) { - if (keys[i] <= 0) { - throw new UnsupportedOperationException("Key length must be > 0"); + return new ColumnSchema(keys, variableTailKeys, hasValue); + } + + public ColumnSchema { + final int keysSize = keys.size(); + final int variableKeysSize = variableTailKeys.size(); + if (variableKeysSize > keysSize) { + throw new RocksDBException(RocksDBErrorType.KEYS_COUNT_MISMATCH, "variable length keys count must be less or equal keysCount"); + } + for (int i = 0; i < keysSize - variableKeysSize; i++) { + if (keys.getInt(i) <= 0) { + throw new RocksDBException(RocksDBErrorType.KEY_LENGTH_MISMATCH, "Key length must be > 0"); } } - for (int i = keys.length - variableLengthKeysCount; i < keys.length; i++) { - if (keys[i] <= 1) { - throw new UnsupportedOperationException("Key hash length must be > 1"); + for (int i = keysSize - variableKeysSize; i < keysSize; i++) { + var hash = variableTailKeys.get(i - (keysSize - variableKeysSize)); + var keySize = keys.getInt(i); + if (keySize != hash.bytesSize()) { + throw new RocksDBException(RocksDBErrorType.KEY_HASH_SIZE_MISMATCH, "Key hash length of type " + hash + " must be " + hash.bytesSize() + ", but it's defined as " + keySize); } } } @@ -22,15 +47,39 @@ public record ColumnSchema(int[] keys, int variableLengthKeysCount, boolean hasV * @return an array with the length of each key, variable-length keys must have the length of their hash */ @Override - public int[] keys() { + public IntList keys() { return keys; } /** * The last n keys that are variable-length */ - @Override public int variableLengthKeysCount() { - return variableLengthKeysCount; + return variableTailKeys.size(); + } + + /** + * The first n keys that are fixed + */ + public int fixedLengthKeysCount() { + return keys.size() - variableTailKeys.size(); + } + + /** + * The first n keys that are fixed + */ + public int keysCount() { + return keys.size(); + } + + public int key(int i) { + return keys.getInt(i); + } + + /** + * @param absoluteIndex index from the first fixed key + */ + public ColumnHashType variableTailKey(int absoluteIndex) { + return variableTailKeys.get(absoluteIndex - fixedLengthKeysCount()); } } diff --git a/src/main/java/it/cavallium/rockserver/core/common/HashFunction.java b/src/main/java/it/cavallium/rockserver/core/common/HashFunction.java new file mode 100644 index 0000000..e389ff0 --- /dev/null +++ b/src/main/java/it/cavallium/rockserver/core/common/HashFunction.java @@ -0,0 +1,9 @@ +package it.cavallium.rockserver.core.common; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; + +public interface HashFunction { + + void hash(MemorySegment inputData, MemorySegment hashResult); +} diff --git a/src/main/java/it/cavallium/rockserver/core/common/RocksDBException.java b/src/main/java/it/cavallium/rockserver/core/common/RocksDBException.java index ce1dfff..22cddaf 100644 --- a/src/main/java/it/cavallium/rockserver/core/common/RocksDBException.java +++ b/src/main/java/it/cavallium/rockserver/core/common/RocksDBException.java @@ -5,7 +5,7 @@ public class RocksDBException extends RuntimeException { private final RocksDBErrorType errorUniqueId; public enum RocksDBErrorType { - PUT_UNKNOWN_ERROR, PUT_2, UNEXPECTED_NULL_VALUE, PUT_1, PUT_3, GET_1, COLUMN_EXISTS, COLUMN_CREATE_FAIL, COLUMN_NOT_FOUND, COLUMN_DELETE_FAIL, CONFIG_ERROR, ROCKSDB_CONFIG_ERROR, VALUE_MUST_BE_NULL, DIRECTORY_DELETE, KEY_LENGTH_MISMATCH, UNSUPPORTED_HASH_SIZE, RAW_KEY_LENGTH_MISMATCH, KEYS_COUNT_MISMATCH, COMMIT_FAILED_TRY_AGAIN, COMMIT_FAILED, TX_NOT_FOUND, ROCKSDB_LOAD_ERROR + PUT_UNKNOWN_ERROR, PUT_2, UNEXPECTED_NULL_VALUE, PUT_1, PUT_3, GET_1, COLUMN_EXISTS, COLUMN_CREATE_FAIL, COLUMN_NOT_FOUND, COLUMN_DELETE_FAIL, CONFIG_ERROR, ROCKSDB_CONFIG_ERROR, VALUE_MUST_BE_NULL, DIRECTORY_DELETE, KEY_LENGTH_MISMATCH, UNSUPPORTED_HASH_SIZE, RAW_KEY_LENGTH_MISMATCH, KEYS_COUNT_MISMATCH, COMMIT_FAILED_TRY_AGAIN, COMMIT_FAILED, TX_NOT_FOUND, KEY_HASH_SIZE_MISMATCH, ROCKSDB_LOAD_ERROR } diff --git a/src/main/java/it/cavallium/rockserver/core/common/Utils.java b/src/main/java/it/cavallium/rockserver/core/common/Utils.java index 69fd5e9..3270b7e 100644 --- a/src/main/java/it/cavallium/rockserver/core/common/Utils.java +++ b/src/main/java/it/cavallium/rockserver/core/common/Utils.java @@ -51,7 +51,7 @@ public class Utils { } @NotNull - public static MemorySegment toMemorySegment(byte @Nullable [] array) { + public static MemorySegment toMemorySegment(byte... array) { if (array != null) { return MemorySegment.ofArray(array); } else { @@ -59,6 +59,19 @@ public class Utils { } } + @NotNull + public static MemorySegment toMemorySegmentSimple(int... array) { + if (array != null) { + var newArray = new byte[array.length]; + for (int i = 0; i < array.length; i++) { + newArray[i] = (byte) array[i]; + } + return MemorySegment.ofArray(newArray); + } else { + return MemorySegment.NULL; + } + } + public static byte[] toByteArray(MemorySegment memorySegment) { return memorySegment.toArray(BIG_ENDIAN_BYTES); } diff --git a/src/main/java/it/cavallium/rockserver/core/impl/Bucket.java b/src/main/java/it/cavallium/rockserver/core/impl/Bucket.java index 78f4786..3e64e33 100644 --- a/src/main/java/it/cavallium/rockserver/core/impl/Bucket.java +++ b/src/main/java/it/cavallium/rockserver/core/impl/Bucket.java @@ -1,6 +1,7 @@ package it.cavallium.rockserver.core.impl; import static it.cavallium.rockserver.core.impl.ColumnInstance.BIG_ENDIAN_INT; +import static it.cavallium.rockserver.core.impl.ColumnInstance.BIG_ENDIAN_INT_UNALIGNED; import static java.lang.Math.toIntExact; import it.cavallium.rockserver.core.common.Utils; @@ -122,15 +123,16 @@ public class Bucket { } private int indexOf(MemorySegment[] bucketVariableKeys) { - for (int i = 0; i < elements.size(); i++) { + nextElement: for (int i = 0; i < elements.size(); i++) { var elem = elements.get(i); var arrayKeys = elem.getKey(); assert arrayKeys.length == bucketVariableKeys.length; for (int j = 0; j < arrayKeys.length; j++) { - if (Utils.valueEquals(arrayKeys[j], bucketVariableKeys[j])) { - return i; + if (!Utils.valueEquals(arrayKeys[j], bucketVariableKeys[j])) { + continue nextElement; } } + return i; } return -1; } @@ -157,7 +159,7 @@ public class Bucket { offset += Integer.BYTES; for (MemorySegment elementAtI : serializedElements) { var elementSize = elementAtI.byteSize(); - segment.set(BIG_ENDIAN_INT, offset, toIntExact(elementSize)); + segment.set(BIG_ENDIAN_INT_UNALIGNED, offset, toIntExact(elementSize)); offset += Integer.BYTES; MemorySegment.copy(elementAtI, 0, segment, offset, elementSize); offset += elementSize; diff --git a/src/main/java/it/cavallium/rockserver/core/impl/ColumnInstance.java b/src/main/java/it/cavallium/rockserver/core/impl/ColumnInstance.java index 681b884..834c760 100644 --- a/src/main/java/it/cavallium/rockserver/core/impl/ColumnInstance.java +++ b/src/main/java/it/cavallium/rockserver/core/impl/ColumnInstance.java @@ -69,7 +69,7 @@ public record ColumnInstance(ColumnFamilyHandle cfh, ColumnSchema schema, int fi } else { finalKey = arena.allocate(finalKeySizeBytes); long offsetBytes = 0; - for (int i = 0; i < schema.keys().length; i++) { + for (int i = 0; i < schema.keys().size(); i++) { var computedKeyAtI = computeKeyAt(arena, i, keys); var computedKeyAtISize = computedKeyAtI.byteSize(); MemorySegment.copy(computedKeyAtI, 0, finalKey, offsetBytes, computedKeyAtISize); @@ -81,20 +81,18 @@ public record ColumnInstance(ColumnFamilyHandle cfh, ColumnSchema schema, int fi } private MemorySegment computeKeyAt(Arena arena, int i, MemorySegment[] keys) { - if (i < schema.keys().length - schema.variableLengthKeysCount()) { - if (keys[i].byteSize() != schema.keys()[i]) { + if (i < schema.keys().size() - schema.variableLengthKeysCount()) { + if (keys[i].byteSize() != schema.key(i)) { throw new RocksDBException(RocksDBErrorType.KEY_LENGTH_MISMATCH, - "Key at index " + i + " has a different length than expected! Expected: " + schema.keys()[i] + "Key at index " + i + " has a different length than expected! Expected: " + schema.key(i) + ", received: " + keys[i].byteSize()); } return keys[i]; } else { - if (schema.keys()[i] != Integer.BYTES) { - throw new RocksDBException(RocksDBErrorType.UNSUPPORTED_HASH_SIZE, - "Hash size different than 32-bit is currently unsupported"); - } else { - return XXHash32.getInstance().hash(arena, keys[i], 0, 0, 0); - } + var tailKey = schema.variableTailKey(i); + var hashResult = arena.allocate(tailKey.bytesSize()); + tailKey.hash(keys[i], hashResult); + return hashResult; } } @@ -107,9 +105,9 @@ public record ColumnInstance(ColumnFamilyHandle cfh, ColumnSchema schema, int fi } private void validateKeyCount(MemorySegment[] keys) { - if (schema.keys().length != keys.length) { + if (schema.keys().size() != keys.length) { throw new RocksDBException(RocksDBErrorType.KEYS_COUNT_MISMATCH, - "Keys count must be equal to the column keys count. Expected: " + schema.keys().length + "Keys count must be equal to the column keys count. Expected: " + schema.keys().size() + ", got: " + keys.length); } } @@ -172,9 +170,9 @@ public record ColumnInstance(ColumnFamilyHandle cfh, ColumnSchema schema, int fi * Get only the variable-length keys */ public MemorySegment[] getBucketElementKeys(MemorySegment[] keys) { - assert keys.length == schema.keys().length; + assert keys.length == schema.keys().size(); return Arrays.copyOfRange(keys, - schema.keys().length - schema.variableLengthKeysCount(), - schema.keys().length); + schema.keys().size() - schema.variableLengthKeysCount(), + schema.keys().size()); } } diff --git a/src/main/java/it/cavallium/rockserver/core/impl/EmbeddedDB.java b/src/main/java/it/cavallium/rockserver/core/impl/EmbeddedDB.java index 576ed27..863daed 100644 --- a/src/main/java/it/cavallium/rockserver/core/impl/EmbeddedDB.java +++ b/src/main/java/it/cavallium/rockserver/core/impl/EmbeddedDB.java @@ -7,14 +7,11 @@ import static java.util.Objects.requireNonNullElse; import static org.rocksdb.KeyMayExist.KeyMayExistEnum.kExistsWithValue; import static org.rocksdb.KeyMayExist.KeyMayExistEnum.kExistsWithoutValue; -import it.cavallium.rockserver.core.common.Callback.CallbackDelta; -import it.cavallium.rockserver.core.common.Callback.CallbackPrevious; -import it.cavallium.rockserver.core.common.Callback.CallbackVoid; +import it.cavallium.rockserver.core.common.Callback; import it.cavallium.rockserver.core.common.Callback.GetCallback; import it.cavallium.rockserver.core.common.Callback.IteratorCallback; import it.cavallium.rockserver.core.common.Callback.PutCallback; import it.cavallium.rockserver.core.common.ColumnSchema; -import it.cavallium.rockserver.core.common.Callback; import it.cavallium.rockserver.core.common.Delta; import it.cavallium.rockserver.core.common.RocksDBAPI; import it.cavallium.rockserver.core.common.RocksDBException.RocksDBErrorType; @@ -39,20 +36,18 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; import org.cliffc.high_scale_lib.NonBlockingHashMapLong; -import org.github.gestalt.config.builder.GestaltBuilder; -import org.github.gestalt.config.exceptions.GestaltException; -import org.github.gestalt.config.source.ClassPathConfigSource; -import org.github.gestalt.config.source.ClassPathConfigSourceBuilder; -import org.github.gestalt.config.source.FileConfigSource; -import org.github.gestalt.config.source.FileConfigSourceBuilder; -import org.github.gestalt.config.source.StringConfigSourceBuilder; import org.jetbrains.annotations.Nullable; -import org.rocksdb.*; +import org.rocksdb.ColumnFamilyDescriptor; +import org.rocksdb.ReadOptions; +import org.rocksdb.RocksDB; +import org.rocksdb.RocksDBException; +import org.rocksdb.RocksIterator; import org.rocksdb.Status.Code; +import org.rocksdb.Transaction; +import org.rocksdb.WriteOptions; public class EmbeddedDB implements RocksDBAPI, Closeable { diff --git a/src/main/java/it/cavallium/rockserver/core/impl/XXHash32.java b/src/main/java/it/cavallium/rockserver/core/impl/XXHash32.java index 122e1bf..5e0ffba 100644 --- a/src/main/java/it/cavallium/rockserver/core/impl/XXHash32.java +++ b/src/main/java/it/cavallium/rockserver/core/impl/XXHash32.java @@ -34,7 +34,7 @@ public abstract class XXHash32 { * Compute the big-endian 32-bits hash of buf[off:off+len] using seed * seed. */ - public abstract MemorySegment hash(Arena arena, MemorySegment buf, int off, int len, int seed); + public abstract void hash(MemorySegment buf, int off, int len, int seed, MemorySegment result); @Override public String toString() { diff --git a/src/main/java/it/cavallium/rockserver/core/impl/XXHash32JavaSafe.java b/src/main/java/it/cavallium/rockserver/core/impl/XXHash32JavaSafe.java index 6116286..a2640c4 100644 --- a/src/main/java/it/cavallium/rockserver/core/impl/XXHash32JavaSafe.java +++ b/src/main/java/it/cavallium/rockserver/core/impl/XXHash32JavaSafe.java @@ -36,8 +36,8 @@ public final class XXHash32JavaSafe extends XXHash32 { public static final ByteOrder NATIVE_BYTE_ORDER = ByteOrder.nativeOrder(); public static final XXHash32 INSTANCE = new XXHash32JavaSafe(); - private static final OfInt INT_LE = ValueLayout.JAVA_INT.withOrder(ByteOrder.LITTLE_ENDIAN); - private static final OfInt INT_BE = ValueLayout.JAVA_INT.withOrder(ByteOrder.BIG_ENDIAN); + private static final OfInt INT_LE = ValueLayout.JAVA_INT_UNALIGNED.withOrder(ByteOrder.LITTLE_ENDIAN); + private static final OfInt INT_BE = ValueLayout.JAVA_INT_UNALIGNED.withOrder(ByteOrder.BIG_ENDIAN); private static final OfByte BYTE_BE = ValueLayout.JAVA_BYTE.withOrder(ByteOrder.BIG_ENDIAN); @Override @@ -104,7 +104,7 @@ public final class XXHash32JavaSafe extends XXHash32 { } @Override - public MemorySegment hash(Arena arena, MemorySegment buf, int off, int len, int seed) { + public void hash(MemorySegment buf, int off, int len, int seed, MemorySegment result) { checkRange(buf, off, len); final int end = off + len; @@ -163,7 +163,8 @@ public final class XXHash32JavaSafe extends XXHash32 { h32 *= PRIME3; h32 ^= h32 >>> 16; - return arena.allocate(INT_BE, h32); + assert result.byteSize() >= Integer.BYTES; + result.set(INT_BE, 0, h32); } private static void checkRange(byte[] buf, int off) { diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index eebafa1..ee9bf6b 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -8,6 +8,7 @@ module rockserver.core { requires high.scale.lib; requires org.github.gestalt.core; requires org.github.gestalt.hocon; + requires it.unimi.dsi.fastutil.core; exports it.cavallium.rockserver.core.client; exports it.cavallium.rockserver.core.common; diff --git a/src/test/java/it/cavallium/rockserver/core/impl/test/EmbeddedDBTest.java b/src/test/java/it/cavallium/rockserver/core/impl/test/EmbeddedDBTest.java index 2a5d455..8f05b6d 100644 --- a/src/test/java/it/cavallium/rockserver/core/impl/test/EmbeddedDBTest.java +++ b/src/test/java/it/cavallium/rockserver/core/impl/test/EmbeddedDBTest.java @@ -1,20 +1,23 @@ package it.cavallium.rockserver.core.impl.test; +import static it.cavallium.rockserver.core.common.Utils.toMemorySegmentSimple; + import it.cavallium.rockserver.core.client.EmbeddedConnection; import it.cavallium.rockserver.core.common.Callback; -import it.cavallium.rockserver.core.common.Callback.CallbackDelta; +import it.cavallium.rockserver.core.common.ColumnHashType; import it.cavallium.rockserver.core.common.ColumnSchema; import it.cavallium.rockserver.core.common.Utils; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.objects.ObjectList; import org.junit.jupiter.api.Assertions; import java.io.IOException; import java.lang.foreign.MemorySegment; -import java.util.concurrent.atomic.AtomicInteger; class EmbeddedDBTest { private EmbeddedConnection db; - private long colId; + private long colId = 0L; @org.junit.jupiter.api.BeforeEach void setUp() throws IOException { @@ -22,7 +25,21 @@ class EmbeddedDBTest { System.setProperty("rockserver.core.print-config", "false"); } db = new EmbeddedConnection(null, "test", null); - this.colId = db.createColumn("put-1", new ColumnSchema(new int[]{1, 2, 1, Integer.BYTES, Integer.BYTES}, 2, true)); + createStandardColumn(); + } + + private void createStandardColumn() { + createColumn(ColumnSchema.of(IntList.of(1, 2, 1), + ObjectList.of(ColumnHashType.XXHASH32, ColumnHashType.XXHASH32), + true + )); + } + + private void createColumn(ColumnSchema schema) { + if (colId != 0L) { + db.deleteColumn(colId); + } + colId = db.createColumn("column-test", schema); } @org.junit.jupiter.api.AfterEach @@ -32,13 +49,13 @@ class EmbeddedDBTest { } @org.junit.jupiter.api.Test - void put() { + void putSameBucketSameKey() { var key = new MemorySegment[] { - Utils.toMemorySegment(new byte[] {3}), - Utils.toMemorySegment(new byte[] {4, 6}), - Utils.toMemorySegment(new byte[] {3}), - Utils.toMemorySegment(new byte[] {1, 2, 3}), - Utils.toMemorySegment(new byte[] {0, 0, 3, 6, 7, 8}) + toMemorySegmentSimple(3), + toMemorySegmentSimple(4, 6), + toMemorySegmentSimple(3), + toMemorySegmentSimple(1, 2, 3), + toMemorySegmentSimple(0, 0, 3, 6, 7, 8) }; var value1 = MemorySegment.ofArray(new byte[] {0, 0, 3}); var value2 = MemorySegment.ofArray(new byte[] {0, 0, 5}); @@ -52,6 +69,101 @@ class EmbeddedDBTest { Assertions.assertTrue(Utils.valueEquals(delta.current(), value2)); } + @org.junit.jupiter.api.Test + void putSameBucketDifferentKey() { + createColumn(ColumnSchema.of(IntList.of(1, 2, 1), ObjectList.of(ColumnHashType.XXHASH32, ColumnHashType.ALLSAME8), true)); + + var lastKey1 = toMemorySegmentSimple(6, 7, 8); + var lastKey2 = toMemorySegmentSimple(6, 7, -48); + + var key1 = new MemorySegment[] { + toMemorySegmentSimple(3), + toMemorySegmentSimple(4, 6), + toMemorySegmentSimple(3), + toMemorySegmentSimple(1, 2, 3), + lastKey1 + }; + var key2 = new MemorySegment[] { + toMemorySegmentSimple(3), + toMemorySegmentSimple(4, 6), + toMemorySegmentSimple(3), + toMemorySegmentSimple(1, 2, 3), + lastKey2 + }; + + var value1 = MemorySegment.ofArray(new byte[] {0, 0, 3}); + var value2 = MemorySegment.ofArray(new byte[] {0, 0, 5}); + + var delta = db.put(0, colId, key1, value1, Callback.delta()); + Assertions.assertNull(delta.previous()); + Assertions.assertTrue(Utils.valueEquals(delta.current(), value1)); + + delta = db.put(0, colId, key2, value2, Callback.delta()); + Assertions.assertNull(delta.previous()); + Assertions.assertTrue(Utils.valueEquals(delta.current(), value2)); + + delta = db.put(0, colId, key2, value1, Callback.delta()); + Assertions.assertTrue(Utils.valueEquals(delta.previous(), value2)); + Assertions.assertTrue(Utils.valueEquals(delta.current(), value1)); + } + + /** + * Some keys have same bucket, some not + */ + @org.junit.jupiter.api.Test + void putMixedBucketMixedKey() { + createColumn(ColumnSchema.of(IntList.of(1, 2, 1), ObjectList.of(ColumnHashType.XXHASH32, ColumnHashType.XXHASH8), true)); + + var lastKey1 = toMemorySegmentSimple(6, 7, 8); + var collidingLastKey1 = toMemorySegmentSimple(6, 7, -48); + var lastKey2 = toMemorySegmentSimple(6, 7, 7); + + var key1 = new MemorySegment[] { + toMemorySegmentSimple(3), + toMemorySegmentSimple(4, 6), + toMemorySegmentSimple(3), + toMemorySegmentSimple(1, 2, 3), + lastKey1 + }; + var collidingKey1 = new MemorySegment[] { + toMemorySegmentSimple(3), + toMemorySegmentSimple(4, 6), + toMemorySegmentSimple(3), + toMemorySegmentSimple(1, 2, 3), + collidingLastKey1 + }; + var key2 = new MemorySegment[] { + toMemorySegmentSimple(3), + toMemorySegmentSimple(4, 6), + toMemorySegmentSimple(3), + toMemorySegmentSimple(1, 2, 3), + lastKey2 + }; + + var value1 = MemorySegment.ofArray(new byte[] {0, 0, 3}); + var value2 = MemorySegment.ofArray(new byte[] {0, 0, 5}); + + var delta = db.put(0, colId, key1, value1, Callback.delta()); + Assertions.assertNull(delta.previous()); + Assertions.assertTrue(Utils.valueEquals(delta.current(), value1)); + + delta = db.put(0, colId, collidingKey1, value2, Callback.delta()); + Assertions.assertNull(delta.previous()); + Assertions.assertTrue(Utils.valueEquals(delta.current(), value2)); + + delta = db.put(0, colId, collidingKey1, value1, Callback.delta()); + Assertions.assertTrue(Utils.valueEquals(delta.previous(), value2)); + Assertions.assertTrue(Utils.valueEquals(delta.current(), value1)); + + delta = db.put(0, colId, key2, value1, Callback.delta()); + Assertions.assertNull(delta.previous()); + Assertions.assertTrue(Utils.valueEquals(delta.current(), value1)); + + delta = db.put(0, colId, key2, value2, Callback.delta()); + Assertions.assertTrue(Utils.valueEquals(delta.previous(), value1)); + Assertions.assertTrue(Utils.valueEquals(delta.current(), value2)); + } + @org.junit.jupiter.api.Test void get() { throw new UnsupportedOperationException(); diff --git a/src/test/java/it/cavallium/rockserver/core/test/XXHash32Test.java b/src/test/java/it/cavallium/rockserver/core/test/XXHash32Test.java index 4768464..077c01b 100644 --- a/src/test/java/it/cavallium/rockserver/core/test/XXHash32Test.java +++ b/src/test/java/it/cavallium/rockserver/core/test/XXHash32Test.java @@ -26,7 +26,8 @@ public class XXHash32Test { ThreadLocalRandom.current().nextBytes(bytes); var hash = safeXxhash32.hash(bytes, 0, bytes.length, Integer.MIN_VALUE); var a = Arena.global(); - var result = myXxhash32.hash(a, a.allocateArray(OfByte.JAVA_BYTE, bytes), 0, bytes.length, Integer.MIN_VALUE); + var result = a.allocate(Integer.BYTES); + myXxhash32.hash(a.allocateArray(OfByte.JAVA_BYTE, bytes), 0, bytes.length, Integer.MIN_VALUE, result); var resultInt = result.get(ColumnInstance.BIG_ENDIAN_INT, 0); Assertions.assertEquals(hash, resultInt); } diff --git a/src/test/java/module-info.java b/src/test/java/module-info.java index f43f685..1e6f645 100644 --- a/src/test/java/module-info.java +++ b/src/test/java/module-info.java @@ -2,6 +2,7 @@ module rockserver.core.test { requires org.lz4.java; requires rockserver.core; requires org.junit.jupiter.api; + requires it.unimi.dsi.fastutil.core; opens it.cavallium.rockserver.core.test; opens it.cavallium.rockserver.core.impl.test; } \ No newline at end of file