/* * 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 com.google.common.collect.ImmutableMap; import io.crate.analyze.relations.DocTableRelation; import io.crate.analyze.symbol.DynamicReference; import io.crate.analyze.symbol.Function; import io.crate.analyze.symbol.Literal; import io.crate.analyze.symbol.Symbol; import io.crate.exceptions.ColumnValidationException; import io.crate.exceptions.TableUnknownException; import io.crate.exceptions.UnsupportedFeatureException; import io.crate.metadata.ColumnIdent; import io.crate.metadata.Reference; import io.crate.metadata.Routing; import io.crate.metadata.Schemas; import io.crate.metadata.TableIdent; import io.crate.metadata.doc.DocTableInfo; import io.crate.metadata.table.TestingTableInfo; import io.crate.test.integration.CrateDummyClusterServiceUnitTest; import io.crate.testing.SQLExecutor; import io.crate.types.ArrayType; import io.crate.types.DataType; import io.crate.types.DataTypes; import io.crate.types.DoubleType; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import static io.crate.analyze.TableDefinitions.SHARD_ROUTING; import static io.crate.analyze.TableDefinitions.USER_TABLE_INFO; import static io.crate.testing.SymbolMatchers.*; import static org.hamcrest.Matchers.*; public class UpdateAnalyzerTest extends CrateDummyClusterServiceUnitTest { private static final String VERSION_EX_FROM_VALIDATOR = "Filtering \"_version\" in WHERE clause only works using the \"=\" operator, checking for a numeric value"; private final TableIdent nestedClusteredByTableIdent = new TableIdent("doc", "nestedclustered"); private final DocTableInfo nestedClusteredByTableInfo = TestingTableInfo.builder( nestedClusteredByTableIdent, SHARD_ROUTING) .add("obj", DataTypes.OBJECT, null) .add("obj", DataTypes.STRING, Collections.singletonList("name")) .add("other_obj", DataTypes.OBJECT, null) .clusteredBy("obj.name").build(); private final TableIdent testAliasTableIdent = new TableIdent(Schemas.DEFAULT_SCHEMA_NAME, "alias"); private final DocTableInfo testAliasTableInfo = new TestingTableInfo.Builder( testAliasTableIdent, new Routing(ImmutableMap.<String, Map<String, List<Integer>>>of())) .add("bla", DataTypes.STRING, null) .isAlias(true).build(); private final TableIdent nestedPk = new TableIdent(Schemas.DEFAULT_SCHEMA_NAME, "t_nested_pk"); private final DocTableInfo tiNestedPk = new TestingTableInfo.Builder( nestedPk, SHARD_ROUTING) .add("o", DataTypes.OBJECT) .add("o", DataTypes.INTEGER, Collections.singletonList("x")) .add("o", DataTypes.INTEGER, Collections.singletonList("y")) .addPrimaryKey("o.x") .build(); private SQLExecutor e; @Before public void prepare() { SQLExecutor.Builder builder = SQLExecutor.builder(clusterService) .enableDefaultTables() .addDocTable(nestedClusteredByTableInfo) .addDocTable(testAliasTableInfo) .addDocTable(tiNestedPk); TableIdent partedGeneratedColumnTableIdent = new TableIdent(null, "parted_generated_column"); TestingTableInfo.Builder partedGeneratedColumnTableInfo = new TestingTableInfo.Builder( partedGeneratedColumnTableIdent, new Routing(ImmutableMap.<String, Map<String, List<Integer>>>of())) .add("ts", DataTypes.TIMESTAMP, null) .addGeneratedColumn("day", DataTypes.TIMESTAMP, "date_trunc('day', ts)", true); builder.addDocTable(partedGeneratedColumnTableInfo); TableIdent nestedPartedGeneratedColumnTableIdent = new TableIdent(null, "nested_parted_generated_column"); TestingTableInfo.Builder nestedPartedGeneratedColumnTableInfo = new TestingTableInfo.Builder( nestedPartedGeneratedColumnTableIdent, new Routing(ImmutableMap.<String, Map<String, List<Integer>>>of())) .add("user", DataTypes.OBJECT, null) .add("user", DataTypes.STRING, Arrays.asList("name")) .addGeneratedColumn("name", DataTypes.STRING, "concat(user['name'], 'bar')", true); builder.addDocTable(nestedPartedGeneratedColumnTableInfo); e = builder.build(); } protected UpdateAnalyzedStatement analyze(String statement) { return e.analyze(statement); } protected UpdateAnalyzedStatement analyze(String statement, Object[] params) { return (UpdateAnalyzedStatement) e.analyze(statement, params); } @Test public void testUpdateAnalysis() throws Exception { AnalyzedStatement analyzedStatement = analyze("update users set name='Ford Prefect'"); assertThat(analyzedStatement, instanceOf(UpdateAnalyzedStatement.class)); } @Test(expected = TableUnknownException.class) public void testUpdateUnknownTable() throws Exception { analyze("update unknown set name='Prosser'"); } @Test public void testUpdateSetColumnToColumnValue() throws Exception { UpdateAnalyzedStatement statement = analyze("update users set name=name"); UpdateAnalyzedStatement.NestedAnalyzedStatement statement1 = statement.nestedStatements().get(0); assertThat(statement1.assignments().size(), is(1)); Symbol value = statement1.assignments().entrySet().iterator().next().getValue(); assertThat(value, isReference("name")); } @Test public void testUpdateSetExpression() throws Exception { UpdateAnalyzedStatement statement = analyze("update users set other_id=other_id+1"); UpdateAnalyzedStatement.NestedAnalyzedStatement statement1 = statement.nestedStatements().get(0); assertThat(statement1.assignments().size(), is(1)); Symbol value = statement1.assignments().entrySet().iterator().next().getValue(); assertThat(value, isFunction("add")); } @Test(expected = IllegalArgumentException.class) public void testUpdateSameReferenceRepeated() throws Exception { analyze("update users set name='Trillian', name='Ford'"); } @Test(expected = IllegalArgumentException.class) public void testUpdateSameNestedReferenceRepeated() throws Exception { analyze("update users set details['arms']=3, details['arms']=5"); } @Test(expected = UnsupportedOperationException.class) public void testUpdateSysTables() throws Exception { analyze("update sys.nodes set fs=?", new Object[]{new HashMap<String, Object>() {{ put("free", 0); }}}); } @Test public void testNumericTypeOutOfRange() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for shorts: -100000 cannot be cast to type short"); analyze("update users set shorts=-100000"); } @Test public void testNumericOutOfRangeFromFunction() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for bytes: 1234 cannot be cast to type byte"); analyze("update users set bytes=abs(-1234)"); } @Test public void testUpdateAssignments() throws Exception { UpdateAnalyzedStatement statement = analyze("update users set name='Trillian'"); UpdateAnalyzedStatement.NestedAnalyzedStatement statement1 = statement.nestedStatements().get(0); assertThat(statement1.assignments().size(), is(1)); assertThat(((DocTableRelation) statement.sourceRelation()).tableInfo().ident(), is(new TableIdent(Schemas.DEFAULT_SCHEMA_NAME, "users"))); Reference ref = statement1.assignments().keySet().iterator().next(); assertThat(ref.ident().tableIdent().name(), is("users")); assertThat(ref.ident().columnIdent().name(), is("name")); assertTrue(statement1.assignments().containsKey(ref)); Symbol value = statement1.assignments().entrySet().iterator().next().getValue(); assertThat(value, isLiteral("Trillian")); } @Test public void testUpdateAssignmentNestedDynamicColumn() throws Exception { UpdateAnalyzedStatement.NestedAnalyzedStatement statement = analyze("update users set details['arms']=3").nestedStatements().get(0); assertThat(statement.assignments().size(), is(1)); Reference ref = statement.assignments().keySet().iterator().next(); assertThat(ref, instanceOf(DynamicReference.class)); Assert.assertEquals(DataTypes.LONG, ref.valueType()); assertThat(ref.ident().columnIdent().isColumn(), is(false)); assertThat(ref.ident().columnIdent().fqn(), is("details.arms")); } @Test(expected = ColumnValidationException.class) public void testUpdateAssignmentWrongType() throws Exception { analyze("update users set other_id='String'"); } @Test public void testUpdateAssignmentConvertableType() throws Exception { UpdateAnalyzedStatement.NestedAnalyzedStatement statement = analyze("update users set other_id=9.9").nestedStatements().get(0); Reference ref = statement.assignments().keySet().iterator().next(); assertThat(ref, not(instanceOf(DynamicReference.class))); assertEquals(DataTypes.LONG, ref.valueType()); Symbol value = statement.assignments().entrySet().iterator().next().getValue(); assertThat(value, isLiteral(9L)); } @Test public void testUpdateMuchAssignments() throws Exception { UpdateAnalyzedStatement.NestedAnalyzedStatement statement = analyze( "update users set other_id=9.9, name='Trillian', details=?, stuff=true, foo='bar'", new Object[]{new HashMap<String, Object>()}).nestedStatements().get(0); assertThat(statement.assignments().size(), is(5)); } @Test public void testNoWhereClause() throws Exception { UpdateAnalyzedStatement.NestedAnalyzedStatement analysis = analyze("update users set other_id=9").nestedStatements().get(0); assertThat(analysis.whereClause(), is(WhereClause.MATCH_ALL)); } @Test public void testNoMatchWhereClause() throws Exception { UpdateAnalyzedStatement.NestedAnalyzedStatement analysis = analyze("update users set other_id=9 where true=false").nestedStatements().get(0); assertThat(analysis.whereClause().noMatch(), is(true)); } @Test public void testUpdateWhereClause() throws Exception { UpdateAnalyzedStatement.NestedAnalyzedStatement analysis = analyze("update users set other_id=9 where name='Trillian'").nestedStatements().get(0); assertThat(analysis.whereClause().hasQuery(), is(true)); assertThat(analysis.whereClause().noMatch(), is(false)); } @Test public void testQualifiedNameReference() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Column reference \"users.name\" has too many parts. A column must not have a schema or a table here."); UpdateAnalyzedStatement.NestedAnalyzedStatement analysis = analyze("update users set users.name='Trillian'").nestedStatements().get(0); } @Test public void testUpdateWithParameter() throws Exception { Map[] friends = new Map[]{ new HashMap<String, Object>() {{ put("name", "Slartibartfast"); }}, new HashMap<String, Object>() {{ put("name", "Marvin"); }} }; UpdateAnalyzedStatement.NestedAnalyzedStatement analysis = analyze("update users set name=?, other_id=?, friends=? where id=?", new Object[]{"Jeltz", 0, friends, "9"}).nestedStatements().get(0); assertThat(analysis.assignments().size(), is(3)); assertThat( analysis.assignments().get(USER_TABLE_INFO.getReference(new ColumnIdent("name"))), isLiteral("Jeltz") ); assertThat( analysis.assignments().get(USER_TABLE_INFO.getReference(new ColumnIdent("friends"))), isLiteral(friends, new ArrayType(DataTypes.OBJECT)) ); assertThat( analysis.assignments().get(USER_TABLE_INFO.getReference(new ColumnIdent("other_id"))), isLiteral(0L) ); assertThat(((Function) analysis.whereClause().query()).arguments().get(1), isLiteral(9L)); } @Test public void testUpdateWithWrongParameters() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Cannot cast [1, 2, 3] to type long"); analyze("update users set name=?, friends=? where other_id=?", new Object[]{ new HashMap<String, Object>(), new Map[0], new Long[]{1L, 2L, 3L}} ); } @Test public void testUpdateWithEmptyObjectArray() throws Exception { UpdateAnalyzedStatement.NestedAnalyzedStatement analysis = analyze("update users set friends=? where other_id=0", new Object[]{new Map[0], 0}).nestedStatements().get(0); Literal friendsLiteral = (Literal) analysis.assignments().get( USER_TABLE_INFO.getReference(new ColumnIdent("friends"))); assertThat(friendsLiteral.valueType().id(), is(ArrayType.ID)); assertEquals(DataTypes.OBJECT, ((ArrayType) friendsLiteral.valueType()).innerType()); assertThat(((Object[]) friendsLiteral.value()).length, is(0)); } @Test public void testUpdateSystemColumn() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for _id: Updating a system column is not supported"); analyze("update users set _id=1"); } @Test public void testUpdatePrimaryKey() throws Exception { expectedException.expect(ColumnValidationException.class); analyze("update users set id=1"); } @Test public void testUpdateClusteredBy() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for id: Updating a clustered-by column is not supported"); analyze("update users_clustered_by_only set id=1"); } @Test(expected = ColumnValidationException.class) public void testUpdatePartitionedByColumn() throws Exception { analyze("update parted set date = 1395874800000"); } @Test public void testUpdatePrimaryKeyIfNestedDoesNotWork() throws Exception { expectedException.expect(ColumnValidationException.class); analyze("update t_nested_pk set o = {y=10}"); } @Test public void testUpdateColumnReferencedInGeneratedPartitionByColumn() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Updating a column which is referenced in a partitioned by generated column expression is not supported"); analyze("update parted_generated_column set ts = 1449999900000"); } @Test public void testUpdateColumnReferencedInGeneratedPartitionByColumnNestedParent() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Updating a column which is referenced in a partitioned by generated column expression is not supported"); analyze("update nested_parted_generated_column set user = {name = 'Ford'}"); } @Test public void testUpdateTableAlias() throws Exception { UpdateAnalyzedStatement.NestedAnalyzedStatement expectedAnalysis = analyze( "update users set awesome=true where awesome=false").nestedStatements().get(0); UpdateAnalyzedStatement.NestedAnalyzedStatement actualAnalysis = analyze( "update users as u set awesome=true where awesome=false").nestedStatements().get(0); assertEquals( expectedAnalysis.assignments(), actualAnalysis.assignments() ); assertEquals( ((Function) expectedAnalysis.whereClause().query()).arguments().get(0), ((Function) actualAnalysis.whereClause().query()).arguments().get(0) ); } @Test(expected = IllegalArgumentException.class) public void testUpdateObjectArrayField() throws Exception { analyze("update users set friends['id'] = ?", new Object[]{ new Long[]{1L, 2L, 3L} }); } @Test public void testUpdateArrayByElement() { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Updating a single element of an array is not supported"); analyze("update users set friends[1] = 2"); } @Test(expected = IllegalArgumentException.class) public void testWhereClauseObjectArrayField() throws Exception { analyze("update users set awesome=true where friends['id'] = 5"); } @Test public void testUpdateWithFQName() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Column reference \"users.name\" has too many parts. A column must not have a schema or a table here."); analyze("update users set users.name = 'Ford Mustang'"); } @Test public void testUpdateDynamicNestedArrayParamLiteral() throws Exception { UpdateAnalyzedStatement statement = analyze("update users set new=[[1.9, 4.8], [9.7, 12.7]]"); DataType dataType = statement.nestedStatements().get(0).assignments().values().iterator().next().valueType(); assertThat(dataType, is((DataType) new ArrayType(new ArrayType(DoubleType.INSTANCE)))); } @Test public void testUpdateDynamicNestedArrayParam() throws Exception { UpdateAnalyzedStatement statement = analyze("update users set new=? where id=1", new Object[]{ new Object[]{ new Object[]{ 1.9, 4.8 }, new Object[]{ 9.7, 12.7 } } }); DataType dataType = statement.nestedStatements().get(0).assignments().values().iterator().next().valueType(); assertThat(dataType, is((DataType) new ArrayType(new ArrayType(DoubleType.INSTANCE)))); } @Test public void testUpdateInvalidType() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for tags: [['a', 'b']] cannot be cast to type string_array"); analyze("update users set tags=? where id=1", new Object[]{ new Object[]{ new Object[]{"a", "b"} } }); } @Test public void testUsingFQColumnNameShouldBePossibleInWhereClause() throws Exception { UpdateAnalyzedStatement statement = analyze("update users set name = 'foo' where users.name != 'foo'"); Function eqFunction = (Function) ((Function) statement.nestedStatements().get(0).whereClause().query()).arguments().get(0); assertThat(eqFunction.arguments().get(0), isReference("name")); } @Test public void testTestUpdateOnTableWithAliasAndFQColumnNameInWhereClause() throws Exception { UpdateAnalyzedStatement statement = analyze("update users t set name = 'foo' where t.name != 'foo'"); Function eqFunction = (Function) ((Function) statement.nestedStatements().get(0).whereClause().query()).arguments().get(0); assertThat(eqFunction.arguments().get(0), isReference("name")); } @Test public void testUpdateNestedClusteredByColumn() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for obj: Updating a clustered-by column is not supported"); analyze("update nestedclustered set obj = {name='foobar'}"); } @Test public void testUpdateNestedClusteredByColumnWithOtherObject() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for obj: Updating a clustered-by column is not supported"); analyze("update nestedclustered set obj = other_obj"); } @Test public void testUpdateWhereVersionUsingWrongOperator() throws Exception { expectedException.expect(UnsupportedOperationException.class); expectedException.expectMessage(VERSION_EX_FROM_VALIDATOR); analyze("update users set text = ? where text = ? and \"_version\" >= ?", new Object[]{"already in panic", "don't panic", 3}); } @Test public void testUpdateWhereVersionIsColumn() throws Exception { expectedException.expect(UnsupportedOperationException.class); expectedException.expectMessage(VERSION_EX_FROM_VALIDATOR); analyze("update users set col2 = ? where _version = id", new Object[]{1}); } @Test public void testUpdateWhereVersionInOperatorColumn() throws Exception { expectedException.expect(UnsupportedFeatureException.class); expectedException.expectMessage(UpdateAnalyzer.VERSION_SEARCH_EX_MSG); analyze("update users set col2 = ? where _version in (1,2,3)", new Object[]{1}); } @Test public void testUpdateWhereVersionOrOperatorColumn() throws Exception { expectedException.expect(UnsupportedFeatureException.class); expectedException.expectMessage(UpdateAnalyzer.VERSION_SEARCH_EX_MSG); analyze("update users set col2 = ? where _version = 1 or _version = 2", new Object[]{1}); } @Test public void testUpdateWhereVersionAddition() throws Exception { expectedException.expect(UnsupportedOperationException.class); expectedException.expectMessage(VERSION_EX_FROM_VALIDATOR); analyze("update users set col2 = ? where _version + 1 = 2", new Object[]{1}); } @Test public void testUpdateWhereVersionNotPredicate() throws Exception { expectedException.expect(UnsupportedOperationException.class); expectedException.expectMessage(VERSION_EX_FROM_VALIDATOR); analyze("update users set text = ? where not (_version = 1 and id = 1)", new Object[]{"Hello"}); } @Test public void testUpdateWhereVersionOrOperator() throws Exception { expectedException.expect(UnsupportedFeatureException.class); expectedException.expectMessage(UpdateAnalyzer.VERSION_SEARCH_EX_MSG); analyze("update users set awesome = true where _version = 1 or _version = 2"); } @Test public void testUpdateWithVersionZero() throws Exception { expectedException.expect(UnsupportedFeatureException.class); expectedException.expectMessage(UpdateAnalyzer.VERSION_SEARCH_EX_MSG); analyze("update users set awesome=true where name='Ford' and _version=0").nestedStatements().get(0); } @Test public void testSelectWhereVersionIsNullPredicate() throws Exception { expectedException.expect(UnsupportedOperationException.class); expectedException.expectMessage(VERSION_EX_FROM_VALIDATOR); analyze("update users set col2 = ? where _version is null", new Object[]{1}); } }