You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by tk...@apache.org on 2022/08/18 09:31:47 UTC
[ignite-3] branch main updated: IGNITE-17534 Introduce HashIndexStorage interface and test implementation (#1014)
This is an automated email from the ASF dual-hosted git repository.
tkalkirill pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 473a34e096 IGNITE-17534 Introduce HashIndexStorage interface and test implementation (#1014)
473a34e096 is described below
commit 473a34e0963601ff8a1fe82c2a4591944f08f6f4
Author: Alexander Polovtcev <al...@gmail.com>
AuthorDate: Thu Aug 18 12:31:43 2022 +0300
IGNITE-17534 Introduce HashIndexStorage interface and test implementation (#1014)
---
.../ignite/internal/schema/BinaryTupleParser.java | 11 +-
.../ignite/internal/storage/StorageUtils.java | 52 -----
.../internal/storage/engine/MvTableStorage.java | 25 ++-
.../storage/index/HashIndexDescriptor.java | 135 +++++++++++++
...dexRowSerializer.java => HashIndexStorage.java} | 31 ++-
.../internal/storage/index/SortedIndexStorage.java | 18 +-
.../storage/AbstractMvTableStorageTest.java | 101 +++++++---
.../ConcurrentHashMapMvTableStorageTest.java | 2 +
.../chm/TestConcurrentHashMapMvTableStorage.java | 61 ++++--
.../index/AbstractHashIndexStorageTest.java | 224 +++++++++++++++++++++
.../index/AbstractSortedIndexStorageTest.java | 57 ++++--
.../storage/index/TestHashIndexStorageTest.java} | 18 +-
.../storage/index/TestSortedIndexStorageTest.java | 2 +-
.../index/impl/BinaryTupleRowDeserializer.java | 52 -----
.../index/impl/BinaryTupleRowSerializer.java | 87 ++++++--
.../storage/index/impl/TestHashIndexStorage.java | 95 +++++++++
.../internal/storage/index/impl/TestIndexRow.java | 13 +-
.../storage/index/impl/TestSortedIndexStorage.java | 14 +-
.../pagememory/AbstractPageMemoryTableStorage.java | 6 +-
.../storage/rocksdb/RocksDbTableStorage.java | 6 +-
20 files changed, 759 insertions(+), 251 deletions(-)
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleParser.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleParser.java
index 414dce3037..d83bb53bdd 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleParser.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleParser.java
@@ -59,7 +59,7 @@ public class BinaryTupleParser {
private final int valueBase;
/** Binary tuple. */
- protected final ByteBuffer buffer;
+ private final ByteBuffer buffer;
/**
* Constructor.
@@ -67,7 +67,7 @@ public class BinaryTupleParser {
* @param numElements Number of tuple elements.
* @param buffer Buffer with a binary tuple.
*/
- public BinaryTupleParser(int numElements, ByteBuffer buffer) {
+ BinaryTupleParser(int numElements, ByteBuffer buffer) {
this.numElements = numElements;
assert buffer.order() == ByteOrder.LITTLE_ENDIAN;
@@ -107,6 +107,13 @@ public class BinaryTupleParser {
return entryBase > BinaryTupleSchema.HEADER_SIZE;
}
+ /**
+ * Returns the content of this tuple as a byte buffer.
+ */
+ public ByteBuffer byteBuffer() {
+ return buffer;
+ }
+
/**
* Locate the specified tuple element.
*
diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/StorageUtils.java b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/StorageUtils.java
deleted file mode 100644
index ca45d634d3..0000000000
--- a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/StorageUtils.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.ignite.internal.storage;
-
-import java.nio.ByteBuffer;
-
-/**
- * Utility class for storages.
- */
-public class StorageUtils {
- /**
- * Returns byte buffer hash that matches corresponding array hash.
- *
- * @param buf Byte buffer.
- */
- public static int hashCode(ByteBuffer buf) {
- int result = 1;
- for (int i = buf.position(); i < buf.limit(); i++) {
- result = 31 * result + buf.get(i);
- }
- return result;
- }
-
- /**
- * Converts to an array of bytes.
- *
- * @param buf Byte buffer.
- */
- // TODO: IGNITE-16350 Get rid of copying byte arrays.
- public static byte[] toByteArray(ByteBuffer buf) {
- byte[] arr = new byte[buf.limit()];
-
- buf.get(arr);
-
- return arr;
- }
-}
diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/engine/MvTableStorage.java b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/engine/MvTableStorage.java
index 70cdacb21c..79085da53f 100644
--- a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/engine/MvTableStorage.java
+++ b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/engine/MvTableStorage.java
@@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
import org.apache.ignite.configuration.schemas.table.TableConfiguration;
import org.apache.ignite.internal.storage.MvPartitionStorage;
import org.apache.ignite.internal.storage.StorageException;
+import org.apache.ignite.internal.storage.index.HashIndexStorage;
import org.apache.ignite.internal.storage.index.SortedIndexStorage;
import org.jetbrains.annotations.Nullable;
@@ -57,24 +58,32 @@ public interface MvTableStorage {
CompletableFuture<Void> destroyPartition(int partitionId) throws StorageException;
/**
- * Creates an index with the given name.
+ * Returns an already created Sorted Index with the given name or creates a new one if it does not exist.
*
- * <p>A prerequisite for calling this method is to have the index already configured under the same name in the Table Configuration
+ * <p>In order for an index to be created, it should be already configured under the same name in the Table Configuration
* (see {@link #configuration()}).
*
+ * @param partitionId Partition ID for which this index has been configured.
* @param indexName Index name.
- * @throws StorageException if no index has been configured under the given name.
+ * @return Sorted Index storage.
+ * @throws StorageException If the given partition does not exist, or if the given index does not exist or is not configured as
+ * a sorted index.
*/
- void createIndex(String indexName);
+ SortedIndexStorage getOrCreateSortedIndex(int partitionId, String indexName);
/**
- * Returns an already created Sorted Index with the given name or {@code null} if it does not exist.
+ * Returns an already created Hash Index with the given name or creates a new one if it does not exist.
*
- * @param partitionId Partition ID for which this index has been built.
+ * <p>In order for an index to be created, it should be already configured under the same name in the Table Configuration
+ * (see {@link #configuration()}).
+ *
+ * @param partitionId Partition ID for which this index has been configured.
* @param indexName Index name.
- * @return Sorted Index storage or {@code null} if it does not exist..
+ * @return Hash Index storage.
+ * @throws StorageException If the given partition does not exist, or the given index does not exist or is not configured as a
+ * hash index.
*/
- @Nullable SortedIndexStorage getSortedIndex(int partitionId, String indexName);
+ HashIndexStorage getOrCreateHashIndex(int partitionId, String indexName);
/**
* Destroys the index under the given name and all data in it.
diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/HashIndexDescriptor.java b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/HashIndexDescriptor.java
new file mode 100644
index 0000000000..dd86345dbd
--- /dev/null
+++ b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/HashIndexDescriptor.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.storage.index;
+
+import static java.util.stream.Collectors.toUnmodifiableList;
+
+import java.util.Arrays;
+import java.util.List;
+import org.apache.ignite.configuration.schemas.table.ColumnView;
+import org.apache.ignite.configuration.schemas.table.HashIndexView;
+import org.apache.ignite.configuration.schemas.table.TableIndexView;
+import org.apache.ignite.configuration.schemas.table.TableView;
+import org.apache.ignite.internal.schema.NativeType;
+import org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter;
+import org.apache.ignite.internal.schema.configuration.SchemaDescriptorConverter;
+import org.apache.ignite.internal.storage.StorageException;
+import org.apache.ignite.internal.tostring.S;
+
+/**
+ * Descriptor for creating a Hash Index Storage.
+ *
+ * @see HashIndexStorage
+ */
+public class HashIndexDescriptor {
+ /**
+ * Descriptor of a Hash Index column.
+ */
+ public static class ColumnDescriptor {
+ private final String name;
+
+ private final NativeType type;
+
+ private final boolean nullable;
+
+ ColumnDescriptor(ColumnView tableColumnView) {
+ this.name = tableColumnView.name();
+ this.type = SchemaDescriptorConverter.convert(SchemaConfigurationConverter.convert(tableColumnView.type()));
+ this.nullable = tableColumnView.nullable();
+ }
+
+ /**
+ * Returns the name of an index column.
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns a column type.
+ */
+ public NativeType type() {
+ return type;
+ }
+
+ /**
+ * Returns {@code true} if this column can contain null values or {@code false} otherwise.
+ */
+ public boolean nullable() {
+ return nullable;
+ }
+
+ @Override
+ public String toString() {
+ return S.toString(this);
+ }
+ }
+
+ private final String name;
+
+ private final List<ColumnDescriptor> columns;
+
+ /**
+ * Creates an Index Descriptor from a given Table Configuration.
+ *
+ * @param name index name.
+ * @param tableConfig table configuration.
+ */
+ public HashIndexDescriptor(String name, TableView tableConfig) {
+ this.name = name;
+
+ TableIndexView indexConfig = tableConfig.indices().get(name);
+
+ if (indexConfig == null) {
+ throw new StorageException(String.format("Index configuration for \"%s\" could not be found", name));
+ }
+
+ if (!(indexConfig instanceof HashIndexView)) {
+ throw new StorageException(String.format(
+ "Index \"%s\" is not configured as a Hash Index. Actual type: %s",
+ name, indexConfig.type()
+ ));
+ }
+
+ String[] indexColumns = ((HashIndexView) indexConfig).columnNames();
+
+ columns = Arrays.stream(indexColumns)
+ .map(columnName -> {
+ ColumnView columnView = tableConfig.columns().get(columnName);
+
+ assert columnView != null : "Incorrect index column configuration. " + columnName + " column does not exist";
+
+ return new ColumnDescriptor(columnView);
+ })
+ .collect(toUnmodifiableList());
+ }
+
+ /**
+ * Returns this index' name.
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns the Column Descriptors that comprise a row of this index.
+ */
+ public List<ColumnDescriptor> indexColumns() {
+ return columns;
+ }
+}
diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/IndexRowSerializer.java b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/HashIndexStorage.java
similarity index 53%
rename from modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/IndexRowSerializer.java
rename to modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/HashIndexStorage.java
index a5054b475b..b35611b5a6 100644
--- a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/IndexRowSerializer.java
+++ b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/HashIndexStorage.java
@@ -17,21 +17,38 @@
package org.apache.ignite.internal.storage.index;
+import java.util.Collection;
import org.apache.ignite.internal.schema.BinaryTuple;
import org.apache.ignite.internal.storage.RowId;
/**
- * Temporary API for creating Index rows from a list of column values. All columns must be sorted according to the index columns order,
- * specified by the {@link SortedIndexDescriptor#indexColumns()}.
+ * Storage for a Hash Index.
+ *
+ * <p>This storage serves as an unordered mapping from a subset of a table's columns (a.k.a. index columns) to a set of {@link RowId}s
+ * from a single {@link org.apache.ignite.internal.storage.MvPartitionStorage} from the same table.
+ *
+ * @see org.apache.ignite.schema.definition.index.HashIndexDefinition
*/
-public interface IndexRowSerializer {
+public interface HashIndexStorage {
+ /**
+ * Returns the Index Descriptor of this storage.
+ */
+ HashIndexDescriptor indexDescriptor();
+
+ /**
+ * Returns a collection of {@code RowId}s that correspond to the given index key.
+ */
+ Collection<RowId> get(BinaryTuple key);
+
/**
- * Creates an Index row from a list of column values.
+ * Adds the given index row to the index.
*/
- IndexRow createIndexRow(Object[] columnValues, RowId rowId);
+ void put(IndexRow row);
/**
- * Creates an Prefix row from a list of column values.
+ * Removes the given row from the index.
+ *
+ * <p>Removing a non-existent row is a no-op.
*/
- BinaryTuple createIndexRowPrefix(Object[] prefixColumnValues);
+ void remove(IndexRow row);
}
diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/SortedIndexStorage.java b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/SortedIndexStorage.java
index 39a29e1917..8c9090542f 100644
--- a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/SortedIndexStorage.java
+++ b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/SortedIndexStorage.java
@@ -26,8 +26,8 @@ import org.jetbrains.annotations.Nullable;
/**
* Storage for a Sorted Index.
*
- * <p>This storage serves as a sorted mapping from a subset of a table's columns (a.k.a. index columns) to a {@link RowId}
- * from a {@link org.apache.ignite.internal.storage.MvPartitionStorage} from the same table.
+ * <p>This storage serves as a sorted mapping from a subset of a table's columns (a.k.a. index columns) to a set of {@link RowId}s
+ * from a single {@link org.apache.ignite.internal.storage.MvPartitionStorage} from the same table.
*
* @see org.apache.ignite.schema.definition.index.SortedIndexDefinition
*/
@@ -55,25 +55,15 @@ public interface SortedIndexStorage {
*/
SortedIndexDescriptor indexDescriptor();
- /**
- * Returns a factory for creating index rows for this storage.
- */
- IndexRowSerializer indexRowSerializer();
-
- /**
- * Returns a class deserializing index columns.
- */
- IndexRowDeserializer indexRowDeserializer();
-
/**
* Adds the given index row to the index.
*/
void put(IndexRow row);
/**
- * Removes the given key from the index.
+ * Removes the given row from the index.
*
- * <p>Removing a non-existent key is a no-op.
+ * <p>Removing a non-existent row is a no-op.
*/
void remove(IndexRow row);
diff --git a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/AbstractMvTableStorageTest.java b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/AbstractMvTableStorageTest.java
index cf8faaaced..b173067062 100644
--- a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/AbstractMvTableStorageTest.java
+++ b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/AbstractMvTableStorageTest.java
@@ -22,6 +22,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.concurrent.CompletableFuture;
import org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter;
@@ -39,9 +40,9 @@ import org.junit.jupiter.api.Test;
// TODO: Use this to test RocksDB-based storage, see https://issues.apache.org/jira/browse/IGNITE-17318
// TODO: Use this to test B+tree-based storage, see https://issues.apache.org/jira/browse/IGNITE-17320
public abstract class AbstractMvTableStorageTest {
- private static final String SORTED_INDEX_NAME_1 = "SORTED_IDX_1";
+ private static final String SORTED_INDEX_NAME = "SORTED_IDX";
- private static final String SORTED_INDEX_NAME_2 = "SORTED_IDX_2";
+ private static final String HASH_INDEX_NAME = "HASH_IDX";
private MvTableStorage tableStorage;
@@ -82,37 +83,41 @@ public abstract class AbstractMvTableStorageTest {
assertThat(tableStorage.getOrCreateMvPartition(partitionId), is(notNullValue()));
- tableStorage.createIndex(SORTED_INDEX_NAME_1);
-
assertThat(tableStorage.getMvPartition(partitionId), is(notNullValue()));
- assertThat(tableStorage.getSortedIndex(partitionId, SORTED_INDEX_NAME_1), is(notNullValue()));
-
- // Validate that destroying a partition also destroys associated indices.
assertThat(tableStorage.destroyPartition(partitionId), willCompleteSuccessfully());
assertThat(tableStorage.getMvPartition(partitionId), is(nullValue()));
-
- assertThat(tableStorage.getSortedIndex(partitionId, SORTED_INDEX_NAME_1), is(nullValue()));
}
/**
- * Test creating an index.
+ * Test creating a Sorted Index.
*/
@Test
public void testCreateSortedIndex() {
int partitionId = 0;
- assertThat(tableStorage.getSortedIndex(partitionId, SORTED_INDEX_NAME_1), is(nullValue()));
+ assertThrows(StorageException.class, () -> tableStorage.getOrCreateSortedIndex(partitionId, SORTED_INDEX_NAME));
- tableStorage.createIndex(SORTED_INDEX_NAME_1);
+ // Index should only be available after the associated partition has been created.
+ tableStorage.getOrCreateMvPartition(partitionId);
- assertThat(tableStorage.getSortedIndex(partitionId, SORTED_INDEX_NAME_1), is(nullValue()));
+ assertThat(tableStorage.getOrCreateSortedIndex(partitionId, SORTED_INDEX_NAME), is(notNullValue()));
+ }
+
+ /**
+ * Test creating a Hash Index.
+ */
+ @Test
+ public void testCreateHashIndex() {
+ int partitionId = 0;
+
+ assertThrows(StorageException.class, () -> tableStorage.getOrCreateHashIndex(partitionId, HASH_INDEX_NAME));
// Index should only be available after the associated partition has been created.
tableStorage.getOrCreateMvPartition(partitionId);
- assertThat(tableStorage.getSortedIndex(partitionId, SORTED_INDEX_NAME_1), is(notNullValue()));
+ assertThat(tableStorage.getOrCreateHashIndex(partitionId, HASH_INDEX_NAME), is(notNullValue()));
}
/**
@@ -124,26 +129,62 @@ public abstract class AbstractMvTableStorageTest {
tableStorage.getOrCreateMvPartition(partitionId);
- tableStorage.createIndex(SORTED_INDEX_NAME_1);
- tableStorage.createIndex(SORTED_INDEX_NAME_2);
+ assertThat(tableStorage.getOrCreateSortedIndex(partitionId, SORTED_INDEX_NAME), is(notNullValue()));
+ assertThat(tableStorage.getOrCreateHashIndex(partitionId, HASH_INDEX_NAME), is(notNullValue()));
- assertThat(tableStorage.getSortedIndex(partitionId, SORTED_INDEX_NAME_1), is(notNullValue()));
- assertThat(tableStorage.getSortedIndex(partitionId, SORTED_INDEX_NAME_2), is(notNullValue()));
+ assertThat(tableStorage.destroyIndex(SORTED_INDEX_NAME), willCompleteSuccessfully());
+ assertThat(tableStorage.destroyIndex(HASH_INDEX_NAME), willCompleteSuccessfully());
+ }
- assertThat(tableStorage.destroyIndex(SORTED_INDEX_NAME_1), willCompleteSuccessfully());
+ /**
+ * Tests that exceptions are thrown if indices are not configured correctly.
+ */
+ @Test
+ public void testMisconfiguredIndices() {
+ int partitionId = 0;
+
+ Exception e = assertThrows(
+ StorageException.class,
+ () -> tableStorage.getOrCreateSortedIndex(partitionId, SORTED_INDEX_NAME)
+ );
+
+ assertThat(e.getMessage(), is("Partition ID " + partitionId + " does not exist"));
- assertThat(tableStorage.getSortedIndex(partitionId, SORTED_INDEX_NAME_1), is(nullValue()));
- assertThat(tableStorage.getSortedIndex(partitionId, SORTED_INDEX_NAME_2), is(notNullValue()));
+ e = assertThrows(
+ StorageException.class,
+ () -> tableStorage.getOrCreateHashIndex(partitionId, HASH_INDEX_NAME)
+ );
- // Check that destroying an index twice is not an error
- assertThat(tableStorage.destroyIndex(SORTED_INDEX_NAME_1), willCompleteSuccessfully());
+ assertThat(e.getMessage(), is("Partition ID " + partitionId + " does not exist"));
- assertThat(tableStorage.getSortedIndex(partitionId, SORTED_INDEX_NAME_1), is(nullValue()));
+ tableStorage.getOrCreateMvPartition(partitionId);
+
+ e = assertThrows(
+ StorageException.class,
+ () -> tableStorage.getOrCreateHashIndex(partitionId, "INVALID_NAME")
+ );
+
+ assertThat(e.getMessage(), is("Index configuration for \"INVALID_NAME\" could not be found"));
- assertThat(tableStorage.destroyIndex(SORTED_INDEX_NAME_2), willCompleteSuccessfully());
+ e = assertThrows(
+ StorageException.class,
+ () -> tableStorage.getOrCreateHashIndex(partitionId, SORTED_INDEX_NAME)
+ );
- assertThat(tableStorage.getSortedIndex(partitionId, SORTED_INDEX_NAME_1), is(nullValue()));
- assertThat(tableStorage.getSortedIndex(partitionId, SORTED_INDEX_NAME_2), is(nullValue()));
+ assertThat(
+ e.getMessage(),
+ is(String.format("Index \"%s\" is not configured as a Hash Index. Actual type: SORTED", SORTED_INDEX_NAME))
+ );
+
+ e = assertThrows(
+ StorageException.class,
+ () -> tableStorage.getOrCreateSortedIndex(partitionId, HASH_INDEX_NAME)
+ );
+
+ assertThat(
+ e.getMessage(),
+ is(String.format("Index \"%s\" is not configured as a Sorted Index. Actual type: HASH", HASH_INDEX_NAME))
+ );
}
private void createTestTable() {
@@ -154,13 +195,13 @@ public abstract class AbstractMvTableStorageTest {
)
.withPrimaryKey("ID")
.withIndex(
- SchemaBuilders.sortedIndex(SORTED_INDEX_NAME_1)
+ SchemaBuilders.sortedIndex(SORTED_INDEX_NAME)
.addIndexColumn("COLUMN0").done()
.build()
)
.withIndex(
- SchemaBuilders.sortedIndex(SORTED_INDEX_NAME_2)
- .addIndexColumn("COLUMN0").done()
+ SchemaBuilders.hashIndex(HASH_INDEX_NAME)
+ .withColumns("COLUMN0")
.build()
)
.build();
diff --git a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/ConcurrentHashMapMvTableStorageTest.java b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/ConcurrentHashMapMvTableStorageTest.java
index c07dbce79d..a3b32fe95a 100644
--- a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/ConcurrentHashMapMvTableStorageTest.java
+++ b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/ConcurrentHashMapMvTableStorageTest.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.storage;
+import org.apache.ignite.configuration.schemas.table.HashIndexConfigurationSchema;
import org.apache.ignite.configuration.schemas.table.NullValueDefaultConfigurationSchema;
import org.apache.ignite.configuration.schemas.table.SortedIndexConfigurationSchema;
import org.apache.ignite.configuration.schemas.table.TableConfiguration;
@@ -38,6 +39,7 @@ public class ConcurrentHashMapMvTableStorageTest extends AbstractMvTableStorageT
polymorphicExtensions = {
TestConcurrentHashMapDataStorageConfigurationSchema.class,
SortedIndexConfigurationSchema.class,
+ HashIndexConfigurationSchema.class,
NullValueDefaultConfigurationSchema.class,
UnlimitedBudgetConfigurationSchema.class
},
diff --git a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/chm/TestConcurrentHashMapMvTableStorage.java b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/chm/TestConcurrentHashMapMvTableStorage.java
index 1f2aca83a5..65274307b2 100644
--- a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/chm/TestConcurrentHashMapMvTableStorage.java
+++ b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/chm/TestConcurrentHashMapMvTableStorage.java
@@ -24,8 +24,11 @@ import org.apache.ignite.configuration.schemas.table.TableConfiguration;
import org.apache.ignite.internal.storage.MvPartitionStorage;
import org.apache.ignite.internal.storage.StorageException;
import org.apache.ignite.internal.storage.engine.MvTableStorage;
+import org.apache.ignite.internal.storage.index.HashIndexDescriptor;
+import org.apache.ignite.internal.storage.index.HashIndexStorage;
import org.apache.ignite.internal.storage.index.SortedIndexDescriptor;
import org.apache.ignite.internal.storage.index.SortedIndexStorage;
+import org.apache.ignite.internal.storage.index.impl.TestHashIndexStorage;
import org.apache.ignite.internal.storage.index.impl.TestSortedIndexStorage;
import org.jetbrains.annotations.Nullable;
@@ -37,17 +40,19 @@ public class TestConcurrentHashMapMvTableStorage implements MvTableStorage {
private final Map<Integer, MvPartitionStorage> partitions = new ConcurrentHashMap<>();
- private final Map<String, Indices> sortedIndicesByName = new ConcurrentHashMap<>();
+ private final Map<String, SortedIndices> sortedIndicesByName = new ConcurrentHashMap<>();
+
+ private final Map<String, HashIndices> hashIndicesByName = new ConcurrentHashMap<>();
/**
* Class for storing Sorted Indices for a particular partition.
*/
- private static class Indices {
+ private static class SortedIndices {
private final SortedIndexDescriptor descriptor;
final Map<Integer, SortedIndexStorage> storageByPartitionId = new ConcurrentHashMap<>();
- Indices(SortedIndexDescriptor descriptor) {
+ SortedIndices(SortedIndexDescriptor descriptor) {
this.descriptor = descriptor;
}
@@ -56,6 +61,23 @@ public class TestConcurrentHashMapMvTableStorage implements MvTableStorage {
}
}
+ /**
+ * Class for storing Hash Indices for a particular partition.
+ */
+ private static class HashIndices {
+ private final HashIndexDescriptor descriptor;
+
+ final Map<Integer, HashIndexStorage> storageByPartitionId = new ConcurrentHashMap<>();
+
+ HashIndices(HashIndexDescriptor descriptor) {
+ this.descriptor = descriptor;
+ }
+
+ HashIndexStorage getOrCreateStorage(Integer partitionId) {
+ return storageByPartitionId.computeIfAbsent(partitionId, id -> new TestHashIndexStorage(descriptor));
+ }
+ }
+
public TestConcurrentHashMapMvTableStorage(TableConfiguration tableCfg) {
this.tableConfig = tableCfg;
}
@@ -78,34 +100,43 @@ public class TestConcurrentHashMapMvTableStorage implements MvTableStorage {
partitions.remove(boxedPartitionId);
sortedIndicesByName.values().forEach(indices -> indices.storageByPartitionId.remove(boxedPartitionId));
+ hashIndicesByName.values().forEach(indices -> indices.storageByPartitionId.remove(boxedPartitionId));
return CompletableFuture.completedFuture(null);
}
@Override
- public void createIndex(String indexName) {
- sortedIndicesByName.computeIfAbsent(indexName, name -> {
- var descriptor = new SortedIndexDescriptor(name, tableConfig.value());
+ public SortedIndexStorage getOrCreateSortedIndex(int partitionId, String indexName) {
+ if (!partitions.containsKey(partitionId)) {
+ throw new StorageException("Partition ID " + partitionId + " does not exist");
+ }
- return new Indices(descriptor);
- });
+ SortedIndices sortedIndices = sortedIndicesByName.computeIfAbsent(
+ indexName,
+ name -> new SortedIndices(new SortedIndexDescriptor(name, tableConfig.value()))
+ );
+
+ return sortedIndices.getOrCreateStorage(partitionId);
}
@Override
- @Nullable
- public SortedIndexStorage getSortedIndex(int partitionId, String indexName) {
- Indices indices = sortedIndicesByName.get(indexName);
-
- if (indices == null || !partitions.containsKey(partitionId)) {
- return null;
+ public HashIndexStorage getOrCreateHashIndex(int partitionId, String indexName) {
+ if (!partitions.containsKey(partitionId)) {
+ throw new StorageException("Partition ID " + partitionId + " does not exist");
}
- return indices.getOrCreateStorage(partitionId);
+ HashIndices sortedIndices = hashIndicesByName.computeIfAbsent(
+ indexName,
+ name -> new HashIndices(new HashIndexDescriptor(name, tableConfig.value()))
+ );
+
+ return sortedIndices.getOrCreateStorage(partitionId);
}
@Override
public CompletableFuture<Void> destroyIndex(String indexName) {
sortedIndicesByName.remove(indexName);
+ hashIndicesByName.remove(indexName);
return CompletableFuture.completedFuture(null);
}
diff --git a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/AbstractHashIndexStorageTest.java b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/AbstractHashIndexStorageTest.java
new file mode 100644
index 0000000000..c2ef16de5b
--- /dev/null
+++ b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/AbstractHashIndexStorageTest.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.storage.index;
+
+import static org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter.convert;
+import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willBe;
+import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static org.apache.ignite.schema.SchemaBuilders.column;
+import static org.apache.ignite.schema.SchemaBuilders.tableBuilder;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.empty;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.configuration.schemas.table.ConstantValueDefaultConfigurationSchema;
+import org.apache.ignite.configuration.schemas.table.FunctionCallDefaultConfigurationSchema;
+import org.apache.ignite.configuration.schemas.table.HashIndexConfigurationSchema;
+import org.apache.ignite.configuration.schemas.table.NullValueDefaultConfigurationSchema;
+import org.apache.ignite.configuration.schemas.table.TableConfiguration;
+import org.apache.ignite.configuration.schemas.table.TableView;
+import org.apache.ignite.configuration.schemas.table.UnlimitedBudgetConfigurationSchema;
+import org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
+import org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
+import org.apache.ignite.internal.storage.RowId;
+import org.apache.ignite.internal.storage.chm.TestConcurrentHashMapStorageEngine;
+import org.apache.ignite.internal.storage.chm.schema.TestConcurrentHashMapDataStorageConfigurationSchema;
+import org.apache.ignite.internal.storage.index.impl.BinaryTupleRowSerializer;
+import org.apache.ignite.schema.SchemaBuilders;
+import org.apache.ignite.schema.definition.ColumnDefinition;
+import org.apache.ignite.schema.definition.ColumnType;
+import org.apache.ignite.schema.definition.TableDefinition;
+import org.apache.ignite.schema.definition.index.HashIndexDefinition;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Base class for Hash Index storage tests.
+ */
+@ExtendWith(ConfigurationExtension.class)
+public abstract class AbstractHashIndexStorageTest {
+ private static final String INT_COLUMN_NAME = "intVal";
+
+ private static final String STR_COLUMN_NAME = "strVal";
+
+ private TableConfiguration tableCfg;
+
+ private HashIndexStorage indexStorage;
+
+ private BinaryTupleRowSerializer serializer;
+
+ @BeforeEach
+ void setUp(@InjectConfiguration(
+ polymorphicExtensions = {
+ HashIndexConfigurationSchema.class,
+ TestConcurrentHashMapDataStorageConfigurationSchema.class,
+ ConstantValueDefaultConfigurationSchema.class,
+ FunctionCallDefaultConfigurationSchema.class,
+ NullValueDefaultConfigurationSchema.class,
+ UnlimitedBudgetConfigurationSchema.class
+ },
+ // This value only required for configuration validity, it's not used otherwise.
+ value = "mock.dataStorage.name = " + TestConcurrentHashMapStorageEngine.ENGINE_NAME
+ ) TableConfiguration tableCfg) {
+ createTestTable(tableCfg);
+
+ this.tableCfg = tableCfg;
+ this.indexStorage = createIndexStorage();
+ this.serializer = new BinaryTupleRowSerializer(indexStorage.indexDescriptor());
+ }
+
+ /**
+ * Configures a test table with some indexed columns.
+ */
+ private static void createTestTable(TableConfiguration tableCfg) {
+ ColumnDefinition pkColumn = column("pk", ColumnType.INT32).asNullable(false).build();
+
+ ColumnDefinition[] allColumns = {
+ pkColumn,
+ column(INT_COLUMN_NAME, ColumnType.INT32).asNullable(true).build(),
+ column(STR_COLUMN_NAME, ColumnType.string()).asNullable(true).build()
+ };
+
+ TableDefinition tableDefinition = tableBuilder("test", "foo")
+ .columns(allColumns)
+ .withPrimaryKey(pkColumn.name())
+ .build();
+
+ CompletableFuture<Void> createTableFuture = tableCfg.change(cfg -> convert(tableDefinition, cfg));
+
+ assertThat(createTableFuture, willCompleteSuccessfully());
+ }
+
+ /**
+ * Creates a storage instance for testing.
+ */
+ protected abstract HashIndexStorage createIndexStorage(String name, TableView tableCfg);
+
+ /**
+ * Configures and creates a storage instance for testing.
+ */
+ private HashIndexStorage createIndexStorage() {
+ HashIndexDefinition indexDefinition = SchemaBuilders.hashIndex("hashIndex")
+ .withColumns(INT_COLUMN_NAME, STR_COLUMN_NAME)
+ .build();
+
+ CompletableFuture<Void> createIndexFuture = tableCfg.change(cfg ->
+ cfg.changeIndices(idxList ->
+ idxList.create(indexDefinition.name(), idx -> convert(indexDefinition, idx))));
+
+ assertThat(createIndexFuture, willBe(nullValue(Void.class)));
+
+ return createIndexStorage(indexDefinition.name(), tableCfg.value());
+ }
+
+ /**
+ * Tests the {@link HashIndexStorage#get} method.
+ */
+ @Test
+ void testGet() {
+ // First two rows have the same index key, but different row IDs
+ IndexRow row1 = serializer.serializeRow(new Object[]{ 1, "foo" }, new RowId(0));
+ IndexRow row2 = serializer.serializeRow(new Object[]{ 1, "foo" }, new RowId(0));
+ IndexRow row3 = serializer.serializeRow(new Object[]{ 2, "bar" }, new RowId(0));
+
+ assertThat(indexStorage.get(row1.indexColumns()), is(empty()));
+ assertThat(indexStorage.get(row2.indexColumns()), is(empty()));
+ assertThat(indexStorage.get(row3.indexColumns()), is(empty()));
+
+ indexStorage.put(row1);
+ indexStorage.put(row2);
+ indexStorage.put(row3);
+
+ assertThat(indexStorage.get(row1.indexColumns()), containsInAnyOrder(row1.rowId(), row2.rowId()));
+ assertThat(indexStorage.get(row2.indexColumns()), containsInAnyOrder(row1.rowId(), row2.rowId()));
+ assertThat(indexStorage.get(row3.indexColumns()), contains(row3.rowId()));
+ }
+
+ /**
+ * Tests that {@link HashIndexStorage#put} does not create row ID duplicates.
+ */
+ @Test
+ void testPutIdempotence() {
+ IndexRow row = serializer.serializeRow(new Object[]{ 1, "foo" }, new RowId(0));
+
+ indexStorage.put(row);
+ indexStorage.put(row);
+
+ assertThat(indexStorage.get(row.indexColumns()), contains(row.rowId()));
+ }
+
+ /**
+ * Tests the {@link HashIndexStorage#remove} method.
+ */
+ @Test
+ void testRemove() {
+ IndexRow row1 = serializer.serializeRow(new Object[]{ 1, "foo" }, new RowId(0));
+ IndexRow row2 = serializer.serializeRow(new Object[]{ 1, "foo" }, new RowId(0));
+ IndexRow row3 = serializer.serializeRow(new Object[]{ 2, "bar" }, new RowId(0));
+
+ indexStorage.put(row1);
+ indexStorage.put(row2);
+ indexStorage.put(row3);
+
+ assertThat(indexStorage.get(row1.indexColumns()), containsInAnyOrder(row1.rowId(), row2.rowId()));
+ assertThat(indexStorage.get(row2.indexColumns()), containsInAnyOrder(row1.rowId(), row2.rowId()));
+ assertThat(indexStorage.get(row3.indexColumns()), contains(row3.rowId()));
+
+ indexStorage.remove(row1);
+
+ assertThat(indexStorage.get(row1.indexColumns()), contains(row2.rowId()));
+ assertThat(indexStorage.get(row2.indexColumns()), contains(row2.rowId()));
+ assertThat(indexStorage.get(row3.indexColumns()), contains(row3.rowId()));
+
+ indexStorage.remove(row2);
+
+ assertThat(indexStorage.get(row1.indexColumns()), is(empty()));
+ assertThat(indexStorage.get(row2.indexColumns()), is(empty()));
+ assertThat(indexStorage.get(row3.indexColumns()), contains(row3.rowId()));
+
+ indexStorage.remove(row3);
+
+ assertThat(indexStorage.get(row1.indexColumns()), is(empty()));
+ assertThat(indexStorage.get(row2.indexColumns()), is(empty()));
+ assertThat(indexStorage.get(row3.indexColumns()), is(empty()));
+ }
+
+ /**
+ * Tests that {@link HashIndexStorage#remove} works normally when removing a non-existent row.
+ */
+ @Test
+ void testRemoveIdempotence() {
+ IndexRow row = serializer.serializeRow(new Object[]{ 1, "foo" }, new RowId(0));
+
+ assertDoesNotThrow(() -> indexStorage.remove(row));
+
+ indexStorage.put(row);
+
+ indexStorage.remove(row);
+
+ assertThat(indexStorage.get(row.indexColumns()), is(empty()));
+
+ assertDoesNotThrow(() -> indexStorage.remove(row));
+ }
+}
diff --git a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/AbstractSortedIndexStorageTest.java b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/AbstractSortedIndexStorageTest.java
index fb56151651..63d89424c9 100644
--- a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/AbstractSortedIndexStorageTest.java
+++ b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/AbstractSortedIndexStorageTest.java
@@ -67,6 +67,7 @@ import org.apache.ignite.internal.storage.RowId;
import org.apache.ignite.internal.storage.chm.TestConcurrentHashMapStorageEngine;
import org.apache.ignite.internal.storage.chm.schema.TestConcurrentHashMapDataStorageConfigurationSchema;
import org.apache.ignite.internal.storage.index.SortedIndexDescriptor.ColumnDescriptor;
+import org.apache.ignite.internal.storage.index.impl.BinaryTupleRowSerializer;
import org.apache.ignite.internal.storage.index.impl.TestIndexRow;
import org.apache.ignite.internal.testframework.VariableSource;
import org.apache.ignite.internal.util.Cursor;
@@ -87,7 +88,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
/**
- * Base test for MV index storages.
+ * Base class for Sorted Index storage tests.
*/
@ExtendWith(ConfigurationExtension.class)
public abstract class AbstractSortedIndexStorageTest {
@@ -96,7 +97,7 @@ public abstract class AbstractSortedIndexStorageTest {
/** Definitions of all supported column types. */
public static final List<ColumnDefinition> ALL_TYPES_COLUMN_DEFINITIONS = allTypesColumnDefinitions();
- protected TableConfiguration tableCfg;
+ private TableConfiguration tableCfg;
private Random random;
@@ -170,7 +171,7 @@ public abstract class AbstractSortedIndexStorageTest {
}
/**
- * Creates a storage instanc efor testing.
+ * Creates a storage instance for testing.
*/
protected abstract SortedIndexStorage createIndexStorage(String name, TableView tableCfg);
@@ -222,9 +223,11 @@ public abstract class AbstractSortedIndexStorageTest {
.map(type -> SchemaTestUtils.generateRandomValue(random, type))
.toArray();
- IndexRow row = indexStorage.indexRowSerializer().createIndexRow(columns, new RowId(0));
+ var serializer = new BinaryTupleRowSerializer(indexStorage.indexDescriptor());
- Object[] actual = indexStorage.indexRowDeserializer().deserializeColumns(row);
+ IndexRow row = serializer.serializeRow(columns, new RowId(0));
+
+ Object[] actual = serializer.deserializeColumns(row);
assertThat(actual, is(equalTo(columns)));
}
@@ -265,7 +268,9 @@ public abstract class AbstractSortedIndexStorageTest {
var columnValues = new Object[] { "foo", 1 };
var rowId = new RowId(0);
- IndexRow row = index.indexRowSerializer().createIndexRow(columnValues, rowId);
+ var serializer = new BinaryTupleRowSerializer(index.indexDescriptor());
+
+ IndexRow row = serializer.serializeRow(columnValues, rowId);
index.put(row);
index.put(row);
@@ -298,9 +303,11 @@ public abstract class AbstractSortedIndexStorageTest {
var rowId2 = new RowId(0);
var rowId3 = new RowId(0);
- IndexRow row1 = index.indexRowSerializer().createIndexRow(columnValues1, rowId1);
- IndexRow row2 = index.indexRowSerializer().createIndexRow(columnValues1, rowId2);
- IndexRow row3 = index.indexRowSerializer().createIndexRow(columnValues2, rowId3);
+ var serializer = new BinaryTupleRowSerializer(index.indexDescriptor());
+
+ IndexRow row1 = serializer.serializeRow(columnValues1, rowId1);
+ IndexRow row2 = serializer.serializeRow(columnValues1, rowId2);
+ IndexRow row3 = serializer.serializeRow(columnValues2, rowId3);
index.put(row1);
index.put(row2);
@@ -334,9 +341,11 @@ public abstract class AbstractSortedIndexStorageTest {
var rowId2 = new RowId(0);
var rowId3 = new RowId(0);
- IndexRow row1 = index.indexRowSerializer().createIndexRow(columnValues1, rowId1);
- IndexRow row2 = index.indexRowSerializer().createIndexRow(columnValues1, rowId2);
- IndexRow row3 = index.indexRowSerializer().createIndexRow(columnValues2, rowId3);
+ var serializer = new BinaryTupleRowSerializer(index.indexDescriptor());
+
+ IndexRow row1 = serializer.serializeRow(columnValues1, rowId1);
+ IndexRow row2 = serializer.serializeRow(columnValues1, rowId2);
+ IndexRow row3 = serializer.serializeRow(columnValues2, rowId3);
index.put(row1);
index.put(row2);
@@ -442,12 +451,12 @@ public abstract class AbstractSortedIndexStorageTest {
Object[] val8020 = { "20", 80 };
for (SortedIndexStorage index : Arrays.asList(index1, index2)) {
- IndexRowSerializer serializer = index.indexRowSerializer();
+ var serializer = new BinaryTupleRowSerializer(index.indexDescriptor());
- index.put(serializer.createIndexRow(val9010, new RowId(0)));
- index.put(serializer.createIndexRow(val8010, new RowId(0)));
- index.put(serializer.createIndexRow(val9020, new RowId(0)));
- index.put(serializer.createIndexRow(val8020, new RowId(0)));
+ index.put(serializer.serializeRow(val9010, new RowId(0)));
+ index.put(serializer.serializeRow(val8010, new RowId(0)));
+ index.put(serializer.serializeRow(val9020, new RowId(0)));
+ index.put(serializer.serializeRow(val8020, new RowId(0)));
}
// Test without bounds.
@@ -591,9 +600,11 @@ public abstract class AbstractSortedIndexStorageTest {
Object[] nullArray = new Object[storage.indexDescriptor().indexColumns().size()];
- IndexRow nullRow = storage.indexRowSerializer().createIndexRow(nullArray, new RowId(0));
+ var serializer = new BinaryTupleRowSerializer(storage.indexDescriptor());
+
+ IndexRow nullRow = serializer.serializeRow(nullArray, new RowId(0));
- TestIndexRow entry2 = new TestIndexRow(storage, nullRow, nullArray);
+ var entry2 = new TestIndexRow(storage, serializer, nullRow, nullArray);
storage.put(entry1);
storage.put(entry2);
@@ -688,7 +699,9 @@ public abstract class AbstractSortedIndexStorageTest {
}
private static BinaryTuple prefix(SortedIndexStorage index, Object... vals) {
- return index.indexRowSerializer().createIndexRowPrefix(vals);
+ var serializer = new BinaryTupleRowSerializer(index.indexDescriptor());
+
+ return serializer.serializeRowPrefix(vals);
}
private static List<Object[]> scan(
@@ -697,11 +710,11 @@ public abstract class AbstractSortedIndexStorageTest {
@Nullable BinaryTuple upperBound,
@MagicConstant(flagsFromClass = SortedIndexStorage.class) int flags
) throws Exception {
- IndexRowDeserializer deserializer = index.indexRowDeserializer();
+ var serializer = new BinaryTupleRowSerializer(index.indexDescriptor());
try (Cursor<IndexRow> cursor = index.scan(lowerBound, upperBound, flags)) {
return cursor.stream()
- .map(deserializer::deserializeColumns)
+ .map(serializer::deserializeColumns)
.collect(toUnmodifiableList());
}
}
diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/IndexRowDeserializer.java b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/TestHashIndexStorageTest.java
similarity index 65%
rename from modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/IndexRowDeserializer.java
rename to modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/TestHashIndexStorageTest.java
index 57cf83f716..929ea55f63 100644
--- a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/IndexRowDeserializer.java
+++ b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/TestHashIndexStorageTest.java
@@ -17,15 +17,15 @@
package org.apache.ignite.internal.storage.index;
+import org.apache.ignite.configuration.schemas.table.TableView;
+import org.apache.ignite.internal.storage.index.impl.TestHashIndexStorage;
+
/**
- * Class for extracting indexed column values from an {@link IndexRow}.
+ * Class for testing the {@link HashIndexStorage}.
*/
-public interface IndexRowDeserializer {
- /**
- * De-serializes column values that were used to create the index.
- *
- * @param indexRow Index row.
- * @return Values of the indexed columns.
- */
- Object[] deserializeColumns(IndexRow indexRow);
+public class TestHashIndexStorageTest extends AbstractHashIndexStorageTest {
+ @Override
+ protected HashIndexStorage createIndexStorage(String name, TableView tableCfg) {
+ return new TestHashIndexStorage(new HashIndexDescriptor(name, tableCfg));
+ }
}
diff --git a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/TestSortedIndexStorageTest.java b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/TestSortedIndexStorageTest.java
index 74b6415351..bf9e191855 100644
--- a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/TestSortedIndexStorageTest.java
+++ b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/TestSortedIndexStorageTest.java
@@ -21,7 +21,7 @@ import org.apache.ignite.configuration.schemas.table.TableView;
import org.apache.ignite.internal.storage.index.impl.TestSortedIndexStorage;
/**
- * MV sorted index storage test implementation for {@link TestSortedIndexStorage} class.
+ * Class for testing the {@link TestSortedIndexStorage}.
*/
public class TestSortedIndexStorageTest extends AbstractSortedIndexStorageTest {
@Override
diff --git a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/BinaryTupleRowDeserializer.java b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/BinaryTupleRowDeserializer.java
deleted file mode 100644
index 3e86c9abbe..0000000000
--- a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/BinaryTupleRowDeserializer.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.ignite.internal.storage.index.impl;
-
-import org.apache.ignite.internal.schema.BinaryTuple;
-import org.apache.ignite.internal.schema.NativeTypeSpec;
-import org.apache.ignite.internal.storage.index.IndexRow;
-import org.apache.ignite.internal.storage.index.IndexRowDeserializer;
-import org.apache.ignite.internal.storage.index.SortedIndexDescriptor;
-
-/**
- * {@link IndexRowDeserializer} implementation that uses {@link BinaryTuple} infrastructure for deserialization purposes.
- */
-class BinaryTupleRowDeserializer implements IndexRowDeserializer {
- private final SortedIndexDescriptor descriptor;
-
- BinaryTupleRowDeserializer(SortedIndexDescriptor descriptor) {
- this.descriptor = descriptor;
- }
-
- @Override
- public Object[] deserializeColumns(IndexRow indexRow) {
- BinaryTuple tuple = indexRow.indexColumns();
-
- assert tuple.count() == descriptor.indexColumns().size();
-
- var result = new Object[descriptor.indexColumns().size()];
-
- for (int i = 0; i < result.length; i++) {
- NativeTypeSpec typeSpec = descriptor.indexColumns().get(i).type().spec();
-
- result[i] = typeSpec.objectValue(tuple, i);
- }
-
- return result;
- }
-}
diff --git a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/BinaryTupleRowSerializer.java b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/BinaryTupleRowSerializer.java
index 96b382f6ae..39d62f7fac 100644
--- a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/BinaryTupleRowSerializer.java
+++ b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/BinaryTupleRowSerializer.java
@@ -17,51 +17,85 @@
package org.apache.ignite.internal.storage.index.impl;
+import static java.util.stream.Collectors.toUnmodifiableList;
+
+import java.util.List;
import org.apache.ignite.internal.schema.BinaryTuple;
import org.apache.ignite.internal.schema.BinaryTupleBuilder;
import org.apache.ignite.internal.schema.BinaryTupleSchema;
import org.apache.ignite.internal.schema.BinaryTupleSchema.Element;
+import org.apache.ignite.internal.schema.NativeType;
+import org.apache.ignite.internal.schema.NativeTypeSpec;
import org.apache.ignite.internal.storage.RowId;
+import org.apache.ignite.internal.storage.index.HashIndexDescriptor;
import org.apache.ignite.internal.storage.index.IndexRow;
-import org.apache.ignite.internal.storage.index.IndexRowSerializer;
import org.apache.ignite.internal.storage.index.SortedIndexDescriptor;
/**
- * {@link IndexRowSerializer} implementation that uses {@link BinaryTuple} as the index keys serialization mechanism.
+ * Class for converting an array of objects into a {@link BinaryTuple} and vice-versa using a given index schema.
*/
-class BinaryTupleRowSerializer implements IndexRowSerializer {
- private final SortedIndexDescriptor descriptor;
+public class BinaryTupleRowSerializer {
+ private static class ColumnDescriptor {
+ final NativeType type;
+
+ final boolean nullable;
+
+ ColumnDescriptor(NativeType type, boolean nullable) {
+ this.type = type;
+ this.nullable = nullable;
+ }
+ }
+
+ private final List<ColumnDescriptor> schema;
+
+ /**
+ * Creates a new instance for a Sorted Index.
+ */
+ public BinaryTupleRowSerializer(SortedIndexDescriptor descriptor) {
+ this.schema = descriptor.indexColumns().stream()
+ .map(colDesc -> new ColumnDescriptor(colDesc.type(), colDesc.nullable()))
+ .collect(toUnmodifiableList());
+ }
- BinaryTupleRowSerializer(SortedIndexDescriptor descriptor) {
- this.descriptor = descriptor;
+ /**
+ * Creates a new instance for a Hash Index.
+ */
+ public BinaryTupleRowSerializer(HashIndexDescriptor descriptor) {
+ this.schema = descriptor.indexColumns().stream()
+ .map(colDesc -> new ColumnDescriptor(colDesc.type(), colDesc.nullable()))
+ .collect(toUnmodifiableList());
}
- @Override
- public IndexRow createIndexRow(Object[] columnValues, RowId rowId) {
- if (columnValues.length != descriptor.indexColumns().size()) {
+ /**
+ * Creates an {@link IndexRow} from the given index columns and a Row ID.
+ */
+ public IndexRow serializeRow(Object[] columnValues, RowId rowId) {
+ if (columnValues.length != schema.size()) {
throw new IllegalArgumentException(String.format(
"Incorrect number of column values passed. Expected %d, got %d",
- descriptor.indexColumns().size(),
+ schema.size(),
columnValues.length
));
}
- return new IndexRowImpl(createIndexRowPrefix(columnValues), rowId);
+ return new IndexRowImpl(serializeRowPrefix(columnValues), rowId);
}
- @Override
- public BinaryTuple createIndexRowPrefix(Object[] prefixColumnValues) {
- if (prefixColumnValues.length > descriptor.indexColumns().size()) {
+ /**
+ * Creates a prefix of an {@link IndexRow} using the provided columns.
+ */
+ public BinaryTuple serializeRowPrefix(Object[] prefixColumnValues) {
+ if (prefixColumnValues.length > schema.size()) {
throw new IllegalArgumentException(String.format(
"Incorrect number of column values passed. Expected not more than %d, got %d",
- descriptor.indexColumns().size(),
+ schema.size(),
prefixColumnValues.length
));
}
- Element[] prefixElements = descriptor.indexColumns().stream()
+ Element[] prefixElements = schema.stream()
.limit(prefixColumnValues.length)
- .map(columnDescriptor -> new Element(columnDescriptor.type(), columnDescriptor.nullable()))
+ .map(columnDescriptor -> new Element(columnDescriptor.type, columnDescriptor.nullable))
.toArray(Element[]::new);
BinaryTupleSchema prefixSchema = BinaryTupleSchema.create(prefixElements);
@@ -74,4 +108,23 @@ class BinaryTupleRowSerializer implements IndexRowSerializer {
return new BinaryTuple(prefixSchema, builder.build());
}
+
+ /**
+ * Converts a byte representation of index columns back into Java objects.
+ */
+ public Object[] deserializeColumns(IndexRow indexRow) {
+ BinaryTuple tuple = indexRow.indexColumns();
+
+ assert tuple.count() == schema.size();
+
+ var result = new Object[schema.size()];
+
+ for (int i = 0; i < result.length; i++) {
+ NativeTypeSpec typeSpec = schema.get(i).type.spec();
+
+ result[i] = typeSpec.objectValue(tuple, i);
+ }
+
+ return result;
+ }
}
diff --git a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/TestHashIndexStorage.java b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/TestHashIndexStorage.java
new file mode 100644
index 0000000000..8eefb36732
--- /dev/null
+++ b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/TestHashIndexStorage.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.storage.index.impl;
+
+import static org.apache.ignite.internal.util.IgniteUtils.capacity;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.apache.ignite.internal.schema.BinaryTuple;
+import org.apache.ignite.internal.storage.RowId;
+import org.apache.ignite.internal.storage.index.HashIndexDescriptor;
+import org.apache.ignite.internal.storage.index.HashIndexStorage;
+import org.apache.ignite.internal.storage.index.IndexRow;
+
+/**
+ * Test-only implementation of a {@link HashIndexStorage}.
+ */
+public class TestHashIndexStorage implements HashIndexStorage {
+ private final ConcurrentMap<ByteBuffer, Set<RowId>> index = new ConcurrentHashMap<>();
+
+ private final HashIndexDescriptor descriptor;
+
+ /**
+ * Constructor.
+ */
+ public TestHashIndexStorage(HashIndexDescriptor descriptor) {
+ this.descriptor = descriptor;
+ }
+
+ @Override
+ public HashIndexDescriptor indexDescriptor() {
+ return descriptor;
+ }
+
+ @Override
+ public Collection<RowId> get(BinaryTuple key) {
+ return index.getOrDefault(key.byteBuffer(), Set.of());
+ }
+
+ @Override
+ public void put(IndexRow row) {
+ index.compute(row.indexColumns().byteBuffer(), (k, v) -> {
+ if (v == null) {
+ return Set.of(row.rowId());
+ } else if (v.contains(row.rowId())) {
+ return v;
+ } else {
+ var result = new HashSet<RowId>(capacity(v.size() + 1));
+
+ result.addAll(v);
+ result.add(row.rowId());
+
+ return result;
+ }
+ });
+ }
+
+ @Override
+ public void remove(IndexRow row) {
+ index.computeIfPresent(row.indexColumns().byteBuffer(), (k, v) -> {
+ if (v.contains(row.rowId())) {
+ if (v.size() == 1) {
+ return null;
+ } else {
+ var result = new HashSet<>(v);
+
+ result.remove(row.rowId());
+
+ return result;
+ }
+ } else {
+ return v;
+ }
+ });
+ }
+}
diff --git a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/TestIndexRow.java b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/TestIndexRow.java
index a8e1d21e27..a906c546b0 100644
--- a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/TestIndexRow.java
+++ b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/TestIndexRow.java
@@ -48,9 +48,12 @@ public class TestIndexRow implements IndexRow, Comparable<TestIndexRow> {
private final SortedIndexStorage indexStorage;
+ private final BinaryTupleRowSerializer serializer;
+
/** Constructor. */
- public TestIndexRow(SortedIndexStorage storage, IndexRow row, Object[] columns) {
+ public TestIndexRow(SortedIndexStorage storage, BinaryTupleRowSerializer serializer, IndexRow row, Object[] columns) {
this.indexStorage = storage;
+ this.serializer = serializer;
this.row = row;
this.columns = columns;
}
@@ -68,16 +71,18 @@ public class TestIndexRow implements IndexRow, Comparable<TestIndexRow> {
var rowId = new RowId(0);
- IndexRow row = indexStorage.indexRowSerializer().createIndexRow(columns, rowId);
+ var serializer = new BinaryTupleRowSerializer(indexStorage.indexDescriptor());
+
+ IndexRow row = serializer.serializeRow(columns, rowId);
- return new TestIndexRow(indexStorage, row, columns);
+ return new TestIndexRow(indexStorage, serializer, row, columns);
}
/**
* Creates an Index Key prefix of the given length.
*/
public BinaryTuple prefix(int length) {
- return indexStorage.indexRowSerializer().createIndexRowPrefix(Arrays.copyOf(columns, length));
+ return serializer.serializeRowPrefix(Arrays.copyOf(columns, length));
}
@Override
diff --git a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/TestSortedIndexStorage.java b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/TestSortedIndexStorage.java
index 16881717e4..56d8949c65 100644
--- a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/TestSortedIndexStorage.java
+++ b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/impl/TestSortedIndexStorage.java
@@ -29,8 +29,6 @@ import java.util.function.ToIntFunction;
import org.apache.ignite.internal.schema.BinaryTuple;
import org.apache.ignite.internal.storage.RowId;
import org.apache.ignite.internal.storage.index.IndexRow;
-import org.apache.ignite.internal.storage.index.IndexRowDeserializer;
-import org.apache.ignite.internal.storage.index.IndexRowSerializer;
import org.apache.ignite.internal.storage.index.SortedIndexDescriptor;
import org.apache.ignite.internal.storage.index.SortedIndexStorage;
import org.apache.ignite.internal.util.Cursor;
@@ -57,21 +55,13 @@ public class TestSortedIndexStorage implements SortedIndexStorage {
return descriptor;
}
- @Override
- public IndexRowSerializer indexRowSerializer() {
- return new BinaryTupleRowSerializer(descriptor);
- }
-
- @Override
- public IndexRowDeserializer indexRowDeserializer() {
- return new BinaryTupleRowDeserializer(descriptor);
- }
-
@Override
public void put(IndexRow row) {
index.compute(row.indexColumns(), (k, v) -> {
if (v == null) {
return Set.of(row.rowId());
+ } else if (v.contains(row.rowId())) {
+ return v;
} else {
var result = new HashSet<RowId>(capacity(v.size() + 1));
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/AbstractPageMemoryTableStorage.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/AbstractPageMemoryTableStorage.java
index e257ead814..fa9bc4d1f6 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/AbstractPageMemoryTableStorage.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/AbstractPageMemoryTableStorage.java
@@ -29,6 +29,7 @@ import org.apache.ignite.internal.pagememory.PageMemory;
import org.apache.ignite.internal.storage.MvPartitionStorage;
import org.apache.ignite.internal.storage.StorageException;
import org.apache.ignite.internal.storage.engine.MvTableStorage;
+import org.apache.ignite.internal.storage.index.HashIndexStorage;
import org.apache.ignite.internal.storage.index.SortedIndexStorage;
import org.apache.ignite.internal.storage.pagememory.mv.AbstractPageMemoryMvPartitionStorage;
import org.apache.ignite.internal.tostring.S;
@@ -141,13 +142,12 @@ public abstract class AbstractPageMemoryTableStorage implements MvTableStorage {
/** {@inheritDoc} */
@Override
- public void createIndex(String indexName) {
+ public SortedIndexStorage getOrCreateSortedIndex(int partitionId, String indexName) {
throw new UnsupportedOperationException("Not implemented yet");
}
- /** {@inheritDoc} */
@Override
- public @Nullable SortedIndexStorage getSortedIndex(int partitionId, String indexName) {
+ public HashIndexStorage getOrCreateHashIndex(int partitionId, String indexName) {
throw new UnsupportedOperationException("Not implemented yet");
}
diff --git a/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/RocksDbTableStorage.java b/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/RocksDbTableStorage.java
index ca6207feb0..d0b03e1b1a 100644
--- a/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/RocksDbTableStorage.java
+++ b/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/RocksDbTableStorage.java
@@ -45,6 +45,7 @@ import org.apache.ignite.internal.rocksdb.ColumnFamily;
import org.apache.ignite.internal.storage.MvPartitionStorage;
import org.apache.ignite.internal.storage.StorageException;
import org.apache.ignite.internal.storage.engine.MvTableStorage;
+import org.apache.ignite.internal.storage.index.HashIndexStorage;
import org.apache.ignite.internal.storage.index.SortedIndexStorage;
import org.apache.ignite.internal.tostring.S;
import org.apache.ignite.internal.util.IgniteSpinBusyLock;
@@ -416,13 +417,12 @@ class RocksDbTableStorage implements MvTableStorage {
/** {@inheritDoc} */
@Override
- public void createIndex(String indexName) {
+ public SortedIndexStorage getOrCreateSortedIndex(int partitionId, String indexName) {
throw new UnsupportedOperationException("Not implemented yet");
}
- /** {@inheritDoc} */
@Override
- public @Nullable SortedIndexStorage getSortedIndex(int partitionId, String indexName) {
+ public HashIndexStorage getOrCreateHashIndex(int partitionId, String indexName) {
throw new UnsupportedOperationException("Not implemented yet");
}