blob: 751b5373a0df6d3b838782a4e587ba3dc5c52d57 [file] [log] [blame]
/*
* 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();
}
}