blob: 5ff386c26e023ae9e8e0ed9b504886031b0ea583 [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;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static org.apache.ignite.internal.testframework.matchers.CompletableFutureExceptionMatcher.willThrow;
import static org.apache.ignite.internal.testframework.matchers.CompletableFutureExceptionMatcher.willThrowFast;
import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willBe;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.apache.ignite.internal.catalog.commands.AlterTableAddColumnParams;
import org.apache.ignite.internal.catalog.commands.AlterTableDropColumnParams;
import org.apache.ignite.internal.catalog.commands.ColumnParams;
import org.apache.ignite.internal.catalog.commands.CreateHashIndexParams;
import org.apache.ignite.internal.catalog.commands.CreateSortedIndexParams;
import org.apache.ignite.internal.catalog.commands.CreateTableParams;
import org.apache.ignite.internal.catalog.commands.DefaultValue;
import org.apache.ignite.internal.catalog.commands.DropIndexParams;
import org.apache.ignite.internal.catalog.commands.DropTableParams;
import org.apache.ignite.internal.catalog.descriptors.ColumnCollation;
import org.apache.ignite.internal.catalog.descriptors.HashIndexDescriptor;
import org.apache.ignite.internal.catalog.descriptors.SchemaDescriptor;
import org.apache.ignite.internal.catalog.descriptors.SortedIndexDescriptor;
import org.apache.ignite.internal.catalog.descriptors.TableColumnDescriptor;
import org.apache.ignite.internal.catalog.descriptors.TableDescriptor;
import org.apache.ignite.internal.catalog.events.AddColumnEventParameters;
import org.apache.ignite.internal.catalog.events.CatalogEvent;
import org.apache.ignite.internal.catalog.events.CatalogEventParameters;
import org.apache.ignite.internal.catalog.events.CreateIndexEventParameters;
import org.apache.ignite.internal.catalog.events.CreateTableEventParameters;
import org.apache.ignite.internal.catalog.events.DropColumnEventParameters;
import org.apache.ignite.internal.catalog.events.DropIndexEventParameters;
import org.apache.ignite.internal.catalog.events.DropTableEventParameters;
import org.apache.ignite.internal.catalog.storage.ObjectIdGenUpdateEntry;
import org.apache.ignite.internal.catalog.storage.UpdateLog;
import org.apache.ignite.internal.catalog.storage.UpdateLog.OnUpdateHandler;
import org.apache.ignite.internal.catalog.storage.UpdateLogImpl;
import org.apache.ignite.internal.catalog.storage.VersionedUpdate;
import org.apache.ignite.internal.manager.EventListener;
import org.apache.ignite.internal.metastorage.MetaStorageManager;
import org.apache.ignite.internal.metastorage.impl.StandaloneMetaStorageManager;
import org.apache.ignite.internal.metastorage.server.SimpleInMemoryKeyValueStorage;
import org.apache.ignite.internal.vault.VaultManager;
import org.apache.ignite.internal.vault.inmemory.InMemoryVaultService;
import org.apache.ignite.lang.ColumnAlreadyExistsException;
import org.apache.ignite.lang.ColumnNotFoundException;
import org.apache.ignite.lang.IgniteInternalException;
import org.apache.ignite.lang.IndexAlreadyExistsException;
import org.apache.ignite.lang.IndexNotFoundException;
import org.apache.ignite.lang.NodeStoppingException;
import org.apache.ignite.lang.TableAlreadyExistsException;
import org.apache.ignite.lang.TableNotFoundException;
import org.apache.ignite.sql.ColumnType;
import org.apache.ignite.sql.SqlException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
/**
* Catalog service self test.
*/
public class CatalogServiceSelfTest {
private static final String SCHEMA_NAME = CatalogService.PUBLIC;
private static final String ZONE_NAME = "ZONE";
private static final String TABLE_NAME = "myTable";
private static final String TABLE_NAME_2 = "myTable2";
private static final String NEW_COLUMN_NAME = "NEWCOL";
private static final String NEW_COLUMN_NAME_2 = "NEWCOL2";
private static final String INDEX_NAME = "myIndex";
private MetaStorageManager metastore;
private VaultManager vault;
private CatalogServiceImpl service;
@BeforeEach
void setUp() throws NodeStoppingException {
vault = new VaultManager(new InMemoryVaultService());
metastore = StandaloneMetaStorageManager.create(
vault, new SimpleInMemoryKeyValueStorage("test")
);
service = new CatalogServiceImpl(new UpdateLogImpl(metastore, vault));
vault.start();
metastore.start();
service.start();
metastore.deployWatches();
}
@AfterEach
public void tearDown() throws Exception {
service.stop();
metastore.stop();
vault.stop();
}
@Test
public void testEmptyCatalog() {
assertNotNull(service.activeSchema(System.currentTimeMillis()));
assertNotNull(service.schema(0));
assertNull(service.schema(1));
assertThrows(IllegalStateException.class, () -> service.activeSchema(-1L));
assertNull(service.table(0, System.currentTimeMillis()));
assertNull(service.index(0, System.currentTimeMillis()));
SchemaDescriptor schema = service.schema(0);
assertEquals(SCHEMA_NAME, schema.name());
assertEquals(0, schema.id());
assertEquals(0, schema.version());
assertEquals(0, schema.tables().length);
assertEquals(0, schema.indexes().length);
}
@Test
public void testCreateTable() {
CreateTableParams params = CreateTableParams.builder()
.schemaName(SCHEMA_NAME)
.tableName(TABLE_NAME)
.zone(ZONE_NAME)
.columns(List.of(
ColumnParams.builder().name("key1").type(ColumnType.INT32).build(),
ColumnParams.builder().name("key2").type(ColumnType.INT32).build(),
ColumnParams.builder().name("val").type(ColumnType.INT32).nullable(true).build()
))
.primaryKeyColumns(List.of("key1", "key2"))
.colocationColumns(List.of("key2"))
.build();
CompletableFuture<Void> fut = service.createTable(params);
assertThat(fut, willBe((Object) null));
// Validate catalog version from the past.
SchemaDescriptor schema = service.schema(0);
assertNotNull(schema);
assertEquals(0, schema.id());
assertEquals(SCHEMA_NAME, schema.name());
assertEquals(0, schema.version());
assertSame(schema, service.activeSchema(0L));
assertSame(schema, service.activeSchema(123L));
assertNull(schema.table(TABLE_NAME));
assertNull(service.table(TABLE_NAME, 123L));
assertNull(service.table(1, 123L));
// Validate actual catalog
schema = service.schema(1);
assertNotNull(schema);
assertEquals(0, schema.id());
assertEquals(SCHEMA_NAME, schema.name());
assertEquals(1, schema.version());
assertSame(schema, service.activeSchema(System.currentTimeMillis()));
assertSame(schema.table(TABLE_NAME), service.table(TABLE_NAME, System.currentTimeMillis()));
assertSame(schema.table(TABLE_NAME), service.table(1, System.currentTimeMillis()));
// Validate newly created table
TableDescriptor table = schema.table(TABLE_NAME);
assertEquals(1L, table.id());
assertEquals(TABLE_NAME, table.name());
assertEquals(0L, table.engineId());
assertEquals(0L, table.zoneId());
// Validate another table creation.
assertThat(service.createTable(simpleTable(TABLE_NAME_2)), willBe((Object) null));
// Validate actual catalog has both tables.
schema = service.schema(2);
assertNotNull(schema);
assertEquals(0, schema.id());
assertEquals(SCHEMA_NAME, schema.name());
assertEquals(2, schema.version());
assertSame(schema, service.activeSchema(System.currentTimeMillis()));
assertSame(schema.table(TABLE_NAME), service.table(TABLE_NAME, System.currentTimeMillis()));
assertSame(schema.table(TABLE_NAME), service.table(1, System.currentTimeMillis()));
assertSame(schema.table(TABLE_NAME_2), service.table(TABLE_NAME_2, System.currentTimeMillis()));
assertSame(schema.table(TABLE_NAME_2), service.table(2, System.currentTimeMillis()));
assertNotSame(schema.table(TABLE_NAME), schema.table(TABLE_NAME_2));
// Try to create another table with same name.
assertThat(service.createTable(simpleTable(TABLE_NAME_2)), willThrowFast(TableAlreadyExistsException.class));
// Validate schema wasn't changed.
assertSame(schema, service.activeSchema(System.currentTimeMillis()));
}
@Test
public void testDropTable() throws InterruptedException {
assertThat(service.createTable(simpleTable(TABLE_NAME)), willBe((Object) null));
assertThat(service.createTable(simpleTable(TABLE_NAME_2)), willBe((Object) null));
long beforeDropTimestamp = System.currentTimeMillis();
Thread.sleep(5);
DropTableParams dropTableParams = DropTableParams.builder().schemaName(SCHEMA_NAME).tableName(TABLE_NAME).build();
assertThat(service.dropTable(dropTableParams), willBe((Object) null));
// Validate catalog version from the past.
SchemaDescriptor schema = service.schema(2);
assertNotNull(schema);
assertEquals(0, schema.id());
assertEquals(SCHEMA_NAME, schema.name());
assertEquals(2, schema.version());
assertSame(schema, service.activeSchema(beforeDropTimestamp));
assertSame(schema.table(TABLE_NAME), service.table(TABLE_NAME, beforeDropTimestamp));
assertSame(schema.table(TABLE_NAME), service.table(1, beforeDropTimestamp));
assertSame(schema.table(TABLE_NAME_2), service.table(TABLE_NAME_2, beforeDropTimestamp));
assertSame(schema.table(TABLE_NAME_2), service.table(2, beforeDropTimestamp));
// Validate actual catalog
schema = service.schema(3);
assertNotNull(schema);
assertEquals(0, schema.id());
assertEquals(SCHEMA_NAME, schema.name());
assertEquals(3, schema.version());
assertSame(schema, service.activeSchema(System.currentTimeMillis()));
assertNull(schema.table(TABLE_NAME));
assertNull(service.table(TABLE_NAME, System.currentTimeMillis()));
assertNull(service.table(1, System.currentTimeMillis()));
assertSame(schema.table(TABLE_NAME_2), service.table(TABLE_NAME_2, System.currentTimeMillis()));
assertSame(schema.table(TABLE_NAME_2), service.table(2, System.currentTimeMillis()));
// Try to drop table once again.
assertThat(service.dropTable(dropTableParams), willThrowFast(TableNotFoundException.class));
// Validate schema wasn't changed.
assertSame(schema, service.activeSchema(System.currentTimeMillis()));
}
@Test
public void testAddColumn() throws InterruptedException {
assertThat(service.createTable(simpleTable(TABLE_NAME)), willBe((Object) null));
AlterTableAddColumnParams params = AlterTableAddColumnParams.builder()
.schemaName(SCHEMA_NAME)
.tableName(TABLE_NAME)
.columns(List.of(ColumnParams.builder()
.name(NEW_COLUMN_NAME)
.type(ColumnType.STRING)
.nullable(true)
.defaultValue(DefaultValue.constant("Ignite!"))
.build()
))
.build();
long beforeAddedTimestamp = System.currentTimeMillis();
Thread.sleep(5);
assertThat(service.addColumn(params), willBe((Object) null));
// Validate catalog version from the past.
SchemaDescriptor schema = service.activeSchema(beforeAddedTimestamp);
assertNotNull(schema);
assertNotNull(schema.table(TABLE_NAME));
assertNull(schema.table(TABLE_NAME).column(NEW_COLUMN_NAME));
// Validate actual catalog
schema = service.activeSchema(System.currentTimeMillis());
assertNotNull(schema);
assertNotNull(schema.table(TABLE_NAME));
// Validate column descriptor.
TableColumnDescriptor column = schema.table(TABLE_NAME).column(NEW_COLUMN_NAME);
assertEquals(NEW_COLUMN_NAME, column.name());
assertEquals(ColumnType.STRING, column.type());
assertTrue(column.nullable());
assertEquals(DefaultValue.Type.CONSTANT, column.defaultValue().type());
assertEquals("Ignite!", ((DefaultValue.ConstantValue) column.defaultValue()).value());
assertEquals(0, column.length());
assertEquals(0, column.precision());
assertEquals(0, column.scale());
}
@Test
public void testDropColumn() throws InterruptedException {
assertThat(service.createTable(simpleTable(TABLE_NAME)), willBe((Object) null));
// Validate dropping column
AlterTableDropColumnParams params = AlterTableDropColumnParams.builder()
.schemaName(SCHEMA_NAME)
.tableName(TABLE_NAME)
.columns(Set.of("VAL"))
.build();
long beforeAddedTimestamp = System.currentTimeMillis();
Thread.sleep(5);
assertThat(service.dropColumn(params), willBe((Object) null));
// Validate catalog version from the past.
SchemaDescriptor schema = service.activeSchema(beforeAddedTimestamp);
assertNotNull(schema);
assertNotNull(schema.table(TABLE_NAME));
assertNotNull(schema.table(TABLE_NAME).column("VAL"));
// Validate actual catalog
schema = service.activeSchema(System.currentTimeMillis());
assertNotNull(schema);
assertNotNull(schema.table(TABLE_NAME));
assertNull(schema.table(TABLE_NAME).column("VAL"));
}
@Test
public void testCreateDropColumnIfTableNotExists() {
assertNull(service.table(TABLE_NAME, System.currentTimeMillis()));
// Try to add a new column.
AlterTableAddColumnParams addColumnParams = AlterTableAddColumnParams.builder()
.schemaName(SCHEMA_NAME)
.tableName(TABLE_NAME)
.columns(List.of(ColumnParams.builder().name(NEW_COLUMN_NAME).type(ColumnType.INT32).nullable(true).build()))
.build();
assertThat(service.addColumn(addColumnParams), willThrow(TableNotFoundException.class));
// Try to drop column.
AlterTableDropColumnParams dropColumnParams = AlterTableDropColumnParams.builder()
.schemaName(SCHEMA_NAME)
.tableName(TABLE_NAME)
.columns(Set.of("VAL"))
.build();
assertThat(service.dropColumn(dropColumnParams), willThrow(TableNotFoundException.class));
}
@Test
public void testDropIndexedColumn() {
assertThat(service.createTable(simpleTable(TABLE_NAME)), willBe((Object) null));
assertThat(service.createIndex(simpleIndex(INDEX_NAME, TABLE_NAME)), willBe((Object) null));
// Try to drop indexed column
AlterTableDropColumnParams params = AlterTableDropColumnParams.builder()
.schemaName(SCHEMA_NAME)
.tableName(TABLE_NAME)
.columns(Set.of("VAL"))
.build();
assertThat(service.dropColumn(params), willThrow(SqlException.class));
// Try to drop PK column
params = AlterTableDropColumnParams.builder()
.schemaName(SCHEMA_NAME)
.tableName(TABLE_NAME)
.columns(Set.of("ID"))
.build();
assertThat(service.dropColumn(params), willThrow(SqlException.class));
// Validate actual catalog
SchemaDescriptor schema = service.activeSchema(System.currentTimeMillis());
assertNotNull(schema);
assertNotNull(schema.table(TABLE_NAME));
assertEquals(2, schema.version());
assertNotNull(schema.table(TABLE_NAME).column("ID"));
assertNotNull(schema.table(TABLE_NAME).column("VAL"));
}
@Test
public void testAddDropMultipleColumns() {
assertThat(service.createTable(simpleTable(TABLE_NAME)), willBe((Object) null));
// Add duplicate column.
AlterTableAddColumnParams addColumnParams = AlterTableAddColumnParams.builder()
.schemaName(SCHEMA_NAME)
.tableName(TABLE_NAME)
.columns(List.of(
ColumnParams.builder().name(NEW_COLUMN_NAME).type(ColumnType.INT32).nullable(true).build(),
ColumnParams.builder().name("VAL").type(ColumnType.INT32).nullable(true).build()
))
.build();
assertThat(service.addColumn(addColumnParams), willThrow(ColumnAlreadyExistsException.class));
// Validate no column added.
SchemaDescriptor schema = service.activeSchema(System.currentTimeMillis());
assertNull(schema.table(TABLE_NAME).column(NEW_COLUMN_NAME));
// Add multiple columns.
addColumnParams = AlterTableAddColumnParams.builder()
.schemaName(SCHEMA_NAME)
.tableName(TABLE_NAME)
.columns(List.of(
ColumnParams.builder().name(NEW_COLUMN_NAME).type(ColumnType.INT32).nullable(true).build(),
ColumnParams.builder().name(NEW_COLUMN_NAME_2).type(ColumnType.INT32).nullable(true).build()
))
.build();
assertThat(service.addColumn(addColumnParams), willBe((Object) null));
// Validate both columns added.
schema = service.activeSchema(System.currentTimeMillis());
assertNotNull(schema.table(TABLE_NAME).column(NEW_COLUMN_NAME));
assertNotNull(schema.table(TABLE_NAME).column(NEW_COLUMN_NAME_2));
// Drop multiple columns.
AlterTableDropColumnParams dropColumnParams = AlterTableDropColumnParams.builder()
.schemaName(SCHEMA_NAME)
.tableName(TABLE_NAME)
.columns(Set.of(NEW_COLUMN_NAME, NEW_COLUMN_NAME_2))
.build();
assertThat(service.dropColumn(dropColumnParams), willBe((Object) null));
// Validate both columns dropped.
schema = service.activeSchema(System.currentTimeMillis());
assertNull(schema.table(TABLE_NAME).column(NEW_COLUMN_NAME));
assertNull(schema.table(TABLE_NAME).column(NEW_COLUMN_NAME_2));
// Check dropping of non-existing column
dropColumnParams = AlterTableDropColumnParams.builder()
.schemaName(SCHEMA_NAME)
.tableName(TABLE_NAME)
.columns(Set.of(NEW_COLUMN_NAME, "VAL"))
.build();
assertThat(service.dropColumn(dropColumnParams), willThrow(ColumnNotFoundException.class));
// Validate no column dropped.
schema = service.activeSchema(System.currentTimeMillis());
assertNotNull(schema.table(TABLE_NAME).column("VAL"));
}
@Test
public void testDropTableWithIndex() throws InterruptedException {
CreateHashIndexParams params = CreateHashIndexParams.builder()
.indexName(INDEX_NAME)
.tableName(TABLE_NAME)
.columns(List.of("VAL"))
.build();
assertThat(service.createTable(simpleTable(TABLE_NAME)), willBe((Object) null));
assertThat(service.createIndex(params), willBe((Object) null));
long beforeDropTimestamp = System.currentTimeMillis();
Thread.sleep(5);
DropTableParams dropTableParams = DropTableParams.builder().schemaName("PUBLIC").tableName(TABLE_NAME).build();
assertThat(service.dropTable(dropTableParams), willBe((Object) null));
// Validate catalog version from the past.
SchemaDescriptor schema = service.schema(2);
assertNotNull(schema);
assertEquals(0, schema.id());
assertEquals(CatalogService.PUBLIC, schema.name());
assertEquals(2, schema.version());
assertSame(schema, service.activeSchema(beforeDropTimestamp));
assertSame(schema.table(TABLE_NAME), service.table(TABLE_NAME, beforeDropTimestamp));
assertSame(schema.table(TABLE_NAME), service.table(1, beforeDropTimestamp));
assertSame(schema.index(INDEX_NAME), service.index(INDEX_NAME, beforeDropTimestamp));
assertSame(schema.index(INDEX_NAME), service.index(2, beforeDropTimestamp));
// Validate actual catalog
schema = service.schema(3);
assertNotNull(schema);
assertEquals(0, schema.id());
assertEquals(CatalogService.PUBLIC, schema.name());
assertEquals(3, schema.version());
assertSame(schema, service.activeSchema(System.currentTimeMillis()));
assertNull(schema.table(TABLE_NAME));
assertNull(service.table(TABLE_NAME, System.currentTimeMillis()));
assertNull(service.table(1, System.currentTimeMillis()));
assertNull(schema.index(INDEX_NAME));
assertNull(service.index(INDEX_NAME, System.currentTimeMillis()));
assertNull(service.index(2, System.currentTimeMillis()));
}
@Test
public void testCreateHashIndex() {
assertThat(service.createTable(simpleTable(TABLE_NAME)), willBe((Object) null));
CreateHashIndexParams params = CreateHashIndexParams.builder()
.indexName(INDEX_NAME)
.tableName(TABLE_NAME)
.columns(List.of("VAL", "ID"))
.build();
assertThat(service.createIndex(params), willBe((Object) null));
// Validate catalog version from the past.
SchemaDescriptor schema = service.schema(1);
assertNotNull(schema);
assertNull(schema.index(INDEX_NAME));
assertNull(service.index(INDEX_NAME, 123L));
assertNull(service.index(2, 123L));
// Validate actual catalog
schema = service.schema(2);
assertNotNull(schema);
assertNull(service.index(1, System.currentTimeMillis()));
assertSame(schema.index(INDEX_NAME), service.index(INDEX_NAME, System.currentTimeMillis()));
assertSame(schema.index(INDEX_NAME), service.index(2, System.currentTimeMillis()));
// Validate newly created hash index
HashIndexDescriptor index = (HashIndexDescriptor) schema.index(INDEX_NAME);
assertEquals(2L, index.id());
assertEquals(INDEX_NAME, index.name());
assertEquals(schema.table(TABLE_NAME).id(), index.tableId());
assertEquals(List.of("VAL", "ID"), index.columns());
assertFalse(index.unique());
assertFalse(index.writeOnly());
}
@Test
public void testCreateSortedIndex() {
assertThat(service.createTable(simpleTable(TABLE_NAME)), willBe((Object) null));
CreateSortedIndexParams params = CreateSortedIndexParams.builder()
.indexName(INDEX_NAME)
.tableName(TABLE_NAME)
.unique()
.columns(List.of("VAL", "ID"))
.collations(List.of(ColumnCollation.DESC_NULLS_FIRST, ColumnCollation.ASC_NULLS_LAST))
.build();
assertThat(service.createIndex(params), willBe((Object) null));
// Validate catalog version from the past.
SchemaDescriptor schema = service.schema(1);
assertNotNull(schema);
assertNull(schema.index(INDEX_NAME));
assertNull(service.index(INDEX_NAME, 123L));
assertNull(service.index(2, 123L));
// Validate actual catalog
schema = service.schema(2);
assertNotNull(schema);
assertNull(service.index(1, System.currentTimeMillis()));
assertSame(schema.index(INDEX_NAME), service.index(INDEX_NAME, System.currentTimeMillis()));
assertSame(schema.index(INDEX_NAME), service.index(2, System.currentTimeMillis()));
// Validate newly created sorted index
SortedIndexDescriptor index = (SortedIndexDescriptor) schema.index(INDEX_NAME);
assertEquals(2L, index.id());
assertEquals(INDEX_NAME, index.name());
assertEquals(schema.table(TABLE_NAME).id(), index.tableId());
assertEquals("VAL", index.columns().get(0).name());
assertEquals("ID", index.columns().get(1).name());
assertEquals(ColumnCollation.DESC_NULLS_FIRST, index.columns().get(0).collation());
assertEquals(ColumnCollation.ASC_NULLS_LAST, index.columns().get(1).collation());
assertTrue(index.unique());
assertFalse(index.writeOnly());
}
@Test
public void testCreateIndexWithSameName() {
assertThat(service.createTable(simpleTable(TABLE_NAME)), willBe((Object) null));
CreateHashIndexParams params = CreateHashIndexParams.builder()
.indexName(INDEX_NAME)
.tableName(TABLE_NAME)
.columns(List.of("VAL"))
.build();
assertThat(service.createIndex(params), willBe((Object) null));
assertThat(service.createIndex(params), willThrow(IndexAlreadyExistsException.class));
}
@Test
public void operationWillBeRetriedFiniteAmountOfTimes() {
UpdateLog updateLogMock = Mockito.mock(UpdateLog.class);
ArgumentCaptor<OnUpdateHandler> updateHandlerCapture = ArgumentCaptor.forClass(OnUpdateHandler.class);
doNothing().when(updateLogMock).registerUpdateHandler(updateHandlerCapture.capture());
CatalogServiceImpl service = new CatalogServiceImpl(updateLogMock);
service.start();
when(updateLogMock.append(any())).thenAnswer(invocation -> {
// here we emulate concurrent updates. First of all, we return a future completed with "false"
// as if someone has concurrently appended an update. Besides, in order to unblock service and allow to
// make another attempt, we must notify service with the same version as in current attempt.
VersionedUpdate updateFromInvocation = invocation.getArgument(0, VersionedUpdate.class);
VersionedUpdate update = new VersionedUpdate(
updateFromInvocation.version(),
List.of(new ObjectIdGenUpdateEntry(1))
);
updateHandlerCapture.getValue().handle(update);
return completedFuture(false);
});
CompletableFuture<Void> createTableFut = service.createTable(simpleTable("T"));
assertThat(createTableFut, willThrow(IgniteInternalException.class, "Max retry limit exceeded"));
// retry limit is hardcoded at org.apache.ignite.internal.catalog.CatalogServiceImpl.MAX_RETRY_COUNT
Mockito.verify(updateLogMock, times(10)).append(any());
}
@Test
public void catalogServiceManagesUpdateLogLifecycle() throws Exception {
UpdateLog updateLogMock = Mockito.mock(UpdateLog.class);
CatalogServiceImpl service = new CatalogServiceImpl(updateLogMock);
service.start();
verify(updateLogMock).start();
service.stop();
verify(updateLogMock).stop();
}
@Test
public void testTableEvents() {
CreateTableParams createTableParams = CreateTableParams.builder()
.schemaName(SCHEMA_NAME)
.tableName(TABLE_NAME)
.zone(ZONE_NAME)
.columns(List.of(
ColumnParams.builder().name("key1").type(ColumnType.INT32).build(),
ColumnParams.builder().name("key2").type(ColumnType.INT32).build(),
ColumnParams.builder().name("val").type(ColumnType.INT32).nullable(true).build()
))
.primaryKeyColumns(List.of("key1", "key2"))
.colocationColumns(List.of("key2"))
.build();
DropTableParams dropTableparams = DropTableParams.builder().tableName(TABLE_NAME).build();
EventListener<CatalogEventParameters> eventListener = Mockito.mock(EventListener.class);
when(eventListener.notify(any(), any())).thenReturn(completedFuture(false));
service.listen(CatalogEvent.TABLE_CREATE, eventListener);
service.listen(CatalogEvent.TABLE_DROP, eventListener);
assertThat(service.createTable(createTableParams), willBe((Object) null));
verify(eventListener).notify(any(CreateTableEventParameters.class), ArgumentMatchers.isNull());
assertThat(service.dropTable(dropTableparams), willBe((Object) null));
verify(eventListener).notify(any(DropTableEventParameters.class), ArgumentMatchers.isNull());
verifyNoMoreInteractions(eventListener);
}
@Test
public void testCreateIndexEvents() {
CreateTableParams createTableParams = CreateTableParams.builder()
.schemaName(CatalogService.PUBLIC)
.tableName(TABLE_NAME)
.zone("ZONE")
.columns(List.of(
ColumnParams.builder().name("key1").type(ColumnType.INT32).build(),
ColumnParams.builder().name("key2").type(ColumnType.INT32).build(),
ColumnParams.builder().name("val").type(ColumnType.INT32).nullable(true).build()
))
.primaryKeyColumns(List.of("key1", "key2"))
.colocationColumns(List.of("key2"))
.build();
DropTableParams dropTableparams = DropTableParams.builder().tableName(TABLE_NAME).build();
CreateHashIndexParams createIndexParams = CreateHashIndexParams.builder()
.schemaName(CatalogService.PUBLIC)
.indexName(INDEX_NAME)
.tableName(TABLE_NAME)
.columns(List.of("key2"))
.build();
DropIndexParams dropIndexParams = DropIndexParams.builder().indexName(INDEX_NAME).build();
EventListener<CatalogEventParameters> eventListener = Mockito.mock(EventListener.class);
when(eventListener.notify(any(), any())).thenReturn(completedFuture(false));
service.listen(CatalogEvent.INDEX_CREATE, eventListener);
service.listen(CatalogEvent.INDEX_DROP, eventListener);
// Try to create index without table.
assertThat(service.createIndex(createIndexParams), willThrow(TableNotFoundException.class));
verifyNoInteractions(eventListener);
// Create table.
assertThat(service.createTable(createTableParams), willBe((Object) null));
// Create index.
assertThat(service.createIndex(createIndexParams), willBe((Object) null));
verify(eventListener).notify(any(CreateIndexEventParameters.class), ArgumentMatchers.isNull());
// Drop index.
assertThat(service.dropIndex(dropIndexParams), willBe((Object) null));
verify(eventListener).notify(any(DropIndexEventParameters.class), ArgumentMatchers.isNull());
// Drop table.
assertThat(service.dropTable(dropTableparams), willBe((Object) null));
// Try drop index once again.
assertThat(service.dropIndex(dropIndexParams), willThrow(IndexNotFoundException.class));
verifyNoMoreInteractions(eventListener);
}
@Test
public void testColumnEvents() {
AlterTableAddColumnParams addColumnParams = AlterTableAddColumnParams.builder()
.schemaName(SCHEMA_NAME)
.tableName(TABLE_NAME)
.columns(List.of(ColumnParams.builder()
.name(NEW_COLUMN_NAME)
.type(ColumnType.INT32)
.defaultValue(DefaultValue.constant(42))
.nullable(true)
.build()
))
.build();
AlterTableDropColumnParams dropColumnParams = AlterTableDropColumnParams.builder()
.schemaName(SCHEMA_NAME)
.tableName(TABLE_NAME)
.columns(Set.of(NEW_COLUMN_NAME))
.build();
EventListener<CatalogEventParameters> eventListener = Mockito.mock(EventListener.class);
when(eventListener.notify(any(), any())).thenReturn(completedFuture(false));
service.listen(CatalogEvent.TABLE_ALTER, eventListener);
// Try to add column without table.
assertThat(service.addColumn(addColumnParams), willThrow(TableNotFoundException.class));
verifyNoInteractions(eventListener);
// Create table.
assertThat(service.createTable(simpleTable(TABLE_NAME)), willBe((Object) null));
// Add column.
assertThat(service.addColumn(addColumnParams), willBe((Object) null));
verify(eventListener).notify(any(AddColumnEventParameters.class), ArgumentMatchers.isNull());
// Drop column.
assertThat(service.dropColumn(dropColumnParams), willBe((Object) null));
verify(eventListener).notify(any(DropColumnEventParameters.class), ArgumentMatchers.isNull());
// Try drop column once again.
assertThat(service.dropColumn(dropColumnParams), willThrow(ColumnNotFoundException.class));
verifyNoMoreInteractions(eventListener);
}
private static CreateTableParams simpleTable(String name) {
return CreateTableParams.builder()
.schemaName(SCHEMA_NAME)
.tableName(name)
.zone(ZONE_NAME)
.columns(List.of(
ColumnParams.builder().name("ID").type(ColumnType.INT32).build(),
ColumnParams.builder().name("VAL").type(ColumnType.INT32).nullable(true).build()
))
.primaryKeyColumns(List.of("ID"))
.build();
}
private static CreateSortedIndexParams simpleIndex(String indexName, String tableName) {
return CreateSortedIndexParams.builder()
.indexName(indexName)
.tableName(tableName)
.unique()
.columns(List.of("VAL"))
.collations(List.of(ColumnCollation.ASC_NULLS_LAST))
.build();
}
}