/* * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor * license agreements. See the NOTICE file distributed with this work for * additional information regarding copyright ownership. Crate 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. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial agreement. */ package io.crate.analyze; import io.crate.core.collections.StringObjectMaps; import io.crate.metadata.ColumnIdent; import io.crate.sql.parser.ParsingException; import io.crate.test.integration.CrateDummyClusterServiceUnitTest; import io.crate.testing.SQLExecutor; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import static io.crate.testing.TestingHelpers.mapToSortedString; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.is; import static org.hamcrest.core.IsEqual.equalTo; public class AlterTableAddColumnAnalyzerTest extends CrateDummyClusterServiceUnitTest { private SQLExecutor e; @Before public void prepare() { e = SQLExecutor.builder(clusterService).enableDefaultTables().build(); } @Test public void testAddColumnOnSystemTableIsNotAllowed() throws Exception { expectedException.expect(UnsupportedOperationException.class); expectedException.expectMessage("The relation \"sys.shards\" doesn't support or allow ALTER " + "operations, as it is read-only."); e.analyze("alter table sys.shards add column foobar string"); } @Test public void testAddColumnOnSinglePartitionNotAllowed() throws Exception { expectedException.expect(UnsupportedOperationException.class); expectedException.expectMessage("Adding a column to a single partition is not supported"); e.analyze("alter table parted partition (date = 1395874800000) add column foobar string"); } @Test public void testAddColumnWithAnalyzerAndNonStringType() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage( "Can't use an Analyzer on column foobar['age'] because analyzers are only allowed on columns of type \"string\""); e.analyze("alter table users add column foobar object as (age int index using fulltext)"); } @Test public void testAddFulltextIndex() throws Exception { expectedException.expect(ParsingException.class); e.analyze("alter table users add column index ft_foo using fulltext (name)"); } @Test public void testAddColumnThatExistsAlready() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("The table doc.users already has a column named name"); e.analyze("alter table users add column name string"); } @Test public void testAddColumnToATableWithoutPrimaryKey() throws Exception { AddColumnAnalyzedStatement analysis = e.analyze( "alter table users_clustered_by_only add column foobar string"); Map<String, Object> mapping = analysis.analyzedTableElements().toMapping(); Object primaryKeys = ((Map) mapping.get("_meta")).get("primary_keys"); assertNull(primaryKeys); // _id shouldn't be included } @SuppressWarnings("ConstantConditions") @Test public void testAddColumnAsPrimaryKey() throws Exception { AddColumnAnalyzedStatement analysis = e.analyze( "alter table users add column additional_pk string primary key"); assertThat(analysis.analyzedTableElements().primaryKeys(), Matchers.contains( "additional_pk", "id" )); AnalyzedColumnDefinition idColumn = null; AnalyzedColumnDefinition additionalPkColumn = null; for (AnalyzedColumnDefinition column : analysis.analyzedTableElements().columns()) { if (column.name().equals("id")) { idColumn = column; } else { additionalPkColumn = column; } } assertNotNull(idColumn); assertThat(idColumn.ident(), equalTo(new ColumnIdent("id"))); assertThat(idColumn.dataType(), equalTo("long")); assertNotNull(additionalPkColumn); assertThat(additionalPkColumn.ident(), equalTo(new ColumnIdent("additional_pk"))); assertThat(additionalPkColumn.dataType(), equalTo("string")); } @Test public void testAddPrimaryKeyColumnWithArrayTypeUnsupported() throws Exception { expectedException.expect(UnsupportedOperationException.class); expectedException.expectMessage("Cannot use columns of type \"array\" as primary key"); e.analyze("alter table users add column newpk array(string) primary key"); } @Test public void testAddColumnToATableWithNotNull() throws Exception { AddColumnAnalyzedStatement analysis = e.analyze("alter table users_clustered_by_only " + "add column notnullcol string not null"); Map<String, Object> mapping = analysis.analyzedTableElements().toMapping(); assertThat((String) ((Set) ((Map) ((Map) mapping.get("_meta")).get("constraints")).get("not_null")) .toArray(new String[0])[0], is("notnullcol")); } @Test public void testAddArrayColumn() throws Exception { AddColumnAnalyzedStatement analysis = e.analyze("alter table users add newtags array(string)"); AnalyzedColumnDefinition columnDefinition = analysis.analyzedTableElements().columns().get(0); assertThat(columnDefinition.name(), Matchers.equalTo("newtags")); assertThat(columnDefinition.dataType(), Matchers.equalTo("string")); assertTrue(columnDefinition.isArrayOrInArray()); Map<String, Object> mappingProperties = (Map) analysis.analyzedTableElements().toMapping().get("properties"); Map<String, Object> newtags = (Map<String, Object>) mappingProperties.get("newtags"); assertThat((String) newtags.get("type"), is("array")); Map<String, Object> inner = (Map<String, Object>) newtags.get("inner"); assertThat((String) inner.get("type"), is("keyword")); } @Test public void testAddNewNestedObjectColumn() throws Exception { AddColumnAnalyzedStatement analysis = e.analyze( "alter table users add column foo['x']['y'] string"); assertThat(analysis.analyzedTableElements().columns().size(), is(2)); // id pk column is also added AnalyzedColumnDefinition column = analysis.analyzedTableElements().columns().get(0); assertThat(column.ident(), Matchers.equalTo(new ColumnIdent("foo"))); assertThat(column.children().size(), is(1)); AnalyzedColumnDefinition xColumn = column.children().get(0); assertThat(xColumn.ident(), Matchers.equalTo(new ColumnIdent("foo", Arrays.asList("x")))); assertThat(xColumn.children().size(), is(1)); AnalyzedColumnDefinition yColumn = xColumn.children().get(0); assertThat(yColumn.ident(), Matchers.equalTo(new ColumnIdent("foo", Arrays.asList("x", "y")))); assertThat(yColumn.children().size(), is(0)); Map<String, Object> mapping = analysis.analyzedTableElements().toMapping(); Map foo = (Map) StringObjectMaps.getByPath(mapping, "properties.foo"); assertThat((String) foo.get("type"), is("object")); Map x = (Map) StringObjectMaps.getByPath(mapping, "properties.foo.properties.x"); assertThat((String) x.get("type"), is("object")); Map y = (Map) StringObjectMaps.getByPath(mapping, "properties.foo.properties.x.properties.y"); assertThat((String) y.get("type"), is("keyword")); } @Test public void testAddNewNestedColumnToObjectArray() throws Exception { AddColumnAnalyzedStatement analysis = e.analyze("alter table users add friends['is_nice'] BOOLEAN"); List<AnalyzedColumnDefinition> columns = analysis.analyzedTableElements().columns(); assertThat(columns.size(), is(2)); // second one is primary key AnalyzedColumnDefinition friends = columns.get(0); assertThat(mapToSortedString(friends.toMapping()), is("inner={" + "dynamic=true, " + "properties={" + "is_nice={type=boolean}" + "}, " + "type=object" + "}, type=array")); } @Test public void testAddColumnToObjectTypeMaintainsObjectPolicy() throws Exception { AddColumnAnalyzedStatement analysis = e.analyze( "alter table users add column address['street'] string"); List<AnalyzedColumnDefinition> columns = analysis.analyzedTableElements().columns(); assertThat(columns.size(), is(2)); AnalyzedColumnDefinition address = columns.get(0); assertThat(address.objectType, is("strict")); } @Test public void testAddColumnToStrictObject() { AddColumnAnalyzedStatement analysis = e.analyze( "alter table users add column address['street'] string"); List<AnalyzedColumnDefinition> columns = analysis.analyzedTableElements().columns(); assertThat(columns.size(), is(2)); AnalyzedColumnDefinition address = columns.get(0); AnalyzedColumnDefinition street = address.children().get(0); assertThat(street.ident(), is(ColumnIdent.fromPath("address.street"))); assertThat(street.dataType(), is("string")); assertThat(street.isParentColumn(), is(false)); } @Test public void testAddNewNestedColumnToObjectColumn() throws Exception { AddColumnAnalyzedStatement analysis = e.analyze( "alter table users add column details['foo'] object as (score float, name string)"); List<AnalyzedColumnDefinition> columns = analysis.analyzedTableElements().columns(); assertThat(columns.size(), is(2)); // second one is primary key AnalyzedColumnDefinition details = columns.get(0); assertThat(details.ident(), is(ColumnIdent.fromPath("details"))); assertThat(details.dataType(), is("object")); assertThat(details.isParentColumn(), is(true)); assertThat(details.children().size(), is(1)); AnalyzedColumnDefinition foo = details.children().get(0); assertThat(foo.ident(), is(ColumnIdent.fromPath("details.foo"))); assertThat(foo.dataType(), is("object")); assertThat(foo.isParentColumn(), is(false)); assertThat(columns.get(0).children().get(0).children().size(), is(2)); AnalyzedColumnDefinition score = columns.get(0).children().get(0).children().get(0); assertThat(score.ident(), is(ColumnIdent.fromPath("details.foo.score"))); assertThat(score.dataType(), is("float")); AnalyzedColumnDefinition name = columns.get(0).children().get(0).children().get(1); assertThat(name.ident(), is(ColumnIdent.fromPath("details.foo.name"))); assertThat(name.dataType(), is("string")); Map<String, Object> mapping = analysis.analyzedTableElements().toMapping(); assertThat(mapToSortedString(mapping), is("_all={enabled=false}, _meta={primary_keys=[id]}, properties={details={dynamic=true, " + "properties={foo={dynamic=true, properties={name={type=keyword}, score={type=float}}, type=object}}, " + "type=object}, id={type=long}}")); } @Test public void testAddNewNestedColumnWithArrayToRoot() throws Exception { AddColumnAnalyzedStatement analysis = e.analyze( "alter table users add column new_obj_col object as (a array(long))"); List<AnalyzedColumnDefinition> columns = analysis.analyzedTableElements().columns(); assertThat(columns.size(), is(2)); // second one is primary key assertThat(columns.get(0).dataType(), is("object")); assertThat(columns.get(0).children().get(0).dataType(), is("long")); assertTrue(columns.get(0).children().get(0).isArrayOrInArray()); } @Test public void testAddNewNestedColumnWithArrayToObjectColumn() throws Exception { AddColumnAnalyzedStatement analysis = e.analyze( "alter table users add column new_obj_col object as (o object as (b array(long)))"); List<AnalyzedColumnDefinition> columns = analysis.analyzedTableElements().columns(); assertThat(columns.size(), is(2)); // second one is primary key assertThat(columns.get(0).children().get(0).dataType(), is("object")); assertThat(columns.get(0).children().get(0).children().get(0).dataType(), is("long")); assertTrue(columns.get(0).children().get(0).children().get(0).isArrayOrInArray()); } @Test public void testAddNewNestedColumnToNestedObjectColumn() throws Exception { AddColumnAnalyzedStatement analysis = e.analyze( "alter table deeply_nested add column details['stuff']['foo'] object as (score float, price string)"); List<AnalyzedColumnDefinition> columns = analysis.analyzedTableElements().columns(); assertThat(columns.size(), is(1)); assertThat(columns.get(0).ident(), is(ColumnIdent.fromPath("details"))); assertThat(columns.get(0).dataType(), is("object")); assertThat(columns.get(0).isParentColumn(), is(true)); assertThat(columns.get(0).children().size(), is(1)); AnalyzedColumnDefinition stuff = columns.get(0).children().get(0); assertThat(stuff.ident(), is(ColumnIdent.fromPath("details.stuff"))); assertThat(stuff.dataType(), is("object")); assertThat(stuff.isParentColumn(), is(true)); assertThat(stuff.children().size(), is(1)); AnalyzedColumnDefinition foo = stuff.children().get(0); assertThat(foo.ident(), is(ColumnIdent.fromPath("details.stuff.foo"))); assertThat(foo.dataType(), is("object")); assertThat(foo.isParentColumn(), is(false)); assertThat(foo.children().size(), is(2)); AnalyzedColumnDefinition score = foo.children().get(0); assertThat(score.ident(), is(ColumnIdent.fromPath("details.stuff.foo.score"))); assertThat(score.dataType(), is("float")); AnalyzedColumnDefinition price = foo.children().get(1); assertThat(price.ident(), is(ColumnIdent.fromPath("details.stuff.foo.price"))); assertThat(price.dataType(), is("string")); Map<String, Object> mapping = analysis.analyzedTableElements().toMapping(); assertThat(mapToSortedString(mapping), is("_all={enabled=false}, _meta={}, properties={details={dynamic=true, " + "properties={stuff={dynamic=true, properties={foo={dynamic=true, properties={price={type=keyword}, " + "score={type=float}}, type=object}}, type=object}}, type=object}}")); } @Test public void testAddGeneratedColumn() throws Exception { AddColumnAnalyzedStatement analysis = e.analyze( "alter table users add column name_generated as concat(name, 'foo')"); assertThat(analysis.hasNewGeneratedColumns(), is(true)); assertThat(analysis.analyzedTableElements().columnIdents(), containsInAnyOrder( new ColumnIdent("name_generated"), new ColumnIdent("id"))); AnalyzedColumnDefinition nameGeneratedColumn = null; for (AnalyzedColumnDefinition columnDefinition : analysis.analyzedTableElements().columns()) { if (columnDefinition.ident().name().equals("name_generated")) { nameGeneratedColumn = columnDefinition; } } assertNotNull(nameGeneratedColumn); assertThat(nameGeneratedColumn.dataType(), equalTo("string")); assertThat(nameGeneratedColumn.formattedGeneratedExpression(), is("concat(name, 'foo')")); } }