| /* |
| * 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.catalog.commands; |
| |
| import static java.util.stream.Collectors.toList; |
| import static org.apache.ignite.internal.catalog.CatalogService.DEFAULT_SCHEMA_NAME; |
| import static org.apache.ignite.internal.catalog.CatalogTestUtils.createCatalogManagerWithTestUpdateLog; |
| import static org.apache.ignite.internal.catalog.CatalogTestUtils.index; |
| import static org.apache.ignite.internal.catalog.commands.CatalogUtils.clusterWideEnsuredActivationTimestamp; |
| import static org.apache.ignite.internal.catalog.commands.CatalogUtils.collectIndexes; |
| import static org.apache.ignite.internal.catalog.commands.CatalogUtils.pkIndexName; |
| import static org.apache.ignite.internal.catalog.commands.CatalogUtils.replaceIndex; |
| import static org.apache.ignite.internal.catalog.commands.CatalogUtils.replaceTable; |
| import static org.apache.ignite.internal.hlc.TestClockService.TEST_MAX_CLOCK_SKEW_MILLIS; |
| import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully; |
| import static org.apache.ignite.sql.ColumnType.INT32; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.hamcrest.Matchers.contains; |
| import static org.hamcrest.Matchers.hasItem; |
| import static org.hamcrest.Matchers.hasItems; |
| import static org.hamcrest.Matchers.is; |
| import static org.hamcrest.Matchers.notNullValue; |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertNotNull; |
| import static org.junit.jupiter.api.Assertions.assertThrows; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.when; |
| |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.List; |
| import org.apache.ignite.internal.catalog.Catalog; |
| import org.apache.ignite.internal.catalog.CatalogCommand; |
| import org.apache.ignite.internal.catalog.CatalogManager; |
| import org.apache.ignite.internal.catalog.CatalogValidationException; |
| import org.apache.ignite.internal.catalog.descriptors.CatalogHashIndexDescriptor; |
| import org.apache.ignite.internal.catalog.descriptors.CatalogIndexDescriptor; |
| import org.apache.ignite.internal.catalog.descriptors.CatalogSchemaDescriptor; |
| import org.apache.ignite.internal.catalog.descriptors.CatalogTableDescriptor; |
| import org.apache.ignite.internal.hlc.HybridClock; |
| import org.apache.ignite.internal.hlc.HybridClockImpl; |
| import org.apache.ignite.internal.hlc.HybridTimestamp; |
| import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest; |
| import org.junit.jupiter.api.AfterEach; |
| import org.junit.jupiter.api.BeforeEach; |
| import org.junit.jupiter.api.Test; |
| |
| /** For {@link CatalogUtils} testing. */ |
| public class CatalogUtilsTest extends BaseIgniteAbstractTest { |
| private static final String TABLE_NAME = "test_table"; |
| |
| private static final String INDEX_NAME = "test_index"; |
| |
| private static final String PK_INDEX_NAME = pkIndexName(TABLE_NAME); |
| |
| private static final String COLUMN_NAME = "key"; |
| |
| private final HybridClock clock = new HybridClockImpl(); |
| |
| private CatalogManager catalogManager; |
| |
| @BeforeEach |
| void setUp() { |
| catalogManager = createCatalogManagerWithTestUpdateLog("test", clock); |
| |
| assertThat(catalogManager.startAsync(), willCompleteSuccessfully()); |
| } |
| |
| @AfterEach |
| void tearDown() { |
| assertThat(catalogManager.stopAsync(), willCompleteSuccessfully()); |
| } |
| |
| @Test |
| void testCollectIndexesAfterCreateTable() { |
| createTable(TABLE_NAME); |
| |
| int latestCatalogVersion = catalogManager.latestCatalogVersion(); |
| int earliestCatalogVersion = catalogManager.earliestCatalogVersion(); |
| |
| int tableId = tableId(latestCatalogVersion, TABLE_NAME); |
| |
| assertThat( |
| collectIndexes(catalogManager, tableId, latestCatalogVersion, latestCatalogVersion), |
| hasItems(index(catalogManager, latestCatalogVersion, PK_INDEX_NAME)) |
| ); |
| |
| assertThat( |
| collectIndexes(catalogManager, tableId, earliestCatalogVersion, latestCatalogVersion), |
| hasItems(index(catalogManager, latestCatalogVersion, PK_INDEX_NAME)) |
| ); |
| } |
| |
| @Test |
| void testCollectIndexesAfterCreateIndex() { |
| createTable(TABLE_NAME); |
| createIndex(TABLE_NAME, INDEX_NAME); |
| |
| int latestCatalogVersion = catalogManager.latestCatalogVersion(); |
| int earliestCatalogVersion = catalogManager.earliestCatalogVersion(); |
| |
| int tableId = tableId(latestCatalogVersion, TABLE_NAME); |
| |
| assertThat( |
| collectIndexes(catalogManager, tableId, latestCatalogVersion, latestCatalogVersion), |
| hasItems( |
| index(catalogManager, latestCatalogVersion, PK_INDEX_NAME), |
| index(catalogManager, latestCatalogVersion, INDEX_NAME) |
| ) |
| ); |
| |
| assertThat( |
| collectIndexes(catalogManager, tableId, earliestCatalogVersion, latestCatalogVersion), |
| hasItems( |
| index(catalogManager, latestCatalogVersion, PK_INDEX_NAME), |
| index(catalogManager, latestCatalogVersion, INDEX_NAME) |
| ) |
| ); |
| } |
| |
| @Test |
| void testCollectIndexesAfterCreateIndexAndStartBuildingIndexAndMakeAvailableIndex() { |
| String indexName0 = INDEX_NAME + 0; |
| String indexName1 = INDEX_NAME + 1; |
| String indexName2 = INDEX_NAME + 2; |
| |
| createTable(TABLE_NAME); |
| createIndex(TABLE_NAME, indexName0); |
| int indexId1 = createIndex(TABLE_NAME, indexName1); |
| int indexId2 = createIndex(TABLE_NAME, indexName2); |
| |
| startBuildingIndex(indexId1); |
| makeIndexAvailable(indexId1); |
| |
| startBuildingIndex(indexId2); |
| |
| int latestCatalogVersion = catalogManager.latestCatalogVersion(); |
| int earliestCatalogVersion = catalogManager.earliestCatalogVersion(); |
| |
| int tableId = tableId(latestCatalogVersion, TABLE_NAME); |
| |
| assertThat( |
| collectIndexes(catalogManager, tableId, latestCatalogVersion, latestCatalogVersion), |
| hasItems( |
| index(catalogManager, latestCatalogVersion, PK_INDEX_NAME), |
| index(catalogManager, latestCatalogVersion, indexName0), |
| index(catalogManager, latestCatalogVersion, indexName1), |
| index(catalogManager, latestCatalogVersion, indexName2) |
| ) |
| ); |
| |
| assertThat( |
| collectIndexes(catalogManager, tableId, earliestCatalogVersion, latestCatalogVersion), |
| hasItems( |
| index(catalogManager, latestCatalogVersion, PK_INDEX_NAME), |
| index(catalogManager, latestCatalogVersion, indexName0), |
| index(catalogManager, latestCatalogVersion, indexName1), |
| index(catalogManager, latestCatalogVersion, indexName2) |
| ) |
| ); |
| } |
| |
| @Test |
| void testCollectIndexesAfterDropIndexes() { |
| String indexName0 = INDEX_NAME + 0; |
| String indexName1 = INDEX_NAME + 1; |
| String indexName2 = INDEX_NAME + 2; |
| |
| createTable(TABLE_NAME); |
| createIndex(TABLE_NAME, indexName0); |
| int indexId1 = createIndex(TABLE_NAME, indexName1); |
| int indexId2 = createIndex(TABLE_NAME, indexName2); |
| |
| startBuildingIndex(indexId1); |
| makeIndexAvailable(indexId1); |
| |
| startBuildingIndex(indexId2); |
| |
| int catalogVersionBeforeDropIndex0 = catalogManager.latestCatalogVersion(); |
| |
| dropIndex(indexName0); |
| |
| int catalogVersionBeforeDropIndex1 = catalogManager.latestCatalogVersion(); |
| |
| dropIndex(indexName1); |
| |
| int catalogVersionBeforeRemoveIndex1 = catalogManager.latestCatalogVersion(); |
| |
| removeIndex(indexId1); |
| |
| int latestCatalogVersion = catalogManager.latestCatalogVersion(); |
| int earliestCatalogVersion = catalogManager.earliestCatalogVersion(); |
| |
| int tableId = tableId(latestCatalogVersion, TABLE_NAME); |
| |
| assertThat( |
| collectIndexes(catalogManager, tableId, latestCatalogVersion, latestCatalogVersion), |
| hasItems(index(catalogManager, latestCatalogVersion, PK_INDEX_NAME)) |
| ); |
| |
| Collection<CatalogIndexDescriptor> collectedIndexes = collectIndexes( |
| catalogManager, |
| tableId, |
| earliestCatalogVersion, |
| latestCatalogVersion |
| ); |
| assertThat(collectedIndexes, hasItem(index(catalogManager, latestCatalogVersion, PK_INDEX_NAME))); |
| assertThat(collectedIndexes, hasItem(index(catalogManager, catalogVersionBeforeDropIndex0, indexName0))); |
| assertThat(collectedIndexes, hasItem(index(catalogManager, catalogVersionBeforeRemoveIndex1, indexName1))); |
| assertThat(collectedIndexes, hasItem(index(catalogManager, catalogVersionBeforeDropIndex1, indexName2))); |
| } |
| |
| /** |
| * Tests the more complex case of getting indexes. |
| * |
| * <p>Consider the following versions of the catalog with its contents:</p> |
| * <pre> |
| * Catalog versions and entity IDs have been simplified. |
| * |
| * 0 : T0 Ipk(A) |
| * 1 : T0 Ipk(A) I0(R) I1(R) I2(R) I3(R) |
| * 2 : T0 Ipk(A) I0(A) I1(B) I2(A) I3(R) |
| * 3 : T0 Ipk(A) I1(B) I2(A) |
| * </pre> |
| * |
| * <p>Expected indexes for range version:</p> |
| * <pre> |
| * 3 -> 3 : Ipk(A) I1(B) I2(A) |
| * 0 -> 3 : Ipk(A) I0(A) I1(B) I2(A) I3(R) |
| * </pre> |
| */ |
| @Test |
| void testCollectIndexesComplexCase() { |
| String indexName0 = INDEX_NAME + 0; |
| String indexName1 = INDEX_NAME + 1; |
| String indexName2 = INDEX_NAME + 2; |
| String indexName3 = INDEX_NAME + 3; |
| |
| createTable(TABLE_NAME); |
| int indexId0 = createIndex(TABLE_NAME, indexName0); |
| int indexId1 = createIndex(TABLE_NAME, indexName1); |
| int indexId2 = createIndex(TABLE_NAME, indexName2); |
| createIndex(TABLE_NAME, indexName3); |
| |
| startBuildingIndex(indexId0); |
| startBuildingIndex(indexId1); |
| startBuildingIndex(indexId2); |
| |
| makeIndexAvailable(indexId0); |
| makeIndexAvailable(indexId2); |
| |
| dropIndex(indexName0); |
| |
| int catalogVersionBeforeRemoveIndex0 = catalogManager.latestCatalogVersion(); |
| |
| removeIndex(indexId0); |
| |
| int catalogVersionBeforeDropIndex3 = catalogManager.latestCatalogVersion(); |
| |
| dropIndex(indexName3); |
| |
| int latestCatalogVersion = catalogManager.latestCatalogVersion(); |
| int earliestCatalogVersion = catalogManager.earliestCatalogVersion(); |
| |
| int tableId = tableId(latestCatalogVersion, TABLE_NAME); |
| |
| assertThat( |
| collectIndexes(catalogManager, tableId, latestCatalogVersion, latestCatalogVersion), |
| hasItems( |
| index(catalogManager, latestCatalogVersion, PK_INDEX_NAME), |
| index(catalogManager, latestCatalogVersion, indexName1), |
| index(catalogManager, latestCatalogVersion, indexName2) |
| ) |
| ); |
| |
| Collection<CatalogIndexDescriptor> collectedIndexes = collectIndexes( |
| catalogManager, |
| tableId, |
| earliestCatalogVersion, |
| latestCatalogVersion |
| ); |
| assertThat(collectedIndexes, hasItems(index(catalogManager, latestCatalogVersion, PK_INDEX_NAME))); |
| assertThat(collectedIndexes, hasItems(index(catalogManager, catalogVersionBeforeRemoveIndex0, indexName0))); |
| assertThat(collectedIndexes, hasItems(index(catalogManager, latestCatalogVersion, indexName1))); |
| assertThat(collectedIndexes, hasItems(index(catalogManager, latestCatalogVersion, indexName2))); |
| assertThat(collectedIndexes, hasItems(index(catalogManager, catalogVersionBeforeDropIndex3, indexName3))); |
| } |
| |
| @Test |
| void testReplaceTable() { |
| createTable("foo"); |
| createTable("bar"); |
| |
| CatalogSchemaDescriptor schema = catalogManager.activeSchema(DEFAULT_SCHEMA_NAME, clock.nowLong()); |
| |
| assertThat(schema, is(notNullValue())); |
| |
| CatalogTableDescriptor fooTable = catalogManager.table("foo", clock.nowLong()); |
| |
| assertThat(fooTable, is(notNullValue())); |
| |
| CatalogTableDescriptor bazTable = fooTable.newDescriptor( |
| "baz", |
| fooTable.tableVersion(), |
| fooTable.columns(), |
| fooTable.updateToken(), |
| fooTable.storageProfile() |
| ); |
| |
| CatalogSchemaDescriptor updatedSchema = replaceTable(schema, bazTable); |
| |
| List<String> tableNames = Arrays.stream(updatedSchema.tables()).map(CatalogTableDescriptor::name).collect(toList()); |
| |
| assertThat(tableNames, contains("baz", "bar")); |
| } |
| |
| @Test |
| void testReplaceTableMissingTable() { |
| CatalogSchemaDescriptor schema = catalogManager.activeSchema(DEFAULT_SCHEMA_NAME, clock.nowLong()); |
| |
| assertThat(schema, is(notNullValue())); |
| |
| var table = mock(CatalogTableDescriptor.class); |
| |
| when(table.id()).thenReturn(Integer.MAX_VALUE); |
| |
| Exception e = assertThrows(CatalogValidationException.class, () -> replaceTable(schema, table)); |
| |
| assertThat(e.getMessage(), is(String.format("Table with ID %d has not been found in schema with ID %d", table.id(), 0))); |
| } |
| |
| @Test |
| void testReplaceIndex() { |
| String tableName = "table"; |
| |
| createTable(tableName); |
| createIndex(tableName, "foo"); |
| createIndex(tableName, "bar"); |
| |
| CatalogSchemaDescriptor schema = catalogManager.activeSchema(DEFAULT_SCHEMA_NAME, clock.nowLong()); |
| |
| assertThat(schema, is(notNullValue())); |
| |
| var fooIndex = (CatalogHashIndexDescriptor) catalogManager.aliveIndex("foo", clock.nowLong()); |
| |
| assertThat(fooIndex, is(notNullValue())); |
| |
| CatalogIndexDescriptor bazIndex = new CatalogHashIndexDescriptor( |
| fooIndex.id(), |
| "baz", |
| fooIndex.tableId(), |
| fooIndex.unique(), |
| fooIndex.status(), |
| fooIndex.txWaitCatalogVersion(), |
| fooIndex.columns() |
| ); |
| |
| CatalogSchemaDescriptor updatedSchema = replaceIndex(schema, bazIndex); |
| |
| List<String> indexNames = Arrays.stream(updatedSchema.indexes()).map(CatalogIndexDescriptor::name).collect(toList()); |
| |
| assertThat(indexNames, contains(tableName + "_PK", "baz", "bar")); |
| } |
| |
| @Test |
| void testReplaceIndexMissingIndex() { |
| CatalogSchemaDescriptor schema = catalogManager.activeSchema(DEFAULT_SCHEMA_NAME, clock.nowLong()); |
| |
| assertThat(schema, is(notNullValue())); |
| |
| var index = mock(CatalogIndexDescriptor.class); |
| |
| when(index.id()).thenReturn(Integer.MAX_VALUE); |
| |
| Exception e = assertThrows(CatalogValidationException.class, () -> replaceIndex(schema, index)); |
| |
| assertThat(e.getMessage(), is(String.format("Index with ID %d has not been found in schema with ID %d", index.id(), 1))); |
| } |
| |
| @Test |
| void testClusterWideEnsuredActivationTimestamp() { |
| createTable(TABLE_NAME); |
| |
| Catalog catalog = catalogManager.catalog(catalogManager.latestCatalogVersion()); |
| |
| HybridTimestamp expClusterWideActivationTs = HybridTimestamp.hybridTimestamp(catalog.time()) |
| .addPhysicalTime(TEST_MAX_CLOCK_SKEW_MILLIS) |
| .roundUpToPhysicalTick(); |
| |
| assertEquals(expClusterWideActivationTs, clusterWideEnsuredActivationTimestamp(catalog, TEST_MAX_CLOCK_SKEW_MILLIS)); |
| } |
| |
| private void createTable(String tableName) { |
| CatalogCommand catalogCommand = CreateTableCommand.builder() |
| .schemaName(DEFAULT_SCHEMA_NAME) |
| .tableName(tableName) |
| .columns(List.of(ColumnParams.builder().name(COLUMN_NAME).type(INT32).build())) |
| // Any type of a primary key index can be used. |
| .primaryKey(TableHashPrimaryKey.builder() |
| .columns(List.of(COLUMN_NAME)) |
| .build() |
| ) |
| .colocationColumns(List.of(COLUMN_NAME)) |
| .build(); |
| |
| assertThat(catalogManager.execute(catalogCommand), willCompleteSuccessfully()); |
| } |
| |
| private int createIndex(String tableName, String indexName) { |
| CatalogCommand catalogCommand = CreateHashIndexCommand.builder() |
| .schemaName(DEFAULT_SCHEMA_NAME) |
| .tableName(tableName) |
| .indexName(indexName) |
| .columns(List.of(COLUMN_NAME)) |
| .unique(false) |
| .build(); |
| |
| assertThat(catalogManager.execute(catalogCommand), willCompleteSuccessfully()); |
| |
| return catalogManager.aliveIndex(indexName, clock.nowLong()).id(); |
| } |
| |
| private void startBuildingIndex(int indexId) { |
| CatalogCommand catalogCommand = StartBuildingIndexCommand.builder().indexId(indexId).build(); |
| |
| assertThat(catalogManager.execute(catalogCommand), willCompleteSuccessfully()); |
| } |
| |
| private void makeIndexAvailable(int indexId) { |
| CatalogCommand catalogCommand = MakeIndexAvailableCommand.builder().indexId(indexId).build(); |
| |
| assertThat(catalogManager.execute(catalogCommand), willCompleteSuccessfully()); |
| } |
| |
| private void dropIndex(String indexName) { |
| CatalogCommand catalogCommand = DropIndexCommand.builder() |
| .schemaName(DEFAULT_SCHEMA_NAME) |
| .indexName(indexName) |
| .build(); |
| |
| assertThat(catalogManager.execute(catalogCommand), willCompleteSuccessfully()); |
| } |
| |
| private void removeIndex(int indexId) { |
| CatalogCommand catalogCommand = RemoveIndexCommand.builder().indexId(indexId).build(); |
| |
| assertThat(catalogManager.execute(catalogCommand), willCompleteSuccessfully()); |
| } |
| |
| private int tableId(int catalogVersion, String tableName) { |
| CatalogTableDescriptor tableDescriptor = catalogManager.tables(catalogVersion).stream() |
| .filter(table -> tableName.equals(table.name())) |
| .findFirst() |
| .orElse(null); |
| |
| assertNotNull(tableDescriptor, "catalogVersion=" + catalogVersion + ", tableName=" + tableName); |
| |
| return tableDescriptor.id(); |
| } |
| } |