From 7486a57b6d266540fd1c9beb6a9ada5be4e3b175 Mon Sep 17 00:00:00 2001 From: sepgh <13250403+sepgh@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:16:28 +0330 Subject: [PATCH] fix: expand bitmap when turning off a value larger than size --- .../github/sepgh/testudo/index/Bitmap.java | 25 ++++++-- .../index/DuplicateBitmapIndexManager.java | 59 +++++++++++-------- .../DuplicateBitmapIndexManagerTestCase.java | 8 +-- 3 files changed, 55 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/github/sepgh/testudo/index/Bitmap.java b/src/main/java/com/github/sepgh/testudo/index/Bitmap.java index da1d977..2b22d92 100644 --- a/src/main/java/com/github/sepgh/testudo/index/Bitmap.java +++ b/src/main/java/com/github/sepgh/testudo/index/Bitmap.java @@ -19,7 +19,7 @@ public Bitmap(Class kClass, byte[] data) { this.width = data.length * Byte.SIZE; } - public void on(K k) { + public boolean on(K k) { BigInteger bitIndex = convertKeyToBigInteger(k); // Convert to BigInteger for safety ensureCapacity(bitIndex); // Resize if necessary @@ -27,20 +27,32 @@ public void on(K k) { int byteIndex = bitIndex.divide(BigInteger.valueOf(Byte.SIZE)).intValue(); int bitPosition = bitIndex.mod(BigInteger.valueOf(Byte.SIZE)).intValue(); + if ((data[byteIndex] & (1 << bitPosition)) != 0) + return false; + // Set the bit data[byteIndex] |= (byte) (1 << bitPosition); + return true; } - public void off(K k) { + public boolean off(K k) { BigInteger bitIndex = convertKeyToBigInteger(k); // Convert to BigInteger for safety - ensureCapacity(bitIndex); // Resize if necessary + + if (needsExpansion(bitIndex)){ + return false; + } // Calculate the byte and bit position int byteIndex = bitIndex.divide(BigInteger.valueOf(Byte.SIZE)).intValue(); int bitPosition = bitIndex.mod(BigInteger.valueOf(Byte.SIZE)).intValue(); + if ((data[byteIndex] & (1 << bitPosition)) == 0){ + return false; + } + // Clear the bit data[byteIndex] &= (byte) ~(1 << bitPosition); + return true; } // Convert K to a BigInteger, handling UnsignedLong values beyond Long.MAX_VALUE @@ -51,9 +63,13 @@ private BigInteger convertKeyToBigInteger(K k) { return BigInteger.valueOf(k.longValue()); // Handles Long, Integer, etc. } + private boolean needsExpansion(BigInteger bitIndex) { + return bitIndex.compareTo(BigInteger.valueOf(width)) >= 0; + } + // Ensure the byte array has enough space to handle the bit at bitIndex private void ensureCapacity(BigInteger bitIndex) { - if (bitIndex.compareTo(BigInteger.valueOf(width)) >= 0) { + if (needsExpansion(bitIndex)) { // We need to grow the array to accommodate the new bit index int newWidth = bitIndex.intValue() + 1; // At least one more than the current bit index int newLength = (newWidth + Byte.SIZE - 1) / Byte.SIZE; // Convert bits to bytes @@ -114,7 +130,6 @@ public K next() { if ((isBitSet && checkForOnBit) || (!isBitSet && !checkForOnBit)) { lastReturnedBitIndex = BigInteger.valueOf(currentByteIndex).multiply(BigInteger.valueOf(Byte.SIZE)).add(BigInteger.valueOf(currentBitPosition)); - System.out.println(lastReturnedBitIndex.intValue()); moveToNextBit(); return convertIndexToK(lastReturnedBitIndex); } diff --git a/src/main/java/com/github/sepgh/testudo/index/DuplicateBitmapIndexManager.java b/src/main/java/com/github/sepgh/testudo/index/DuplicateBitmapIndexManager.java index f0e6bf0..0ddc56d 100644 --- a/src/main/java/com/github/sepgh/testudo/index/DuplicateBitmapIndexManager.java +++ b/src/main/java/com/github/sepgh/testudo/index/DuplicateBitmapIndexManager.java @@ -30,7 +30,6 @@ public DuplicateBitmapIndexManager(int collectionId, UniqueTreeIndexManager pointerOptional = this.indexManager.getIndex(identifier); @@ -45,38 +44,42 @@ public boolean addIndex(K identifier, V value) throws InternalOperationException int initialSize = dbObject.getDataSize(); Bitmap vBitmap = new Bitmap<>(valueIndexBinaryObjectFactory.getType(), dbObject.getData()); - vBitmap.on(value); - - if (initialSize >= vBitmap.getData().length) { - databaseStorageManager.update(pointer, dbObject1 -> { + boolean result = vBitmap.on(value); + + if (result) { + if (initialSize >= vBitmap.getData().length) { + databaseStorageManager.update(pointer, dbObject1 -> { + try { + dbObject1.modifyData(vBitmap.getData()); + } catch (VerificationException.InvalidDBObjectWrapper e) { + throw new RuntimeException(e); + } + }); + } else { + Pointer pointer1 = databaseStorageManager.store(collectionId, 1, vBitmap.getData()); try { - dbObject1.modifyData(vBitmap.getData()); - } catch (VerificationException.InvalidDBObjectWrapper e) { + this.indexManager.updateIndex(identifier, pointer1); + databaseStorageManager.remove(pointer); + } catch (IndexMissingException e) { throw new RuntimeException(e); } - }); - } else { - Pointer pointer1 = databaseStorageManager.store(collectionId, 1, vBitmap.getData()); - try { - this.indexManager.updateIndex(identifier, pointer1); - databaseStorageManager.remove(pointer); - } catch (IndexMissingException e) { - throw new RuntimeException(e); } } + + return result; } else { Bitmap vBitmap = new Bitmap<>(valueIndexBinaryObjectFactory.getType(), new byte[1]); vBitmap.on(value); Pointer pointer = databaseStorageManager.store(collectionId, 1, vBitmap.getData()); try { this.indexManager.addIndex(identifier, pointer); + return true; } catch (IndexExistsException e) { // Another thread stored index? shouldn't happen, but retry - this.addIndex(identifier, value); + return this.addIndex(identifier, value); } } - return true; } @@ -104,15 +107,19 @@ public boolean removeIndex(K identifier, V value) throws InternalOperationExcept if (dbObjectOptional.isPresent()) { DBObject dbObject = dbObjectOptional.get(); Bitmap vBitmap = new Bitmap<>(valueIndexBinaryObjectFactory.getType(), dbObject.getData()); - vBitmap.off(value); - databaseStorageManager.update(pointer, dbObject1 -> { - try { - dbObject1.modifyData(vBitmap.getData()); - } catch (VerificationException.InvalidDBObjectWrapper e) { - throw new RuntimeException(e); - } - }); - return true; + boolean result = vBitmap.off(value); + + if (result) { + databaseStorageManager.update(pointer, dbObject1 -> { + try { + dbObject1.modifyData(vBitmap.getData()); + } catch (VerificationException.InvalidDBObjectWrapper e) { + throw new RuntimeException(e); + } + }); + } + + return result; } } diff --git a/src/test/java/com/github/sepgh/test/index/DuplicateBitmapIndexManagerTestCase.java b/src/test/java/com/github/sepgh/test/index/DuplicateBitmapIndexManagerTestCase.java index eda712a..f53201d 100644 --- a/src/test/java/com/github/sepgh/test/index/DuplicateBitmapIndexManagerTestCase.java +++ b/src/test/java/com/github/sepgh/test/index/DuplicateBitmapIndexManagerTestCase.java @@ -79,7 +79,7 @@ public void test_addRemoveAndIterate() throws Exception { int[] testItems = new int[]{1,2,3,4,9,10,11,12}; for (int item : testItems) { - duplicateIndexManager.addIndex(1, item); + Assertions.assertTrue(duplicateIndexManager.addIndex(1, item)); } @@ -96,11 +96,7 @@ public void test_addRemoveAndIterate() throws Exception { Assertions.assertTrue(duplicateIndexManager.removeIndex(1, testItems[0])); - - // Todo: items that dont exist should return false when getting removed - // Currently an exception is thrown because doing `off()` on bitmap will extend it's size, - // and updating with extended size is not valid -// Assertions.assertFalse(duplicateIndexManager.removeIndex(1, 10000)); + Assertions.assertFalse(duplicateIndexManager.removeIndex(1, 10000)); listIteratorOptional = duplicateIndexManager.getIndex(1);