This is an automated email from the ASF dual-hosted git repository. amashenkov pushed a commit to branch ignite-19775 in repository https://gitbox.apache.org/repos/asf/ignite-3.git
commit c3758a2134bf86c852273c3cbdf9a2db079dcf62 Author: amashenkov <[email protected]> AuthorDate: Wed Jun 28 01:05:00 2023 +0300 Add index to TestBuilders. --- .../internal/sql/engine/schema/IgniteIndex.java | 15 + .../sql/engine/framework/TestBuilders.java | 327 ++++++++++++++++++--- .../internal/sql/engine/framework/TestIndex.java | 101 +++++++ .../internal/sql/engine/framework/TestNode.java | 11 + 4 files changed, 416 insertions(+), 38 deletions(-) diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteIndex.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteIndex.java index 459baaec3e..47b736e442 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteIndex.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteIndex.java @@ -30,6 +30,7 @@ import org.apache.ignite.internal.index.SortedIndex; import org.apache.ignite.internal.index.SortedIndexDescriptor; import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; /** * Schema object representing an Index. @@ -88,6 +89,20 @@ public class IgniteIndex { this.type = index instanceof SortedIndex ? Type.SORTED : Type.HASH; } + /** + * Constructs the Index object. + */ + @TestOnly + public IgniteIndex(Type type, List<String> columns, @Nullable List<Collation> collations) { + assert type == Type.HASH ^ collations == null; + + this.columns = columns; + this.collations = collations; + this.type = type; + + index = null; + } + /** Returns a list of names of indexed columns. */ public List<String> columns() { return columns; diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java index 70ce3549c6..4462a6e371 100644 --- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java +++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java @@ -41,6 +41,8 @@ import org.apache.ignite.internal.sql.engine.metadata.FragmentDescription; import org.apache.ignite.internal.sql.engine.schema.ColumnDescriptor; import org.apache.ignite.internal.sql.engine.schema.ColumnDescriptorImpl; import org.apache.ignite.internal.sql.engine.schema.DefaultValueStrategy; +import org.apache.ignite.internal.sql.engine.schema.IgniteIndex; +import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Collation; import org.apache.ignite.internal.sql.engine.schema.IgniteSchema; import org.apache.ignite.internal.sql.engine.schema.TableDescriptorImpl; import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution; @@ -85,8 +87,8 @@ public class TestBuilders { /** * Sets desired names for the cluster nodes. * - * @param firstNodeName A name of the first node. There is no difference in what node should be first. This parameter was introduced - * to force user to provide at least one node name. + * @param firstNodeName A name of the first node. There is no difference in what node should be first. This parameter was + * introduced to force user to provide at least one node name. * @param otherNodeNames An array of rest of the names to create cluster from. * @return {@code this} for chaining. */ @@ -99,10 +101,14 @@ public class TestBuilders { */ ClusterTableBuilder addTable(); + ClusterSortedIndexBuilder addSortedIndex(); + + ClusterHashIndexBuilder addHashIndex(); + /** * When specified the given factory is used to create instances of - * {@link ClusterTableBuilder#defaultDataProvider(DataProvider) default data providers} for tables - * that have no {@link ClusterTableBuilder#defaultDataProvider(DataProvider) default data provider} set. + * {@link ClusterTableBuilder#defaultDataProvider(DataProvider) default data providers} for tables that have no + * {@link ClusterTableBuilder#defaultDataProvider(DataProvider) default data provider} set. * * <p>Note: when a table has default data provider this method has no effect. * @@ -132,19 +138,80 @@ public class TestBuilders { public TestTable build(); } + /** + * A builder to create a test object that representing sorted index. + * + * @see TestIndex + */ + public interface SortedIndexBuilder extends SortedIndexBuilderBase<SortedIndexBuilder> { + /** + * Builds a index. + * + * @return Created index object. + */ + IgniteIndex build(); + } + + /** + * A builder to create a test object that representing hash index. + * + * @see TestIndex + */ + public interface HashIndexBuilder extends HashIndexBuilderBase<HashIndexBuilder> { + /** + * Builds a index. + * + * @return Created index object. + */ + IgniteIndex build(); + } + /** * A builder to create a test table as nested object of the cluster. * * @see TestTable * @see TestCluster */ - public interface ClusterTableBuilder extends TableBuilderBase<ClusterTableBuilder>, NestedBuilder<ClusterBuilder> { + public interface ClusterTableBuilder extends TableBuilderBase<ClusterTableBuilder>, + DataSourceBuilder<ClusterTableBuilder>, + NestedBuilder<ClusterBuilder> { + } + + /** + * A builder to create a test object, which represents sorted index, as nested object of the cluster. + * + * @see TestIndex + * @see TestCluster + */ + public interface ClusterSortedIndexBuilder extends SortedIndexBuilderBase<ClusterSortedIndexBuilder>, + DataSourceBuilder<ClusterSortedIndexBuilder>, + NestedBuilder<ClusterBuilder> { + } + + /** + * A builder to create a test object, which represents hash index, as nested object of the cluster. + * + * @see TestIndex + * @see TestCluster + */ + public interface ClusterHashIndexBuilder extends HashIndexBuilderBase<ClusterHashIndexBuilder>, + DataSourceBuilder<ClusterHashIndexBuilder>, + NestedBuilder<ClusterBuilder> { + } + + /** + * A builder interface to enrich a builder object with data-source related fields. + */ + public interface DataSourceBuilder<ChildT> { /** * Adds a default data provider, which will be used for those nodes for which no specific provider is specified. * - * <p>Note: this method will force all nodes in the cluster to have a data provider for the given table. + * <p>Note: this method will force all nodes in the cluster to have a data provider for the given object. */ - ClusterTableBuilder defaultDataProvider(DataProvider<?> dataProvider); + ChildT defaultDataProvider(DataProvider<?> dataProvider); + + /** Adds a data provider for the given node to the data source object. */ + ChildT addDataProvider(String targetNode, DataProvider<?> dataProvider); } /** @@ -231,6 +298,7 @@ public class TestBuilders { private static class ClusterBuilderImpl implements ClusterBuilder { private final List<ClusterTableBuilderImpl> tableBuilders = new ArrayList<>(); + private final List<AbstractIndexBuilderImpl> indexBuilders = new ArrayList<>(); private DataProviderFactory dataProviderFactory; private List<String> nodeNames; @@ -251,6 +319,19 @@ public class TestBuilders { return new ClusterTableBuilderImpl(this); } + /** {@inheritDoc} */ + @Override + public ClusterSortedIndexBuilder addSortedIndex() { + return new ClusterSortedIndexBuilderImpl(this); + } + + /** {@inheritDoc} */ + @Override + public ClusterHashIndexBuilder addHashIndex() { + return new ClusterHashIndexBuilderImpl(this); + } + + /** {@inheritDoc} */ @Override public ClusterBuilder defaultDataProviderFactory(DataProviderFactory dataProviderFactory) { @@ -264,7 +345,8 @@ public class TestBuilders { var clusterService = new ClusterServiceFactory(nodeNames); for (ClusterTableBuilderImpl tableBuilder : tableBuilders) { - validateTableBuilder(tableBuilder); + validateDataSourceBuilder(tableBuilder); + injectDefaultDataProvidersIfNeeded(tableBuilder); injectDataProvidersIfNeeded(tableBuilder); } @@ -272,7 +354,19 @@ public class TestBuilders { .map(ClusterTableBuilderImpl::build) .collect(Collectors.toMap(TestTable::name, Function.identity())); - var schemaManager = new PredefinedSchemaManager(new IgniteSchema("PUBLIC", tableMap, null, SCHEMA_VERSION)); + for (AbstractIndexBuilderImpl<?> indexBuilder : indexBuilders) { + validateDataSourceBuilder(indexBuilder); + validateIndexBuilder(indexBuilder, tableMap); + injectDataProvidersIfNeeded(indexBuilder); + } + + Map<Integer, IgniteIndex> indexMap = indexBuilders.stream() + .map(AbstractIndexBuilderImpl::build) + .collect(Collectors.toMap(TestIndex::id, Function.identity())); + + indexMap.values().forEach(idx -> ((TestTable) tableMap.get(((TestIndex) idx).tableName())).addIndex(idx)); + + var schemaManager = new PredefinedSchemaManager(new IgniteSchema("PUBLIC", tableMap, indexMap, SCHEMA_VERSION)); Map<String, TestNode> nodes = nodeNames.stream() .map(name -> new TestNode(name, clusterService.forNode(name), schemaManager)) @@ -281,7 +375,7 @@ public class TestBuilders { return new TestCluster(nodes); } - private void validateTableBuilder(ClusterTableBuilderImpl tableBuilder) { + private void validateDataSourceBuilder(AbstractDataSourceBuilderImpl<?> tableBuilder) { Set<String> tableOwners = new HashSet<>(tableBuilder.dataProviders.keySet()); tableOwners.removeAll(nodeNames); @@ -292,21 +386,34 @@ public class TestBuilders { } } - private void injectDataProvidersIfNeeded(ClusterTableBuilderImpl tableBuilder) { - if (tableBuilder.defaultDataProvider == null) { - if (dataProviderFactory != null) { - tableBuilder.defaultDataProvider = dataProviderFactory.createDataProvider(tableBuilder.name, tableBuilder.columns); - } else { - return; - } + private void validateIndexBuilder(AbstractIndexBuilderImpl<?> indexBuilder, Map<String, Table> tableMap) { + String tableName = indexBuilder.tableName; + + TestTable table = (TestTable) tableMap.get(tableName); + + if (table == null) { + throw new AssertionError(format("The index refers to an invalid table " + + "[indexName={}, tableName={}]", indexBuilder.name, tableName)); + } + } + + private void injectDefaultDataProvidersIfNeeded(ClusterTableBuilderImpl tableBuilder) { + if (tableBuilder.defaultDataProvider == null && dataProviderFactory != null) { + tableBuilder.defaultDataProvider = dataProviderFactory.createDataProvider(tableBuilder.name, tableBuilder.columns); + } + } + + private void injectDataProvidersIfNeeded(AbstractDataSourceBuilderImpl<?> builder) { + if (builder.defaultDataProvider == null) { + return; } Set<String> nodesWithoutDataProvider = new HashSet<>(nodeNames); - nodesWithoutDataProvider.removeAll(tableBuilder.dataProviders.keySet()); + nodesWithoutDataProvider.removeAll(builder.dataProviders.keySet()); for (String name : nodesWithoutDataProvider) { - tableBuilder.addDataProvider(name, tableBuilder.defaultDataProvider); + builder.addDataProvider(name, builder.defaultDataProvider); } } } @@ -330,7 +437,7 @@ public class TestBuilders { return new TestTable( new TableDescriptorImpl(columns, distribution), Objects.requireNonNull(name), - dataProviders, + Map.of(), size ); } @@ -380,6 +487,66 @@ public class TestBuilders { } } + private static class ClusterSortedIndexBuilderImpl extends AbstractIndexBuilderImpl<ClusterSortedIndexBuilder> + implements ClusterSortedIndexBuilder { + private final ClusterBuilderImpl parent; + + ClusterSortedIndexBuilderImpl(ClusterBuilderImpl parent) { + this.parent = parent; + } + + /** {@inheritDoc} */ + @Override + ClusterSortedIndexBuilder self() { + return this; + } + + /** {@inheritDoc} */ + @Override + public ClusterBuilder end() { + parent.indexBuilders.add(this); + + return parent; + } + + @Override + TestIndex build() { + assert collations.size() == columns.size(); + + return TestIndex.createSorted(name, tableName, columns, collations, dataProviders); + } + } + + private static class ClusterHashIndexBuilderImpl extends AbstractIndexBuilderImpl<ClusterHashIndexBuilder> + implements ClusterHashIndexBuilder { + private final ClusterBuilderImpl parent; + + ClusterHashIndexBuilderImpl(ClusterBuilderImpl parent) { + this.parent = parent; + } + + /** {@inheritDoc} */ + @Override + ClusterHashIndexBuilder self() { + return this; + } + + /** {@inheritDoc} */ + @Override + public ClusterBuilder end() { + parent.indexBuilders.add(this); + + return parent; + } + + @Override + TestIndex build() { + assert collations == null; + + return TestIndex.createHash(name, tableName, columns, dataProviders); + } + } + /** * A factory that creates {@link DataProvider data providers}. */ @@ -389,24 +556,20 @@ public class TestBuilders { /** * Creates a {@link DataProvider} for the given table. * - * @param tableName a table name. - * @param columns a list of columns. - * - * @return an instance of {@link DataProvider}. + * @param tableName a table name. + * @param columns a list of columns. + * @return an instance of {@link DataProvider}. */ DataProvider<Object[]> createDataProvider(String tableName, List<ColumnDescriptor> columns); } - private abstract static class AbstractTableBuilderImpl<ChildT> implements TableBuilderBase<ChildT> { + private abstract static class AbstractTableBuilderImpl<ChildT> extends AbstractDataSourceBuilderImpl<ChildT> + implements TableBuilderBase<ChildT> { protected final List<ColumnDescriptor> columns = new ArrayList<>(); - protected final Map<String, DataProvider<?>> dataProviders = new HashMap<>(); - protected String name; protected IgniteDistribution distribution; protected int size = 100_000; - protected abstract ChildT self(); - /** {@inheritDoc} */ @Override public ChildT name(String name) { @@ -449,16 +612,69 @@ public class TestBuilders { /** {@inheritDoc} */ @Override - public ChildT addDataProvider(String targetNode, DataProvider<?> dataProvider) { - this.dataProviders.put(targetNode, dataProvider); + public ChildT size(int size) { + this.size = size; return self(); } + } + + private abstract static class AbstractIndexBuilderImpl<ChildT> extends AbstractDataSourceBuilderImpl<ChildT> + implements SortedIndexBuilderBase<ChildT>, HashIndexBuilderBase<ChildT> { + protected final List<String> columns = new ArrayList<>(); + protected List<Collation> collations = new ArrayList<>(); + protected String tableName; /** {@inheritDoc} */ @Override - public ChildT size(int size) { - this.size = size; + public ChildT table(String tableName) { + this.tableName = tableName; + + return self(); + } + + /** {@inheritDoc} */ + @Override + public ChildT addColumn(String columnName) { + columns.add(columnName); + + return self(); + } + + /** {@inheritDoc} */ + @Override + public ChildT addColumn(String columnName, Collation collation) { + columns.add(columnName); + collations.add(collation); + + return self(); + } + + abstract TestIndex build(); + } + + private abstract static class AbstractDataSourceBuilderImpl<ChildT> { + + protected String name; + final Map<String, DataProvider<?>> dataProviders = new HashMap<>(); + DataProvider<?> defaultDataProvider = null; + + abstract ChildT self(); + + public ChildT name(String name) { + this.name = name; + + return self(); + } + + public ChildT defaultDataProvider(DataProvider<?> dataProvider) { + this.defaultDataProvider = dataProvider; + + return self(); + } + + public ChildT addDataProvider(String targetNode, DataProvider<?> dataProvider) { + this.dataProviders.put(targetNode, dataProvider); return self(); } @@ -486,13 +702,49 @@ public class TestBuilders { /** Adds a column with the given default value to the table. */ ChildT addColumn(String name, NativeType type, @Nullable Object defaultValue); - /** Adds a data provider for the given node to the table. */ - ChildT addDataProvider(String targetNode, DataProvider<?> dataProvider); - /** Sets the size of the table. */ ChildT size(int size); } + /** + * Base interface describing the common set of index-related fields. + * + * <p>The sole purpose of this interface is to keep in sync both variants of index's builders. + * + * @param <ChildT> An actual type of builder that should be exposed to the user. + * @see ClusterHashIndexBuilder + * @see ClusterSortedIndexBuilder + * @see HashIndexBuilder + * @see SortedIndexBuilder + */ + private interface IndexBuilderBase<ChildT> { + /** Sets the name of the index. */ + ChildT name(String name); + + /** Sets the name of the table that index belongs to. */ + ChildT table(String name); + } + + /** + * Base interface describing the set of sorted-index related fields. + * + * @param <ChildT> An actual type of builder that should be exposed to the user. + */ + private interface SortedIndexBuilderBase<ChildT> extends IndexBuilderBase<ChildT> { + /** Adds a column with specified collation to the index. */ + ChildT addColumn(String columnName, Collation collation); + } + + /** + * Base interface describing the set of hash-index related fields. + * + * @param <ChildT> An actual type of builder that should be exposed to the user. + */ + private interface HashIndexBuilderBase<ChildT> extends IndexBuilderBase<ChildT> { + /** Adds a column to the index. */ + ChildT addColumn(String columnName); + } + /** * This interfaces provides a nested builder with ability to return on the previous layer. * @@ -520,8 +772,7 @@ public class TestBuilders { @FunctionalInterface private interface NestedBuilder<ParentT> { /** - * Notifies the builder's chain of the nested builder that we need to return back to the - * previous layer. + * Notifies the builder's chain of the nested builder that we need to return back to the previous layer. * * @return An instance of the parent builder. */ diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestIndex.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestIndex.java new file mode 100644 index 0000000000..a64b58018f --- /dev/null +++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestIndex.java @@ -0,0 +1,101 @@ +/* + * 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.sql.engine.framework; + +import static org.apache.ignite.lang.IgniteStringFormatter.format; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.ignite.internal.sql.engine.schema.IgniteIndex; +import org.jetbrains.annotations.Nullable; + +/** + * A test index that implements all the necessary for the optimizer methods to be used to prepare a query, as well as provides access to the + * data to use this index in execution-related scenarios. + */ +public class TestIndex extends IgniteIndex { + private static final String DATA_PROVIDER_NOT_CONFIGURED_MESSAGE_TEMPLATE = + "DataProvider is not configured [index={}, node={}]"; + + /** Factory method for creating hash-index. */ + static TestIndex createHash(String name, String tableName, List<String> indexedColumns, Map<String, DataProvider<?>> dataProviders) { + return new TestIndex(name, tableName, Type.HASH, indexedColumns, null, dataProviders); + } + + /** Factory method for creating sorted-index. */ + static TestIndex createSorted(String name, String tableName, List<String> columns, List<Collation> collations, + Map<String, DataProvider<?>> dataProviders) { + return new TestIndex(name, tableName, Type.SORTED, columns, collations, dataProviders); + } + + private static final AtomicInteger ID = new AtomicInteger(); + + private final int id = ID.incrementAndGet(); + private final String name; + private final String tableName; + + private final Map<String, DataProvider<?>> dataProviders; + + /** Constructor. */ + private TestIndex( + String name, + String tableName, + Type type, + List<String> columns, + @Nullable List<Collation> collations, + Map<String, DataProvider<?>> dataProviders + ) { + super(type, columns, collations); + + this.name = name; + this.tableName = tableName; + this.dataProviders = dataProviders; + } + + /** Returns an id of the index. */ + public int id() { + return id; + } + + /** {@inheritDoc} */ + @Override + public String name() { + return name; + } + + public String tableName() { + return tableName; + } + + /** + * Returns the data provider for the given node. + * + * @param nodeName Name of the node of interest. + * @param <RowT> A type of the rows the data provider should produce. + * @return A data provider for the node of interest. + * @throws AssertionError in case data provider is not configured for the given node. + */ + <RowT> DataProvider<RowT> dataProvider(String nodeName) { + if (!dataProviders.containsKey(nodeName)) { + throw new AssertionError(format(DATA_PROVIDER_NOT_CONFIGURED_MESSAGE_TEMPLATE, name(), nodeName)); + } + + return (DataProvider<RowT>) dataProviders.get(nodeName); + } +} diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java index 4360462bd9..8840ff3699 100644 --- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java +++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java @@ -61,6 +61,7 @@ import org.apache.ignite.internal.sql.engine.prepare.PrepareService; import org.apache.ignite.internal.sql.engine.prepare.PrepareServiceImpl; import org.apache.ignite.internal.sql.engine.prepare.QueryPlan; import org.apache.ignite.internal.sql.engine.prepare.ddl.DdlSqlToCommandConverter; +import org.apache.ignite.internal.sql.engine.rel.IgniteIndexScan; import org.apache.ignite.internal.sql.engine.rel.IgniteTableScan; import org.apache.ignite.internal.sql.engine.schema.SqlSchemaManager; import org.apache.ignite.internal.sql.engine.sql.IgniteSqlParser; @@ -140,6 +141,16 @@ public class TestNode implements LifecycleAware { return new ScanNode<>(ctx, dataProvider); } + + @Override + public Node<Object[]> visit(IgniteIndexScan rel) { + TestTable tbl = rel.getTable().unwrap(TestTable.class); + TestIndex idx = (TestIndex) tbl.getIndex(rel.indexName()); + + DataProvider<Object[]> dataProvider = idx.dataProvider(ctx.localNode().name()); + + return new ScanNode<>(ctx, dataProvider); + } } )); }
