blob: 9f48989acd4321233c258fd82aaf61acb91a7eba [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 org.apache.ignite.internal.lang.IgniteStringFormatter.format;
import static org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCause;
import static org.apache.ignite.sql.ColumnType.INT32;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import java.util.List;
import java.util.stream.Stream;
import org.apache.ignite.internal.catalog.Catalog;
import org.apache.ignite.internal.catalog.CatalogCommand;
import org.apache.ignite.internal.catalog.CatalogValidationException;
import org.apache.ignite.internal.catalog.descriptors.CatalogColumnCollation;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
/**
* Tests to verify validation of {@link CreateTableCommand}.
*/
@SuppressWarnings({"DataFlowIssue", "ThrowableNotThrown"})
public class CreateTableCommandValidationTest extends AbstractCommandValidationTest {
@ParameterizedTest(name = "[{index}] ''{argumentsWithNames}''")
@MethodSource("nullAndBlankStrings")
void schemaNameMustNotBeNullOrBlank(String name) {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
builder = fillProperties(builder);
builder.schemaName(name);
assertThrowsWithCause(
builder::build,
CatalogValidationException.class,
"Name of the schema can't be null or blank"
);
}
@ParameterizedTest(name = "[{index}] ''{argumentsWithNames}''")
@MethodSource("nullAndBlankStrings")
void tableNameMustNotBeNullOrBlank(String name) {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
builder = fillProperties(builder);
builder.tableName(name);
assertThrowsWithCause(
builder::build,
CatalogValidationException.class,
"Name of the table can't be null or blank"
);
}
@ParameterizedTest(name = "[{index}] {argumentsWithNames}")
@MethodSource("nullAndEmptyLists")
void tableShouldHaveAtLeastOneColumn(List<ColumnParams> columns) {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
builder = fillProperties(builder);
builder.columns(columns);
assertThrowsWithCause(
builder::build,
CatalogValidationException.class,
"Table should have at least one column"
);
}
@Test
void functionalDefaultNotSupportsForNonPkColumns() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
builder = fillProperties(builder);
builder.columns(List.of(
ColumnParams.builder().name("ID").type(INT32).defaultValue(DefaultValue.functionCall("function")).build(),
ColumnParams.builder().name("C").type(INT32).defaultValue(DefaultValue.constant(1)).build()
));
assertThrowsWithCause(
builder::build,
CatalogValidationException.class,
"Functional defaults are not supported for non-primary key columns [col=ID]."
);
}
@Test
void functionalDefaultSupportsForPkColumns() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
builder = fillProperties(builder);
builder.columns(List.of(
ColumnParams.builder().name("C").type(INT32).defaultValue(DefaultValue.functionCall("function")).build(),
ColumnParams.builder().name("D").type(INT32).defaultValue(DefaultValue.constant(1)).build()
)).primaryKey(primaryKey("C", "D"));
builder.build();
}
@Test
void columnShouldNotHaveDuplicates() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
ColumnParams column = ColumnParams.builder()
.name("C")
.type(INT32)
.build();
builder = fillProperties(builder).columns(List.of(column, column));
assertThrowsWithCause(
builder::build,
CatalogValidationException.class,
"Column with name 'C' specified more than once"
);
}
@Test
void primaryKeyColumnsShouldNotHaveDuplicates() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
builder = fillProperties(builder)
.primaryKey(primaryKey("C", "C"));
assertThrowsWithCause(
builder::build,
CatalogValidationException.class,
"PK column 'C' specified more that once."
);
}
@Test
void primaryKeyColumnsShouldNotContainNullable() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
builder = fillProperties(builder)
.columns(List.of(
ColumnParams.builder().name("C").type(INT32).nullable(true).build(),
ColumnParams.builder().name("D").type(INT32).build()))
.primaryKey(primaryKey("D", "C"));
assertThrowsWithCause(
builder::build,
CatalogValidationException.class,
"Primary key cannot contain nullable column [col=C]."
);
}
@Test
void primaryKeyColumnsCanContainOnlyTableColumns() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
builder = fillProperties(builder)
.columns(List.of(
ColumnParams.builder().name("C").type(INT32).build(),
ColumnParams.builder().name("D").type(INT32).build()))
.primaryKey(primaryKey("Z", "D", "E"));
assertThrowsWithCause(
builder::build,
CatalogValidationException.class,
"Primary key constraint contains undefined columns: [cols=[Z, E]]."
);
}
@ParameterizedTest(name = "[{index}] {arguments}")
@MethodSource("nullAndEmptyPrimaryKeys")
void tableShouldHaveAtLeastOnePrimaryKeyColumn(TablePrimaryKey pk) {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
builder = fillProperties(builder).primaryKey(pk);
assertThrowsWithCause(
builder::build,
CatalogValidationException.class,
"Table should have primary key"
);
}
private static Stream<TablePrimaryKey> nullAndEmptyPrimaryKeys() {
return Stream.of(
null,
TableSortedPrimaryKey.builder().build(),
TableHashPrimaryKey.builder().build()
);
}
@ParameterizedTest
@MethodSource("notMatchingNumberOfColumnsAndCollations")
void tableWithSortedPrimaryKeyWithNotMatchColumnsCollations(List<String> columns, List<CatalogColumnCollation> collations) {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
TableSortedPrimaryKey primaryKey = TableSortedPrimaryKey.builder()
.columns(columns)
.collations(collations)
.build();
builder = fillProperties(builder)
.columns(List.of(
ColumnParams.builder()
.name("C1")
.type(INT32)
.build(),
ColumnParams.builder()
.name("C2")
.type(INT32)
.build()
))
.colocationColumns(primaryKey.columns())
.primaryKey(primaryKey);
assertThrowsWithCause(
builder::build,
CatalogValidationException.class,
"Number of collations does not match"
);
}
private static Stream<Arguments> notMatchingNumberOfColumnsAndCollations() {
return Stream.of(
Arguments.of(List.of("C1"), List.of()),
Arguments.of(List.of("C1"), List.of(CatalogColumnCollation.ASC_NULLS_LAST, CatalogColumnCollation.ASC_NULLS_LAST)),
Arguments.of(List.of("C1", "C2"), List.of(CatalogColumnCollation.ASC_NULLS_LAST))
);
}
@Test
void colocationColumnsCouldNotBeEmpty() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
builder = fillProperties(builder)
.colocationColumns(List.of());
assertThrowsWithCause(
builder::build,
CatalogValidationException.class,
"Colocation columns could not be empty"
);
}
@Test
void colocationColumnShouldNotHaveDuplicates() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
builder = fillProperties(builder)
.colocationColumns(List.of("C", "C"));
assertThrowsWithCause(
builder::build,
CatalogValidationException.class,
"Colocation column 'C' specified more that once"
);
}
@Test
void colocationColumnShouldBePresentedInColumnsList() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
ColumnParams c1 = ColumnParams.builder()
.name("C1")
.type(INT32)
.build();
ColumnParams c2 = ColumnParams.builder()
.name("C2")
.type(INT32)
.build();
builder = fillProperties(builder)
.columns(List.of(c1, c2))
.primaryKey(primaryKey("C1"))
.colocationColumns(List.of("C2"));
assertThrowsWithCause(
builder::build,
CatalogValidationException.class,
"Colocation column 'C2' is not part of PK"
);
}
@Test
void exceptionIsThrownIfZoneNeitherSpecifiedExplicitlyNorDefaultWasSet() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
Catalog catalog = emptyCatalog();
CatalogCommand command = fillProperties(builder).zone(null).build();
assertThrowsWithCause(
() -> command.get(catalog),
CatalogValidationException.class,
"The zone is not specified. Please specify zone explicitly or set default one."
);
}
private static CreateTableCommandBuilder fillProperties(CreateTableCommandBuilder builder) {
return builder
.schemaName(SCHEMA_NAME)
.tableName("TEST")
.zone(ZONE_NAME)
.columns(List.of(
ColumnParams.builder()
.name("C")
.type(INT32)
.build()
))
.primaryKey(primaryKey("C"))
.colocationColumns(List.of("C"));
}
@Test
void exceptionIsThrownIfSchemaNotExists() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
Catalog catalog = catalogWithDefaultZone();
CatalogCommand command = fillProperties(builder).schemaName(SCHEMA_NAME + "_UNK").build();
assertThrowsWithCause(
() -> command.get(catalog),
CatalogValidationException.class,
"Schema with name 'PUBLIC_UNK' not found"
);
}
@ParameterizedTest
@MethodSource("reservedSchemaNames")
void exceptionIsThrownIfSchemaIsReserved(String schema) {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
builder = fillProperties(builder).schemaName(schema);
assertThrowsWithCause(
builder::build,
CatalogValidationException.class,
"Operations with reserved schemas are not allowed"
);
}
@Test
void exceptionIsThrownIfZoneNotExists() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
Catalog catalog = catalogWithDefaultZone();
CatalogCommand command = fillProperties(builder).zone(ZONE_NAME + "_UNK").build();
assertThrowsWithCause(
() -> command.get(catalog),
CatalogValidationException.class,
"Distribution zone with name 'Default_UNK' not found"
);
}
@Test
void exceptionIsThrownIfTableWithGivenNameAlreadyExists() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
Catalog catalog = catalogWithTable("TEST");
CatalogCommand command = fillProperties(builder).tableName("TEST").build();
assertThrowsWithCause(
() -> command.get(catalog),
CatalogValidationException.class,
"Table with name 'PUBLIC.TEST' already exists"
);
}
@Test
void exceptionIsThrownIfIndexWithGivenNameAlreadyExists() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
Catalog catalog = catalogWithIndex("TEST_IDX");
CatalogCommand command = fillProperties(builder).tableName("TEST_IDX").build();
assertThrowsWithCause(
() -> command.get(catalog),
CatalogValidationException.class,
"Index with name 'PUBLIC.TEST_IDX' already exists"
);
}
@Test
void exceptionIsThrownIfTableWithNameSimilarToAutogeneratedPkNameAlreadyExists() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
Catalog catalog = catalogWithTable("TEST_PK");
CatalogCommand command = fillProperties(builder).tableName("TEST").build();
assertThrowsWithCause(
() -> command.get(catalog),
CatalogValidationException.class,
"Table with name 'PUBLIC.TEST_PK' already exists"
);
}
@Test
void exceptionIsThrownIfIndexWithNameSimilarToAutogeneratedPkNameAlreadyExists() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
Catalog catalog = catalogWithIndex("FOO_PK");
CatalogCommand command = fillProperties(builder).tableName("FOO").build();
assertThrowsWithCause(
() -> command.get(catalog),
CatalogValidationException.class,
"Index with name 'PUBLIC.FOO_PK' already exists"
);
}
@Test
void exceptionIsThrownIfZoneDoesNotContainTableStorageProfile() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
String zoneName = "testZone";
Catalog catalog = catalog(createZoneCommand(zoneName, List.of("profile1, profile2")));
String tableProfile = "profile3";
CatalogCommand command = fillProperties(builder).zone(zoneName).storageProfile(tableProfile).build();
assertThrowsWithCause(
() -> command.get(catalog),
CatalogValidationException.class,
format("Zone with name '{}' does not contain table's storage profile [storageProfile='{}']", zoneName, tableProfile)
);
assertDoesNotThrow(() -> {
// Let's check the success case.
Catalog newCatalog = catalog(createZoneCommand(zoneName, List.of("profile1", "profile2", tableProfile)));
command.get(newCatalog);
});
}
}