blob: f530bd7a0dc71622e49872d5b9a00882b1d41da8 [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.sql.engine.sql;
import static java.util.Collections.singleton;
import static org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.calcite.schema.ColumnStrategy;
import org.apache.calcite.sql.SqlBasicCall;
import org.apache.calcite.sql.SqlBinaryStringLiteral;
import org.apache.calcite.sql.SqlCharStringLiteral;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNumericLiteral;
import org.apache.calcite.sql.SqlUnknownLiteral;
import org.apache.calcite.sql.ddl.SqlColumnDeclaration;
import org.apache.ignite.lang.ErrorGroups.Sql;
import org.hamcrest.CustomMatcher;
import org.hamcrest.Matcher;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/**
* Test suite to verify parsing of the DDL command.
*/
public class SqlDdlParserTest extends AbstractParserTest {
/**
* Very simple case where only table name and a few columns are presented.
*/
@Test
public void createTableSimpleCase() {
String query = "create table my_table(id int, val varchar)";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateTable.class));
IgniteSqlCreateTable createTable = (IgniteSqlCreateTable) node;
assertThat(createTable.name().names, is(List.of("MY_TABLE")));
assertThat(createTable.ifNotExists, is(false));
assertThat(createTable.columnList(), hasItem(columnWithName("ID")));
assertThat(createTable.columnList(), hasItem(columnWithName("VAL")));
expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" ("
+ "\"ID\" INTEGER, "
+ "\"VAL\" VARCHAR)"
);
}
/**
* Parsing of CREATE TABLE with function identifier as a default expression.
*/
@Test
public void createTableAutogenFuncDefault() {
String query = "create table my_table(id varchar default gen_random_uuid primary key, val varchar)";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateTable.class));
IgniteSqlCreateTable createTable = (IgniteSqlCreateTable) node;
assertThat(createTable.name().names, is(List.of("MY_TABLE")));
assertThat(createTable.columnList(), hasItem(ofTypeMatching(
"Column with function's identifier as default",
SqlColumnDeclaration.class,
col -> "ID".equals(col.name.getSimple())
&& col.expression instanceof SqlIdentifier
&& "GEN_RANDOM_UUID".equals(((SqlIdentifier) col.expression).getSimple())
)));
assertThat(createTable.columnList(), hasItem(ofTypeMatching(
"PK constraint with name \"ID\"", IgniteSqlPrimaryKeyConstraint.class,
constraint -> hasItem(ofTypeMatching("identifier \"ID\"", SqlIdentifier.class, id -> "ID".equals(id.names.get(0))))
.matches(constraint.getOperandList().get(1))
&& constraint.getOperandList().get(0) == null
&& constraint.getIndexType() == IgniteSqlPrimaryKeyIndexType.IMPLICIT_HASH
&& constraint.isA(singleton(SqlKind.PRIMARY_KEY)))));
expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" ("
+ "PRIMARY KEY (\"ID\"), "
+ "\"ID\" VARCHAR DEFAULT (\"GEN_RANDOM_UUID\"), "
+ "\"VAL\" VARCHAR)"
);
}
/**
* Parsing of CREATE TABLE with a literal as a default expression.
*/
@Test
public void createTableWithDefaultLiteral() {
String query = "CREATE TABLE my_table("
+ "id BIGINT DEFAULT 1, "
+ "valdate DATE DEFAULT DATE '2001-12-21',"
+ "valdate2 DATE DEFAULT '2001-12-21',"
+ "valtime TIME DEFAULT TIME '11:22:33.444',"
+ "valtime2 TIME DEFAULT '11:22:33.444',"
+ "valts TIMESTAMP DEFAULT TIMESTAMP '2001-12-21 11:22:33.444',"
+ "valts2 TIMESTAMP DEFAULT '2001-12-21 11:22:33.444',"
+ "valbin VARBINARY DEFAULT x'ff',"
+ "valstr VARCHAR DEFAULT 'string'"
+ ")";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateTable.class));
IgniteSqlCreateTable createTable = (IgniteSqlCreateTable) node;
assertThat(createTable.name().names, is(List.of("MY_TABLE")));
assertThat(createTable.columnList(), hasColumnWithDefault("ID", SqlNumericLiteral.class, "1"));
assertThat(createTable.columnList(), hasColumnWithDefault("VALSTR", SqlCharStringLiteral.class, "string"));
assertThat(createTable.columnList(), hasColumnWithDefault("VALBIN", SqlBinaryStringLiteral.class, "11111111"));
assertThat(createTable.columnList(), hasColumnWithDefault("VALDATE", SqlUnknownLiteral.class, "2001-12-21"));
assertThat(createTable.columnList(), hasColumnWithDefault("VALTIME", SqlUnknownLiteral.class, "11:22:33.444"));
assertThat(createTable.columnList(), hasColumnWithDefault("VALTS", SqlUnknownLiteral.class, "2001-12-21 11:22:33.444"));
assertThat(createTable.columnList(), hasColumnWithDefault("VALDATE2", SqlCharStringLiteral.class, "2001-12-21"));
assertThat(createTable.columnList(), hasColumnWithDefault("VALTIME2", SqlCharStringLiteral.class, "11:22:33.444"));
assertThat(createTable.columnList(), hasColumnWithDefault("VALTS2", SqlCharStringLiteral.class, "2001-12-21 11:22:33.444"));
expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" "
+ "(\"ID\" BIGINT DEFAULT (1), "
+ "\"VALDATE\" DATE DEFAULT (DATE '2001-12-21'), "
+ "\"VALDATE2\" DATE DEFAULT ('2001-12-21'), "
+ "\"VALTIME\" TIME DEFAULT (TIME '11:22:33.444'), "
+ "\"VALTIME2\" TIME DEFAULT ('11:22:33.444'), "
+ "\"VALTS\" TIMESTAMP DEFAULT (TIMESTAMP '2001-12-21 11:22:33.444'), "
+ "\"VALTS2\" TIMESTAMP DEFAULT ('2001-12-21 11:22:33.444'), "
+ "\"VALBIN\" VARBINARY DEFAULT (X'FF'), "
+ "\"VALSTR\" VARCHAR DEFAULT ('string'))"
);
}
private Matcher<Iterable<? super SqlColumnDeclaration>> hasColumnWithDefault(
String columnName,
Class<? extends SqlLiteral> literalType,
String literalValue
) {
return hasItem(ofTypeMatching(
"Column with literal as default: columnName=" + columnName,
SqlColumnDeclaration.class,
col -> columnName.equals(col.name.getSimple())
&& literalType.isInstance(col.expression)
&& literalValue.equals(((SqlLiteral) col.expression).toValue())
));
}
/**
* Parsing of CREATE TABLE statement with quoted identifiers.
*/
@Test
public void createTableQuotedIdentifiers() {
String query = "create table \"My_Table\"(\"Id\" int, \"Val\" varchar)";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateTable.class));
IgniteSqlCreateTable createTable = (IgniteSqlCreateTable) node;
assertThat(createTable.name().names, is(List.of("My_Table")));
assertThat(createTable.ifNotExists, is(false));
assertThat(createTable.columnList(), hasItem(columnWithName("Id")));
assertThat(createTable.columnList(), hasItem(columnWithName("Val")));
expectUnparsed(node, "CREATE TABLE \"My_Table\" "
+ "(\"Id\" INTEGER, "
+ "\"Val\" VARCHAR)"
);
}
/**
* Parsing of CREATE TABLE statement with IF NOT EXISTS.
*/
@Test
public void createTableIfNotExists() {
String query = "create table if not exists my_table(id int, val varchar)";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateTable.class));
IgniteSqlCreateTable createTable = (IgniteSqlCreateTable) node;
assertThat(createTable.name().names, is(List.of("MY_TABLE")));
assertThat(createTable.ifNotExists, is(true));
assertThat(createTable.columnList(), hasItem(columnWithName("ID")));
assertThat(createTable.columnList(), hasItem(columnWithName("VAL")));
expectUnparsed(node, "CREATE TABLE IF NOT EXISTS \"MY_TABLE\" ("
+ "\"ID\" INTEGER, \"VAL\" VARCHAR)"
);
}
/**
* Parsing of CREATE TABLE with specified PK constraint where constraint is a shortcut within a column definition.
*/
@Test
public void createTableWithPkCase1() {
String query = "create table my_table(id int primary key, val varchar)";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateTable.class));
IgniteSqlCreateTable createTable = (IgniteSqlCreateTable) node;
assertThat(createTable.name().names, is(List.of("MY_TABLE")));
assertThat(createTable.ifNotExists, is(false));
assertThat(createTable.columnList(), hasItem(ofTypeMatching(
"PK constraint with name \"ID\"", IgniteSqlPrimaryKeyConstraint.class,
constraint -> hasItem(ofTypeMatching("identifier \"ID\"", SqlIdentifier.class, id -> "ID".equals(id.names.get(0))))
.matches(constraint.getOperandList().get(1))
&& constraint.getOperandList().get(0) == null
&& constraint.getIndexType() == IgniteSqlPrimaryKeyIndexType.IMPLICIT_HASH
&& constraint.isA(singleton(SqlKind.PRIMARY_KEY)))));
expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" ("
+ "PRIMARY KEY (\"ID\"), "
+ "\"ID\" INTEGER, "
+ "\"VAL\" VARCHAR)"
);
}
/**
* Parsing of CREATE TABLE with specified PK constraint where constraint is set explicitly and has no name.
*/
@Test
public void createTableWithPkCase2() {
String query = "create table my_table(id int, val varchar, primary key(id))";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateTable.class));
IgniteSqlCreateTable createTable = (IgniteSqlCreateTable) node;
assertThat(createTable.name().names, is(List.of("MY_TABLE")));
assertThat(createTable.ifNotExists, is(false));
assertThat(createTable.columnList(), hasItem(ofTypeMatching(
"PK constraint without name containing column \"ID\"", IgniteSqlPrimaryKeyConstraint.class,
constraint -> hasItem(ofTypeMatching("identifier \"ID\"", SqlIdentifier.class, id -> "ID".equals(id.names.get(0))))
.matches(constraint.getOperandList().get(1))
&& constraint.getOperandList().get(0) == null
&& constraint.getIndexType() == IgniteSqlPrimaryKeyIndexType.IMPLICIT_HASH
&& constraint.isA(singleton(SqlKind.PRIMARY_KEY)))));
expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" "
+ "(\"ID\" INTEGER, "
+ "\"VAL\" VARCHAR, "
+ "PRIMARY KEY (\"ID\"))"
);
}
/**
* Parsing of CREATE TABLE with specified PK constraint where constraint is set explicitly and has a name.
*/
@Test
public void createTableWithPkCase3() {
String query = "create table my_table(id int, val varchar, constraint pk_key primary key(id))";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateTable.class));
IgniteSqlCreateTable createTable = (IgniteSqlCreateTable) node;
assertThat(createTable.name().names, is(List.of("MY_TABLE")));
assertThat(createTable.ifNotExists, is(false));
assertThat(createTable.columnList(), hasItem(ofTypeMatching(
"PK constraint with name \"PK_KEY\" containing column \"ID\"", IgniteSqlPrimaryKeyConstraint.class,
constraint -> hasItem(ofTypeMatching("identifier \"ID\"", SqlIdentifier.class, id -> "ID".equals(id.names.get(0))))
.matches(constraint.getOperandList().get(1))
&& "PK_KEY".equals(((SqlIdentifier) constraint.getOperandList().get(0)).names.get(0))
&& constraint.getIndexType() == IgniteSqlPrimaryKeyIndexType.IMPLICIT_HASH
&& constraint.isA(singleton(SqlKind.PRIMARY_KEY)))));
expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" ("
+ "\"ID\" INTEGER, "
+ "\"VAL\" VARCHAR, "
+ "CONSTRAINT \"PK_KEY\" PRIMARY KEY (\"ID\"))"
);
}
/**
* Parsing of CREATE TABLE with specified PK constraint where constraint consists of several columns.
*/
@Test
public void createTableWithPkCase4() {
String query = "create table my_table(id1 int, id2 int, val varchar, primary key(id1, id2))";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateTable.class));
IgniteSqlCreateTable createTable = (IgniteSqlCreateTable) node;
assertThat(createTable.name().names, is(List.of("MY_TABLE")));
assertThat(createTable.ifNotExists, is(false));
assertThat(createTable.columnList(), hasItem(ofTypeMatching(
"PK constraint with two columns", IgniteSqlPrimaryKeyConstraint.class,
constraint -> hasItem(ofTypeMatching("identifier \"ID1\"", SqlIdentifier.class, id -> "ID1".equals(id.names.get(0))))
.matches(constraint.getOperandList().get(1))
&& hasItem(ofTypeMatching("identifier \"ID2\"", SqlIdentifier.class, id -> "ID2".equals(id.names.get(0))))
.matches(constraint.getOperandList().get(1))
&& constraint.getIndexType() == IgniteSqlPrimaryKeyIndexType.IMPLICIT_HASH
&& constraint.getOperandList().get(0) == null
&& constraint.isA(singleton(SqlKind.PRIMARY_KEY)))));
expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" ("
+ "\"ID1\" INTEGER, \"ID2\" INTEGER, "
+ "\"VAL\" VARCHAR, "
+ "PRIMARY KEY (\"ID1\", \"ID2\"))"
);
}
/**
* Parsing of CREATE TABLE with primary key index type SORTED.
*/
@Test
public void createTableWithSortedPk() {
String query = "create table my_table(id1 int, id2 int, val varchar, primary key using sorted (id1 ASC, id2 DESC))";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateTable.class));
IgniteSqlCreateTable createTable = (IgniteSqlCreateTable) node;
assertThat(createTable.name().names, is(List.of("MY_TABLE")));
assertThat(createTable.ifNotExists, is(false));
assertThat(createTable.columnList(), hasItem(ofTypeMatching(
"PK constraint without name containing column \"ID1\"", IgniteSqlPrimaryKeyConstraint.class,
constraint -> hasItem(ofTypeMatching("identifier \"ID1\"", SqlIdentifier.class, id -> "ID1".equals(id.names.get(0))))
.matches(constraint.getOperandList().get(1))
&& constraint.getIndexType() == IgniteSqlPrimaryKeyIndexType.SORTED
&& constraint.getOperandList().get(0) == null
&& constraint.isA(singleton(SqlKind.PRIMARY_KEY)))));
expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" "
+ "(\"ID1\" INTEGER, "
+ "\"ID2\" INTEGER, "
+ "\"VAL\" VARCHAR, "
+ "PRIMARY KEY USING SORTED (\"ID1\", \"ID2\" DESC))"
);
}
/**
* Parsing of CREATE TABLE with primary key index type HASH.
*/
@Test
public void createTableWithHashPk() {
String query = "create table my_table(id int, val varchar, primary key using hash (id))";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateTable.class));
IgniteSqlCreateTable createTable = (IgniteSqlCreateTable) node;
assertThat(createTable.name().names, is(List.of("MY_TABLE")));
assertThat(createTable.ifNotExists, is(false));
assertThat(createTable.columnList(), hasItem(ofTypeMatching(
"PK constraint without name containing column \"ID\"", IgniteSqlPrimaryKeyConstraint.class,
constraint -> hasItem(ofTypeMatching("identifier \"ID\"", SqlIdentifier.class, id -> "ID".equals(id.names.get(0))))
.matches(constraint.getOperandList().get(1))
&& constraint.getOperandList().get(0) == null
&& constraint.getIndexType() == IgniteSqlPrimaryKeyIndexType.HASH
&& constraint.isA(singleton(SqlKind.PRIMARY_KEY)))));
expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" "
+ "(\"ID\" INTEGER, "
+ "\"VAL\" VARCHAR, "
+ "PRIMARY KEY USING HASH (\"ID\"))"
);
assertThrowsSqlException(
Sql.STMT_PARSE_ERR,
"Encountered \"asc\"",
() -> parse("create table my_table(id int, val varchar, primary key using hash (id asc))"));
}
/**
* Parsing of CREATE TABLE with primary key index of unsupported type should return an error.
*/
@Test
public void createTableWithSomeOtherPkFails() {
assertThrowsSqlException(
Sql.STMT_PARSE_ERR,
"Encountered \"using\"",
() -> parse("create table my_table(id int, val varchar, primary key using bad (id))"));
}
/**
* Parsing of CREATE TABLE with specified colocation columns.
*/
@Test
public void createTableWithColocationBy() {
IgniteSqlCreateTable createTable;
createTable = parseCreateTable(
"CREATE TABLE MY_TABLE(ID0 INT, ID1 INT, ID2 INT, VAL INT, PRIMARY KEY (ID0, ID1, ID2))"
);
assertNull(createTable.colocationColumns());
createTable = parseCreateTable(
"CREATE TABLE MY_TABLE(ID0 INT, ID1 INT, ID2 INT, VAL INT, PRIMARY KEY (ID0, ID1, ID2)) COLOCATE BY (ID2, ID1)"
);
assertThat(
createTable.colocationColumns().getList().stream()
.map(SqlIdentifier.class::cast)
.map(SqlIdentifier::getSimple)
.collect(Collectors.toList()),
equalTo(List.of("ID2", "ID1"))
);
expectUnparsed(createTable, "CREATE TABLE \"MY_TABLE\" ("
+ "\"ID0\" INTEGER, \"ID1\" INTEGER, "
+ "\"ID2\" INTEGER, \"VAL\" INTEGER, PRIMARY KEY (\"ID0\", \"ID1\", \"ID2\")"
+ ") COLOCATE BY (\"ID2\", \"ID1\")"
);
createTable = parseCreateTable(
"CREATE TABLE MY_TABLE(ID0 INT, ID1 INT, ID2 INT, VAL INT, PRIMARY KEY (ID0, ID1, ID2)) COLOCATE (ID0)"
);
assertThat(
createTable.colocationColumns().getList().stream()
.map(SqlIdentifier.class::cast)
.map(SqlIdentifier::getSimple)
.collect(Collectors.toList()),
equalTo(List.of("ID0"))
);
// Check uparse 'COLOCATE' and 'WITH' together.
createTable = parseCreateTable(
"CREATE TABLE MY_TABLE(ID0 INT, ID1 INT, ID2 INT, VAL INT, PRIMARY KEY (ID0, ID1, ID2)) COLOCATE (ID0) "
+ "with "
+ "replicas=2, "
+ "partitions=3"
);
expectUnparsed(createTable, "CREATE TABLE \"MY_TABLE\" ("
+ "\"ID0\" INTEGER, \"ID1\" INTEGER, "
+ "\"ID2\" INTEGER, \"VAL\" INTEGER, PRIMARY KEY (\"ID0\", \"ID1\", \"ID2\")"
+ ") COLOCATE BY (\"ID0\") WITH \"REPLICAS\" = 2, \"PARTITIONS\" = 3"
);
}
@Test
public void createTableWithOptions() {
String query = "create table my_table(id int) with"
+ " replicas=2,"
+ " partitions=3,"
+ " primary_zone='zone123'";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateTable.class));
IgniteSqlCreateTable createTable = (IgniteSqlCreateTable) node;
assertThatOptionPresent(createTable.createOptionList().getList(), "REPLICAS", 2);
assertThatOptionPresent(createTable.createOptionList().getList(), "PARTITIONS", 3);
assertThatOptionPresent(createTable.createOptionList().getList(), "PRIMARY_ZONE", "zone123");
expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" ("
+ "\"ID\" INTEGER"
+ ") WITH \"REPLICAS\" = 2, \"PARTITIONS\" = 3, \"PRIMARY_ZONE\" = 'zone123'"
);
}
@Test
public void createTableWithIdentifierZone() {
String sqlQuery = "create table my_table(id int) with primary_zone=zone123";
SqlNode node = parse(sqlQuery);
assertThat(node, instanceOf(IgniteSqlCreateTable.class));
IgniteSqlCreateTable createTable = (IgniteSqlCreateTable) node;
assertThatOptionPresent(createTable.createOptionList().getList(), "PRIMARY_ZONE", "ZONE123");
expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" (\"ID\" INTEGER) WITH \"PRIMARY_ZONE\" = \"ZONE123\"");
}
@Test
public void createTableWithLiteralZone() {
String sqlQuery = "create table my_table(id int) with primary_zone='zone123'";
SqlNode node = parse(sqlQuery);
assertThat(node, instanceOf(IgniteSqlCreateTable.class));
IgniteSqlCreateTable createTable = (IgniteSqlCreateTable) node;
assertThatOptionPresent(createTable.createOptionList().getList(), "PRIMARY_ZONE", "zone123");
expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" (\"ID\" INTEGER) WITH \"PRIMARY_ZONE\" = 'zone123'");
}
@Test
public void createIndexSimpleCase() {
var query = "create index my_index on my_table (col)";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateIndex.class));
var createIndex = (IgniteSqlCreateIndex) node;
assertThat(createIndex.indexName().getSimple(), is("MY_INDEX"));
assertThat(createIndex.tableName().names, is(List.of("MY_TABLE")));
assertThat(createIndex.ifNotExists, is(false));
assertThat(createIndex.type(), is(IgniteSqlIndexType.IMPLICIT_SORTED));
assertThat(createIndex.columnList(), hasItem(ofTypeMatching("col", SqlIdentifier.class,
id -> id.isSimple() && id.getSimple().equals("COL"))));
expectUnparsed(node, "CREATE INDEX \"MY_INDEX\" ON \"MY_TABLE\" (\"COL\")");
}
@Test
public void createIndexImplicitTypeExplicitDirection() {
var query = "create index my_index on my_table (col1 asc, col2 desc)";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateIndex.class));
var createIndex = (IgniteSqlCreateIndex) node;
assertThat(createIndex.indexName().getSimple(), is("MY_INDEX"));
assertThat(createIndex.tableName().names, is(List.of("MY_TABLE")));
assertThat(createIndex.ifNotExists, is(false));
assertThat(createIndex.type(), is(IgniteSqlIndexType.IMPLICIT_SORTED));
assertThat(createIndex.columnList(), hasItem(ofTypeMatching("col1 asc", SqlIdentifier.class,
id -> id.isSimple() && id.getSimple().equals("COL1"))));
assertThat(createIndex.columnList(), hasItem(ofTypeMatching("col2 desc", SqlBasicCall.class,
bc -> bc.isA(Set.of(SqlKind.DESCENDING))
&& bc.getOperandList().size() == 1
&& bc.getOperandList().get(0) instanceof SqlIdentifier
&& ((SqlIdentifier) bc.getOperandList().get(0)).isSimple()
&& ((SqlIdentifier) bc.getOperandList().get(0)).getSimple().equals("COL2"))));
expectUnparsed(node, "CREATE INDEX \"MY_INDEX\" ON \"MY_TABLE\" (\"COL1\", \"COL2\" DESC)");
}
@Test
public void createIndexExplicitTypeMixedDirection() {
var query = "create index my_index on my_table using sorted (col1, col2 asc, col3 desc)";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateIndex.class));
var createIndex = (IgniteSqlCreateIndex) node;
assertThat(createIndex.indexName().getSimple(), is("MY_INDEX"));
assertThat(createIndex.tableName().names, is(List.of("MY_TABLE")));
assertThat(createIndex.ifNotExists, is(false));
assertThat(createIndex.type(), is(IgniteSqlIndexType.SORTED));
assertThat(createIndex.columnList(), hasItem(ofTypeMatching("col1", SqlIdentifier.class,
id -> id.isSimple() && id.getSimple().equals("COL1"))));
assertThat(createIndex.columnList(), hasItem(ofTypeMatching("col2 asc", SqlIdentifier.class,
id -> id.isSimple() && id.getSimple().equals("COL2"))));
assertThat(createIndex.columnList(), hasItem(ofTypeMatching("col3 desc", SqlBasicCall.class,
bc -> bc.isA(Set.of(SqlKind.DESCENDING))
&& bc.getOperandList().size() == 1
&& bc.getOperandList().get(0) instanceof SqlIdentifier
&& ((SqlIdentifier) bc.getOperandList().get(0)).isSimple()
&& ((SqlIdentifier) bc.getOperandList().get(0)).getSimple().equals("COL3"))));
expectUnparsed(node, "CREATE INDEX \"MY_INDEX\" ON \"MY_TABLE\" USING SORTED (\"COL1\", \"COL2\", \"COL3\" DESC)");
}
@Test
public void createHashIndex() {
var query = "create index my_index on my_table using hash (col)";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateIndex.class));
var createIndex = (IgniteSqlCreateIndex) node;
assertThat(createIndex.indexName().getSimple(), is("MY_INDEX"));
assertThat(createIndex.tableName().names, is(List.of("MY_TABLE")));
assertThat(createIndex.ifNotExists, is(false));
assertThat(createIndex.type(), is(IgniteSqlIndexType.HASH));
assertThat(createIndex.columnList(), hasItem(ofTypeMatching("col", SqlIdentifier.class,
id -> id.isSimple() && id.getSimple().equals("COL"))));
expectUnparsed(node, "CREATE INDEX \"MY_INDEX\" ON \"MY_TABLE\" USING HASH (\"COL\")");
}
@Test
public void sortDirectionMustNotBeSpecifiedForHashIndex() {
var query = "create index my_index on my_table using hash (col1, col2 asc)";
assertThrowsSqlException(
Sql.STMT_PARSE_ERR,
"Encountered \"asc\"",
() -> parse(query));
}
@Test
public void createIndexIfNotExists() {
var query = "create index if not exists my_index on my_table (col)";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateIndex.class));
var createIndex = (IgniteSqlCreateIndex) node;
assertThat(createIndex.indexName().getSimple(), is("MY_INDEX"));
assertThat(createIndex.tableName().names, is(List.of("MY_TABLE")));
assertThat(createIndex.ifNotExists, is(true));
expectUnparsed(node, "CREATE INDEX IF NOT EXISTS \"MY_INDEX\" ON \"MY_TABLE\" (\"COL\")");
}
@Test
public void createIndexTableInParticularSchema() {
var query = "create index my_index on my_schema.my_table (col)";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateIndex.class));
var createIndex = (IgniteSqlCreateIndex) node;
assertThat(createIndex.indexName().getSimple(), is("MY_INDEX"));
assertThat(createIndex.tableName().names, is(List.of("MY_SCHEMA", "MY_TABLE")));
expectUnparsed(node, "CREATE INDEX \"MY_INDEX\" ON \"MY_SCHEMA\".\"MY_TABLE\" (\"COL\")");
}
@Disabled("https://issues.apache.org/jira/browse/IGNITE-21672")
@Test
public void createIndexExplicitNullDirection() {
var query = "create index my_index on my_table (col1 nulls first, col2 nulls last, col3 desc nulls first)";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlCreateIndex.class));
var createIndex = (IgniteSqlCreateIndex) node;
assertThat(createIndex.indexName().getSimple(), is("MY_INDEX"));
assertThat(createIndex.tableName().names, is(List.of("MY_TABLE")));
assertThat(createIndex.columnList(), hasItem(ofTypeMatching("col1 nulls first", SqlBasicCall.class,
bc -> bc.isA(Set.of(SqlKind.NULLS_FIRST))
&& bc.getOperandList().size() == 1
&& bc.getOperandList().get(0) instanceof SqlIdentifier
&& ((SqlIdentifier) bc.getOperandList().get(0)).isSimple()
&& ((SqlIdentifier) bc.getOperandList().get(0)).getSimple().equals("COL1"))));
assertThat(createIndex.columnList(), hasItem(ofTypeMatching("col2 nulls last", SqlBasicCall.class,
bc -> bc.isA(Set.of(SqlKind.NULLS_LAST))
&& bc.getOperandList().size() == 1
&& bc.getOperandList().get(0) instanceof SqlIdentifier
&& ((SqlIdentifier) bc.getOperandList().get(0)).isSimple()
&& ((SqlIdentifier) bc.getOperandList().get(0)).getSimple().equals("COL2"))));
assertThat(createIndex.columnList(), hasItem(ofTypeMatching("col3 desc nulls first", SqlBasicCall.class,
bc -> bc.isA(Set.of(SqlKind.NULLS_FIRST))
&& bc.getOperandList().size() == 1
&& bc.getOperandList().get(0) instanceof SqlBasicCall
&& bc.getOperandList().get(0).isA(Set.of(SqlKind.DESCENDING))
&& ((SqlBasicCall) bc.getOperandList().get(0)).getOperandList().size() == 1
&& ((SqlBasicCall) bc.getOperandList().get(0)).getOperandList().get(0) instanceof SqlIdentifier
&& ((SqlIdentifier) ((SqlBasicCall) bc.getOperandList().get(0)).getOperandList().get(0)).isSimple()
&& ((SqlIdentifier) ((SqlBasicCall) bc.getOperandList().get(0)).getOperandList().get(0))
.getSimple().equals("COL3"))));
expectUnparsed(node, "CREATE INDEX \"MY_INDEX\" ON \"MY_TABLE\" ("
+ "\"COL1\" NULLS FIRST, \"COL2\" NULLS LAST, \"COL3\" DESC NULLS FIRST)"
);
}
@Test
public void dropTable() {
var query = "drop table my_table";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlDropTable.class));
var dropIndex = (IgniteSqlDropTable) node;
assertThat(dropIndex.ifExists(), is(false));
assertThat(dropIndex.name().names, is(List.of("MY_TABLE")));
expectUnparsed(node, "DROP TABLE \"MY_TABLE\"");
}
@Test
public void dropTableSchemaSpecified() {
var query = "drop table my_schema.my_table";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlDropTable.class));
var dropIndex = (IgniteSqlDropTable) node;
assertThat(dropIndex.ifExists(), is(false));
assertThat(dropIndex.name().names, is(List.of("MY_SCHEMA", "MY_TABLE")));
expectUnparsed(node, "DROP TABLE \"MY_SCHEMA\".\"MY_TABLE\"");
}
@Test
public void dropTableIfExists() {
var query = "drop table if exists my_table";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlDropTable.class));
var dropIndex = (IgniteSqlDropTable) node;
assertThat(dropIndex.ifExists(), is(true));
assertThat(dropIndex.name().names, is(List.of("MY_TABLE")));
expectUnparsed(node, "DROP TABLE IF EXISTS \"MY_TABLE\"");
}
@Test
public void dropIndexSimpleCase() {
var query = "drop index my_index";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlDropIndex.class));
var dropIndex = (IgniteSqlDropIndex) node;
assertThat(dropIndex.ifExists(), is(false));
assertThat(dropIndex.indexName().names, is(List.of("MY_INDEX")));
expectUnparsed(node, "DROP INDEX \"MY_INDEX\"");
}
@Test
public void dropIndexSchemaSpecified() {
var query = "drop index my_schema.my_index";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlDropIndex.class));
var dropIndex = (IgniteSqlDropIndex) node;
assertThat(dropIndex.ifExists(), is(false));
assertThat(dropIndex.indexName().names, is(List.of("MY_SCHEMA", "MY_INDEX")));
expectUnparsed(node, "DROP INDEX \"MY_SCHEMA\".\"MY_INDEX\"");
}
@Test
public void dropIndexIfExists() {
var query = "drop index if exists my_index";
SqlNode node = parse(query);
assertThat(node, instanceOf(IgniteSqlDropIndex.class));
var dropIndex = (IgniteSqlDropIndex) node;
assertThat(dropIndex.ifExists(), is(true));
assertThat(dropIndex.indexName().names, is(List.of("MY_INDEX")));
expectUnparsed(node, "DROP INDEX IF EXISTS \"MY_INDEX\"");
}
/**
* Ensures that the user cannot use the TIME_WITH_LOCAL_TIME_ZONE and TIMESTAMP_WITH_LOCAL_TIME_ZONE types for table columns.
*/
// TODO: Remove after https://issues.apache.org/jira/browse/IGNITE-21555 is implemented.
@Test
public void timeWithLocalTimeZoneIsNotSupported() {
assertThrowsSqlException(
Sql.STMT_PARSE_ERR,
"Encountered \"WITH\"",
() -> parse("CREATE TABLE test (ts TIME WITH LOCAL TIME ZONE)")
);
}
private IgniteSqlCreateTable parseCreateTable(String stmt) {
SqlNode node = parse(stmt);
assertThat(node, instanceOf(IgniteSqlCreateTable.class));
return (IgniteSqlCreateTable) node;
}
@Test
public void alterTableAddColumn() {
SqlNode sqlNode = parse("ALTER TABLE t ADD COLUMN c INT");
IgniteSqlAlterTableAddColumn addColumn = assertInstanceOf(IgniteSqlAlterTableAddColumn.class, sqlNode);
SqlColumnDeclaration declaration = (SqlColumnDeclaration) addColumn.columns().get(0);
assertThat(addColumn.name.names, is(List.of("T")));
expectColumnBasic(declaration, "C", ColumnStrategy.NULLABLE, "INTEGER", true);
assertThat(declaration.expression, is(nullValue()));
expectUnparsed(addColumn, "ALTER TABLE \"T\" ADD COLUMN \"C\" INTEGER");
}
@Test
public void alterTableAddColumnNull() {
SqlNode sqlNode = parse("ALTER TABLE t ADD COLUMN c INT NULL");
IgniteSqlAlterTableAddColumn addColumn = assertInstanceOf(IgniteSqlAlterTableAddColumn.class, sqlNode);
SqlColumnDeclaration column = (SqlColumnDeclaration) addColumn.columns().get(0);
assertThat(addColumn.name.names, is(List.of("T")));
expectColumnBasic(column, "C", ColumnStrategy.NULLABLE, "INTEGER", true);
assertThat(column.expression, is(nullValue()));
expectUnparsed(addColumn, "ALTER TABLE \"T\" ADD COLUMN \"C\" INTEGER");
}
@Test
public void alterTableAddColumnNotNull() {
SqlNode sqlNode = parse("ALTER TABLE t ADD COLUMN c INT NOT NULL");
IgniteSqlAlterTableAddColumn addColumn = assertInstanceOf(IgniteSqlAlterTableAddColumn.class, sqlNode);
SqlColumnDeclaration column = (SqlColumnDeclaration) addColumn.columns().get(0);
assertThat(addColumn.name.names, is(List.of("T")));
expectColumnBasic(column, "C", ColumnStrategy.NOT_NULLABLE, "INTEGER", false);
assertThat(column.expression, is(nullValue()));
expectUnparsed(addColumn, "ALTER TABLE \"T\" ADD COLUMN \"C\" INTEGER NOT NULL");
}
/**
* Matcher to verify name in the column declaration.
*
* @param name Expected name.
* @return {@code true} in case name in the column declaration equals to the expected one.
*/
private static <T extends SqlColumnDeclaration> Matcher<T> columnWithName(String name) {
return new CustomMatcher<T>("column with name=" + name) {
/** {@inheritDoc} */
@Override
public boolean matches(Object item) {
return item instanceof SqlColumnDeclaration
&& ((SqlColumnDeclaration) item).name.names.get(0).equals(name);
}
};
}
/** Checks basic column properties such as name, type name and type's nullability. */
private static void expectColumnBasic(SqlColumnDeclaration declaration,
String columnName, ColumnStrategy columnStrategy,
String typeName, Boolean nullable) {
assertThat(List.of(columnName), is(declaration.name.names));
assertThat(columnStrategy, is(declaration.strategy));
assertThat(List.of(typeName), is(declaration.dataType.getTypeName().names));
assertThat(nullable, is(declaration.dataType.getNullable()));
}
private void assertThatOptionPresent(List<SqlNode> optionList, String option, Object expVal) {
assertThat(optionList, hasItem(ofTypeMatching(
option + "=" + expVal,
IgniteSqlCreateTableOption.class,
opt -> {
if (opt.key().getSimple().equals(option)) {
SqlNode valNode = opt.value();
if (valNode instanceof SqlLiteral) {
return Objects.equals(expVal, ((SqlLiteral) valNode).getValueAs(expVal.getClass()));
} else if (valNode instanceof SqlIdentifier) {
return Objects.equals(expVal, ((SqlIdentifier) valNode).getSimple());
}
}
return false;
}
)));
}
}