/* * 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.symbol.Function; import io.crate.analyze.symbol.Symbol; import io.crate.exceptions.ColumnUnknownException; import io.crate.exceptions.ColumnValidationException; import io.crate.exceptions.InvalidColumnNameException; import io.crate.exceptions.ValidationException; import io.crate.metadata.ColumnIdent; import io.crate.metadata.PartitionName; import io.crate.metadata.Reference.IndexType; import io.crate.metadata.Routing; import io.crate.metadata.TableIdent; import io.crate.metadata.doc.DocTableInfo; import io.crate.metadata.table.ColumnPolicy; import io.crate.metadata.table.TestingTableInfo; import io.crate.operation.scalar.arithmetic.ArithmeticFunctions; import io.crate.test.integration.CrateDummyClusterServiceUnitTest; import io.crate.testing.SQLExecutor; import io.crate.types.DataTypes; import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.collect.MapBuilder; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import java.util.*; import static io.crate.analyze.TableDefinitions.SHARD_ROUTING; import static io.crate.analyze.TableDefinitions.USER_TABLE_IDENT; import static io.crate.testing.SymbolMatchers.*; import static org.hamcrest.Matchers.*; import static org.hamcrest.core.Is.is; public class InsertFromValuesAnalyzerTest extends CrateDummyClusterServiceUnitTest { private final TableIdent testAliasTableIdent = new TableIdent(null, "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 nestedClusteredTableIdent = new TableIdent(null, "nested_clustered"); private final DocTableInfo nestedClusteredTableInfo = new TestingTableInfo.Builder( nestedClusteredTableIdent, new Routing(ImmutableMap.<String, Map<String, List<Integer>>>of())) .add("o", DataTypes.OBJECT, null) .add("o", DataTypes.STRING, Arrays.asList("c")) .clusteredBy("o.c") .build(); private SQLExecutor e; @Before public void prepare() { SQLExecutor.Builder executorBuilder = SQLExecutor.builder(clusterService) .enableDefaultTables() .addDocTable(testAliasTableInfo) .addDocTable(nestedClusteredTableInfo); TableIdent notNullColumnTableIdent = new TableIdent(null, "not_null_column"); TestingTableInfo.Builder notNullColumnTable = new TestingTableInfo.Builder( notNullColumnTableIdent, new Routing(ImmutableMap.of())) .add("id", DataTypes.INTEGER, null) .add("name", DataTypes.STRING, null, ColumnPolicy.DYNAMIC, IndexType.NOT_ANALYZED, false, false); executorBuilder.addDocTable(notNullColumnTable); TableIdent generatedColumnTableIdent = new TableIdent(null, "generated_column"); TestingTableInfo.Builder generatedColumnTable = new TestingTableInfo.Builder( generatedColumnTableIdent, new Routing(ImmutableMap.<String, Map<String, List<Integer>>>of())) .add("ts", DataTypes.TIMESTAMP, null) .add("user", DataTypes.OBJECT, null) .add("user", DataTypes.STRING, Arrays.asList("name")) .addGeneratedColumn("day", DataTypes.TIMESTAMP, "date_trunc('day', ts)", false) .addGeneratedColumn("name", DataTypes.STRING, "concat(user['name'], 'bar')", false); executorBuilder.addDocTable(generatedColumnTable); TableIdent generatedPkColumnTableIdent = new TableIdent(null, "generated_pk_column"); TestingTableInfo.Builder generatedPkColumnTable = new TestingTableInfo.Builder( generatedPkColumnTableIdent, SHARD_ROUTING) .add("serial_no", DataTypes.INTEGER, null) .add("product_no", DataTypes.INTEGER, null) .add("color", DataTypes.STRING, null) .addGeneratedColumn("id", DataTypes.INTEGER, "serial_no + 1", false) .addGeneratedColumn("id2", DataTypes.INTEGER, "product_no + 1", false) .addPrimaryKey("id") .addPrimaryKey("id2"); executorBuilder.addDocTable(generatedPkColumnTable); TableIdent generatedClusteredByTableIdent = new TableIdent(null, "generated_clustered_by_column"); TestingTableInfo.Builder clusteredByGeneratedTable = new TestingTableInfo.Builder( generatedClusteredByTableIdent, SHARD_ROUTING) .add("serial_no", DataTypes.INTEGER, null) .add("color", DataTypes.STRING, null) .addGeneratedColumn("routing_col", DataTypes.INTEGER, "serial_no + 1", false) .clusteredBy("routing_col"); executorBuilder.addDocTable(clusteredByGeneratedTable); TableIdent generatedNestedClusteredByTableIdent = new TableIdent(null, "generated_nested_clustered_by"); TestingTableInfo.Builder generatedNestedClusteredByInfo = new TestingTableInfo.Builder( generatedNestedClusteredByTableIdent, SHARD_ROUTING) .add("o", DataTypes.OBJECT, null, ColumnPolicy.DYNAMIC) .add("o", DataTypes.INTEGER, Arrays.asList("serial_number")) .addGeneratedColumn("routing_col", DataTypes.INTEGER, "o['serial_number'] + 1", false) .clusteredBy("routing_col"); executorBuilder.addDocTable(generatedNestedClusteredByInfo); e = executorBuilder.build(); } @Test public void testInsertWithColumns() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze("insert into users (id, name) values (1, 'Trillian')"); assertThat(analysis.tableInfo().ident(), is(USER_TABLE_IDENT)); assertThat(analysis.columns().size(), is(2)); assertThat(analysis.columns().get(0).ident().columnIdent().name(), is("id")); assertEquals(DataTypes.LONG, analysis.columns().get(0).valueType()); assertThat(analysis.columns().get(1).ident().columnIdent().name(), is("name")); assertEquals(DataTypes.STRING, analysis.columns().get(1).valueType()); assertThat(analysis.sourceMaps().size(), is(1)); assertThat(analysis.sourceMaps().get(0).length, is(2)); assertThat((Long) analysis.sourceMaps().get(0)[0], is(1L)); assertThat((BytesRef) analysis.sourceMaps().get(0)[1], is(new BytesRef("Trillian"))); } @Test public void testInsertWithTwistedColumns() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze("insert into users (name, id) values ('Trillian', 2)"); assertThat(analysis.tableInfo().ident(), is(USER_TABLE_IDENT)); assertThat(analysis.columns().size(), is(2)); assertThat(analysis.columns().get(0).ident().columnIdent().name(), is("name")); assertEquals(DataTypes.STRING, analysis.columns().get(0).valueType()); assertThat(analysis.columns().get(1).ident().columnIdent().name(), is("id")); assertEquals(DataTypes.LONG, analysis.columns().get(1).valueType()); assertThat(analysis.sourceMaps().size(), is(1)); assertThat(analysis.sourceMaps().get(0).length, is(2)); assertThat((BytesRef) analysis.sourceMaps().get(0)[0], is(new BytesRef("Trillian"))); assertThat((Long) analysis.sourceMaps().get(0)[1], is(2L)); } @Test(expected = IllegalArgumentException.class) public void testInsertWithColumnsAndTooManyValues() throws Exception { e.analyze("insert into users (name, id) values ('Trillian', 2, true)"); } @Test(expected = IllegalArgumentException.class) public void testInsertWithColumnsAndTooLessValues() throws Exception { e.analyze("insert into users (name, id) values ('Trillian')"); } @Test(expected = ValidationException.class) public void testInsertWithWrongType() throws Exception { e.analyze("insert into users (name, id) values (1, 'Trillian')"); } @Test public void testInsertWithNumericTypeOutOfRange() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for bytes: 1234 cannot be cast to type byte"); e.analyze("insert into users (name, id, bytes) values ('Trillian', 4, 1234)"); } @Test(expected = ValidationException.class) public void testInsertWithWrongParameterType() throws Exception { e.analyze("insert into users (name, id) values (?, ?)", new Object[]{1, true}); } @Test(expected = IllegalArgumentException.class) public void testInsertSameReferenceRepeated() throws Exception { e.analyze("insert into users (name, name) values ('Trillian', 'Ford')"); } @Test public void testInsertWithConvertedTypes() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze( "insert into users (id, name, awesome) values ($1, 'Trillian', $2)", new Object[]{1.0f, "true"}); assertEquals(DataTypes.LONG, analysis.columns().get(0).valueType()); assertEquals(DataTypes.BOOLEAN, analysis.columns().get(2).valueType()); assertThat(analysis.sourceMaps().get(0).length, is(3)); assertThat((Long) analysis.sourceMaps().get(0)[0], is(1L)); assertThat((Boolean) analysis.sourceMaps().get(0)[2], is(true)); } @Test public void testInsertWithFunction() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze("insert into users (id, name) values (ABS(-1), 'Trillian')"); assertThat(analysis.tableInfo().ident(), is(USER_TABLE_IDENT)); assertThat(analysis.columns().size(), is(2)); assertThat(analysis.columns().get(0).ident().columnIdent().name(), is("id")); assertEquals(DataTypes.LONG, analysis.columns().get(0).valueType()); assertThat(analysis.columns().get(1).ident().columnIdent().name(), is("name")); assertEquals(DataTypes.STRING, analysis.columns().get(1).valueType()); assertThat(analysis.sourceMaps().size(), is(1)); assertThat(analysis.sourceMaps().get(0).length, is(2)); assertThat((Long) analysis.sourceMaps().get(0)[0], is(1L)); assertThat((BytesRef) analysis.sourceMaps().get(0)[1], is(new BytesRef("Trillian"))); } @Test public void testInsertWithoutColumns() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze("insert into users values (1, 1, 'Trillian')"); assertThat(analysis.tableInfo().ident(), is(USER_TABLE_IDENT)); assertThat(analysis.columns().size(), is(3)); assertThat(analysis.columns().get(0).ident().columnIdent().name(), is("id")); assertEquals(DataTypes.LONG, analysis.columns().get(0).valueType()); assertThat(analysis.columns().get(1).ident().columnIdent().name(), is("other_id")); assertEquals(DataTypes.LONG, analysis.columns().get(1).valueType()); assertThat(analysis.columns().get(2).ident().columnIdent().name(), is("name")); assertEquals(DataTypes.STRING, analysis.columns().get(2).valueType()); assertThat(analysis.sourceMaps().size(), is(1)); assertThat(analysis.sourceMaps().get(0).length, is(3)); assertThat((Long) analysis.sourceMaps().get(0)[0], is(1L)); assertThat((Long) analysis.sourceMaps().get(0)[1], is(1L)); assertThat((BytesRef) analysis.sourceMaps().get(0)[2], is(new BytesRef("Trillian"))); } @Test public void testInsertWithoutColumnsAndOnlyOneColumn() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze("insert into users values (1)"); assertThat(analysis.tableInfo().ident(), is(USER_TABLE_IDENT)); assertThat(analysis.columns().size(), is(1)); assertThat(analysis.columns().get(0).ident().columnIdent().name(), is("id")); assertEquals(DataTypes.LONG, analysis.columns().get(0).valueType()); assertThat(analysis.sourceMaps().size(), is(1)); assertThat(analysis.sourceMaps().get(0).length, is(1)); assertThat((Long) analysis.sourceMaps().get(0)[0], is(1L)); } @Test(expected = UnsupportedOperationException.class) public void testInsertIntoSysTable() throws Exception { e.analyze("insert into sys.nodes (id, name) values (666, 'evilNode')"); } @Test public void testInsertIntoAliasTable() throws Exception { expectedException.expect(UnsupportedOperationException.class); expectedException.expectMessage("The relation \"doc.alias\" doesn't support or allow INSERT " + "operations, as it is read-only."); e.analyze("insert into alias (bla) values ('blubb')"); } @Test(expected = IllegalArgumentException.class) public void testInsertWithoutPrimaryKey() throws Exception { e.analyze("insert into users (name) values ('Trillian')"); } @Test(expected = IllegalArgumentException.class) public void testNullPrimaryKey() throws Exception { e.analyze("insert into users (id) values (?)", new Object[]{null}); } @Test public void testNullLiterals() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze("insert into users (id, name, awesome, details) values (?, ?, ?, ?)", new Object[]{1, null, null, null}); assertThat(analysis.sourceMaps().get(0).length, is(4)); assertNull(analysis.sourceMaps().get(0)[1]); assertNull(analysis.sourceMaps().get(0)[2]); assertNull(analysis.sourceMaps().get(0)[3]); } @Test public void testObjectLiterals() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze("insert into users (id, name, awesome, details) values (?, ?, ?, ?)", new Object[]{1, null, null, new HashMap<String, Object>() {{ put("new_col", "new value"); }}}); assertThat(analysis.sourceMaps().get(0).length, is(4)); assertThat((Long) analysis.sourceMaps().get(0)[0], is(1L)); assertThat(analysis.sourceMaps().get(0)[3], instanceOf(Map.class)); } @Test(expected = ColumnValidationException.class) public void testInsertArrays() throws Exception { // error because in the schema are non-array types: e.analyze("insert into users (id, name, awesome, details) values (?, ?, ?, ?)", new Object[]{ new Long[]{1l, 2l}, new String[]{"Karl Liebknecht", "Rosa Luxemburg"}, new Boolean[]{true, false}, new Map[]{ new HashMap<String, Object>(), new HashMap<String, Object>() } } ); } public void testInsertNotNullConstraintAbsentColumn() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Cannot insert null value for column name"); e.analyze("insert into not_null_column (id) values (1)"); } @Test public void testInsertObjectArrayParameter() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze("insert into users (id, friends) values(?, ?)", new Object[]{0, new Map[]{ new HashMap<String, Object>() {{ put("name", "Jeltz"); }}, new HashMap<String, Object>() {{ put("name", "Prosser"); }} } }); assertThat(analysis.sourceMaps().get(0).length, is(2)); assertThat((Long) analysis.sourceMaps().get(0)[0], is(0L)); assertArrayEquals( (Object[]) analysis.sourceMaps().get(0)[1], new Object[]{ new MapBuilder<String, Object>().put("name", "Jeltz").map(), new MapBuilder<String, Object>().put("name", "Prosser").map(), } ); } @Test(expected = ColumnValidationException.class) public void testInsertInvalidObjectArrayParameter1() throws Exception { e.analyze("insert into users (id, friends) values(?, ?)", new Object[]{0, new Map[]{ new HashMap<String, Object>() {{ put("id", "Jeltz"); }} } }); } @Test(expected = ColumnValidationException.class) public void testInsertInvalidObjectArrayParameter2() throws Exception { e.analyze("insert into users (id, friends) values(?, ?)", new Object[]{0, new Map[]{ new HashMap<String, Object>() {{ put("id", 1L); put("groups", "a"); }} } }); } @Test public void testInsertInvalidObjectArrayInObject() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for details: invalid value for object array type"); e.analyze("insert into deeply_nested (details) " + "values (" + " {awesome=true, arguments=[1,2,3]}" + ")"); } @Test public void testInsertInvalidObjectArrayFieldInObjectArray() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for tags['metadata']['id']: Invalid long"); e.analyze("insert into deeply_nested (tags) " + "values (" + " [" + " {name='right', metadata=[{id=1}, {id=2}]}," + " {name='wrong', metadata=[{id='foo'}]}" + " ]" + ")"); } @Test public void testInsertNestedObjectLiteral() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze( "insert into deeply_nested (tags) " + "values ([" + " {\"name\"='cool', \"metadata\"=[{\"id\"=0}, {\"id\"=1}]}, " + " {\"name\"='fancy', \"metadata\"=[{\"id\"='2'}, {\"id\"=3}]}" + " ])"); assertThat(analysis.sourceMaps().size(), is(1)); Object[] arrayValue = (Object[]) analysis.sourceMaps().get(0)[0]; assertThat(arrayValue.length, is(2)); assertThat(arrayValue[0], instanceOf(Map.class)); assertThat((BytesRef) ((Map) arrayValue[0]).get("name"), is(new BytesRef("cool"))); assertThat((BytesRef) ((Map) arrayValue[1]).get("name"), is(new BytesRef("fancy"))); assertThat(Arrays.toString(((Object[]) ((Map) arrayValue[0]).get("metadata"))), is("[{id=0}, {id=1}]")); assertThat(Arrays.toString(((Object[]) ((Map) arrayValue[1]).get("metadata"))), is("[{id=2}, {id=3}]")); } @Test public void testInsertEmptyObjectArrayParameter() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze("insert into users (id, friends) values(?, ?)", new Object[]{0, new Map[0]}); assertThat((Long) analysis.sourceMaps().get(0)[0], is(0L)); assertThat(((Object[]) analysis.sourceMaps().get(0)[1]).length, is(0)); } @Test(expected = IllegalArgumentException.class) public void testInsertSystemColumn() throws Exception { e.analyze("insert into users (id, _id) values (?, ?)", new Object[]{1, "1"}); } @Test public void testNestedPk() throws Exception { // FYI: insert nested clustered by test here too InsertFromValuesAnalyzedStatement analysis = e.analyze("insert into nested_pk (id, o) values (?, ?)", new Object[]{1, new MapBuilder<String, Object>().put("b", 4).map()}); assertThat(analysis.ids().size(), is(1)); assertThat(analysis.ids().get(0), is(generateId(Arrays.asList(new ColumnIdent("id"), new ColumnIdent("o.b")), Arrays.asList(new BytesRef("1"), new BytesRef("4")), new ColumnIdent("o.b")))); } @Test public void testNestedPkAllColumns() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze("insert into nested_pk values (?, ?)", new Object[]{1, new MapBuilder<String, Object>().put("b", 4).map()}); assertThat(analysis.ids().size(), is(1)); assertThat(analysis.ids().get(0), is(generateId(Arrays.asList(new ColumnIdent("id"), new ColumnIdent("o.b")), Arrays.asList(new BytesRef("1"), new BytesRef("4")), new ColumnIdent("o.b")))); } @Test(expected = IllegalArgumentException.class) public void testMissingNestedPk() throws Exception { e.analyze("insert into nested_pk (id) values (?)", new Object[]{1}); } @Test(expected = IllegalArgumentException.class) public void testMissingNestedPkInMap() throws Exception { e.analyze("insert into nested_pk (id, o) values (?, ?)", new Object[]{1, new HashMap<String, Object>()}); } @Test public void testTwistedNestedPk() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze("insert into nested_pk (o, id) values (?, ?)", new Object[]{new MapBuilder<String, Object>().put("b", 4).map(), 1}); assertThat(analysis.ids().get(0), is(generateId(Arrays.asList(new ColumnIdent("id"), new ColumnIdent("o.b")), Arrays.asList(new BytesRef("1"), new BytesRef("4")), new ColumnIdent("o.b")))); } private String generateId(List<ColumnIdent> pkColumns, List<BytesRef> pkValues, ColumnIdent clusteredBy) { return Id.compileWithNullValidation(pkColumns, clusteredBy).apply(pkValues); } @Test public void testInsertMultipleValues() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze( "insert into users (id, name, awesome) values (?, ?, ?), (?, ?, ?)", new Object[]{99, "Marvin", true, 42, "Deep Thought", false}); assertThat(analysis.sourceMaps().size(), is(2)); assertThat((Long) analysis.sourceMaps().get(0)[0], is(99L)); assertThat((BytesRef) analysis.sourceMaps().get(0)[1], is(new BytesRef("Marvin"))); assertThat((Boolean) analysis.sourceMaps().get(0)[2], is(true)); assertThat((Long) analysis.sourceMaps().get(1)[0], is(42L)); assertThat((BytesRef) analysis.sourceMaps().get(1)[1], is(new BytesRef("Deep Thought"))); assertThat((Boolean) analysis.sourceMaps().get(1)[2], is(false)); } @Test public void testInsertPartitionedTable() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze( "insert into parted (id, name, date) " + "values (?, ?, ?)", new Object[]{0, "Trillian", 0L}); assertThat(analysis.sourceMaps().size(), is(1)); assertThat(analysis.sourceMaps().get(0).length, is(3)); assertThat(analysis.columns().size(), is(3)); assertThat(analysis.partitionMaps().size(), is(1)); assertThat(analysis.partitionMaps().get(0), hasEntry("date", "0")); } @Test public void testInsertIntoPartitionedTableOnlyPartitionColumns() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze( "insert into parted (date) " + "values (?)", new Object[]{0L}); assertThat(analysis.sourceMaps().size(), is(1)); assertThat(analysis.sourceMaps().get(0).length, is(1)); assertThat(analysis.columns().size(), is(1)); assertThat(analysis.partitionMaps().size(), is(1)); assertThat(analysis.partitionMaps().get(0), hasEntry("date", "0")); } @Test public void bulkIndexPartitionedTable() throws Exception { // multiple values InsertFromValuesAnalyzedStatement analysis = e.analyze( "insert into parted (id, name, date) " + "values (?, ?, ?), (?, ?, ?), (?, ?, ?)", new Object[]{ 1, "Trillian", 13963670051500L, 2, "Ford", 0L, 3, "Zaphod", null }); validateBulkIndexPartitionedTableAnalysis(analysis); // bulk args analysis = e.analyze("insert into parted (id, name, date) " + "values (?, ?, ?)", new Object[][]{ new Object[]{1, "Trillian", 13963670051500L}, new Object[]{2, "Ford", 0L}, new Object[]{3, "Zaphod", null} }); validateBulkIndexPartitionedTableAnalysis(analysis); } private void validateBulkIndexPartitionedTableAnalysis(InsertFromValuesAnalyzedStatement analysis) { assertThat(analysis.generatePartitions(), contains( new PartitionName("parted", Arrays.asList(new BytesRef("13963670051500"))).asIndexName(), new PartitionName("parted", Arrays.asList(new BytesRef("0"))).asIndexName(), new PartitionName("parted", new ArrayList<BytesRef>() {{ add(null); }}).asIndexName() )); assertThat(analysis.sourceMaps().size(), is(3)); assertThat((Integer) analysis.sourceMaps().get(0)[0], is(1)); assertThat((BytesRef) analysis.sourceMaps().get(0)[1], is(new BytesRef("Trillian"))); assertThat((Integer) analysis.sourceMaps().get(1)[0], is(2)); assertThat((BytesRef) analysis.sourceMaps().get(1)[1], is(new BytesRef("Ford"))); assertThat((Integer) analysis.sourceMaps().get(2)[0], is(3)); assertThat((BytesRef) analysis.sourceMaps().get(2)[1], is(new BytesRef("Zaphod"))); assertThat(analysis.partitionMaps().size(), is(3)); assertThat(analysis.partitionMaps().get(0), hasEntry("date", "13963670051500")); assertThat(analysis.partitionMaps().get(1), hasEntry("date", "0")); assertThat(analysis.partitionMaps().get(2), hasEntry("date", null)); } @Test public void testInsertWithMatchPredicateInValues() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for awesome: " + "Invalid value 'match({\"name\"=NULL}, 'bar', 'best_fields', {})' in insert statement"); e.analyze("insert into users (id, awesome) values (1, match(name, 'bar'))"); } @Test public void testInsertNestedPartitionedColumn() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze( "insert into nested_parted (id, date, obj)" + "values (?, ?, ?), (?, ?, ?)", new Object[]{ 1, "1970-01-01", new MapBuilder<String, Object>().put("name", "Zaphod").map(), 2, "2014-05-21", new MapBuilder<String, Object>().put("name", "Arthur").map() }); assertThat(analysis.generatePartitions(), contains( new PartitionName("nested_parted", Arrays.asList(new BytesRef("0"), new BytesRef("Zaphod"))).asIndexName(), new PartitionName("nested_parted", Arrays.asList(new BytesRef("1400630400000"), new BytesRef("Arthur"))).asIndexName() )); assertThat(analysis.sourceMaps().size(), is(2)); } @Test public void testInsertWithBulkArgs() throws Exception { InsertFromValuesAnalyzedStatement analysis; analysis = e.analyze( "insert into users (id, name) values (?, ?)", new Object[][]{ new Object[]{1, "foo"}, new Object[]{2, "bar"} }); assertThat(analysis.sourceMaps().size(), is(2)); assertThat((Long) analysis.sourceMaps().get(0)[0], is(1L)); assertThat((Long) analysis.sourceMaps().get(1)[0], is(2L)); } @Test public void testInsertWithBulkArgsMultiValue() throws Exception { // should be equal to testInsertWithBulkArgs() InsertFromValuesAnalyzedStatement analysis; analysis = e.analyze( "insert into users (id, name) values (?, ?), (?, ?)", new Object[][]{ {1, "foo", 2, "bar"} // one bulk row }); assertThat(analysis.sourceMaps().size(), is(2)); assertThat((Long) analysis.sourceMaps().get(0)[0], is(1L)); assertThat((Long) analysis.sourceMaps().get(1)[0], is(2L)); } @Test public void testInsertWithBulkArgsTypeMissMatch() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for id: '11!' cannot be cast to type long"); e.analyze("insert into users (id, name) values (?, ?)", new Object[][]{ new Object[]{10, "foo"}, new Object[]{"11!", "bar"} } ); } @Test public void testInsertWithBulkArgsMixedLength() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("mixed number of arguments inside bulk arguments"); e.analyze("insert into users (id, name) values (?, ?)", new Object[][]{ new Object[]{10, "foo"}, new Object[]{"11"} } ); } @Test public void testInsertWithBulkArgsNullValues() throws Exception { InsertFromValuesAnalyzedStatement analysis; analysis = e.analyze("insert into users (id, name) values (?, ?)", new Object[][]{ new Object[]{10, "foo"}, new Object[]{11, null} }); assertThat(analysis.sourceMaps().size(), is(2)); assertEquals(analysis.sourceMaps().get(0)[1], new BytesRef("foo")); assertEquals(analysis.sourceMaps().get(1)[1], null); } @Test public void testInsertWithBulkArgsNullValuesFirst() throws Exception { InsertFromValuesAnalyzedStatement analysis; analysis = e.analyze("insert into users (id, name) values (?, ?)", new Object[][]{ new Object[]{12, null}, new Object[]{13, "foo"}, }); assertThat(analysis.sourceMaps().size(), is(2)); assertEquals(analysis.sourceMaps().get(0)[1], null); assertEquals(analysis.sourceMaps().get(1)[1], new BytesRef("foo")); } @Test public void testInsertWithBulkArgsArrayNullValuesFirst() throws Exception { InsertFromValuesAnalyzedStatement analysis; analysis = e.analyze("insert into users (id, new_col) values (?, ?)", new Object[][]{ new Object[]{12, new String[]{null}}, new Object[]{13, new String[]{"foo"}}, }); assertThat(analysis.sourceMaps().size(), is(2)); assertThat((Object[]) analysis.sourceMaps().get(0)[1], arrayContaining((Object) null)); assertThat((Object[]) analysis.sourceMaps().get(1)[1], arrayContaining((Object) new BytesRef("foo"))); } @Test public void testInsertBulkArgWithFirstArgsContainsUnrecognizableObject() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage(Matchers.allOf( Matchers.startsWith("Got an argument \""), Matchers.endsWith("that couldn't be recognized") )); e.analyze("insert into users (id, name) values (?, ?)", new Object[][]{ new Object[]{new Foo()}, }); } @Test public void testInsertBulkArgWithUnrecognizableObject() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage(Matchers.allOf( Matchers.startsWith("Got an argument \""), Matchers.endsWith("that couldn't be recognized") )); e.analyze("insert into users (id, name) values (?, ?)", new Object[][]{ new Object[]{1, "Arthur"}, new Object[]{new Foo(), "Ford"}, }); } private static class Foo {} @Test public void testInsertWithTooFewArguments() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Tried to resolve a parameter but the arguments provided with the SQLRequest don't contain a parameter at position 1"); e.analyze("insert into users (id, name) values (?, ?)", new Object[]{1}); } @Test public void testInsertWithTooFewBulkArguments() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Tried to resolve a parameter but the arguments provided with the SQLRequest don't contain a parameter at position 0"); e.analyze("insert into users (id, name) values (?, ?)", new Object[][]{ new Object[]{}, new Object[]{} }); } @Test public void testInvalidTypeParamLiteral() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for tags: [['the', 'answer'], ['what''s', 'the', " + "'question', '?']] cannot be cast to type string_array"); e.analyze("insert into users (id, name, tags) values (42, 'Deep Thought', [['the', 'answer'], ['what''s', 'the', 'question', '?']])"); } @Test public void testInvalidTypeParam() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for tags: [['the', 'answer'], ['what''s', 'the', " + "'question', '?']] cannot be cast to type string_array"); e.analyze("insert into users (id, name, tags) values (42, 'Deep Thought', ?)", new Object[]{ new String[][]{ new String[]{"the", "answer"}, new String[]{"what's", "the", "question", "?"} } }); } @Test public void testInvalidTypeBulkParam() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for tags: [['the', 'answer'], ['what''s', 'the', " + "'question', '?']] cannot be cast to type string_array"); e.analyze("insert into users (id, name, tags) values (42, 'Deep Thought', ?)", new Object[][]{ new Object[]{ new String[][]{ new String[]{"the", "answer"}, new String[]{"what's", "the", "question", "?"} } } }); } @Test public void testDynamicNestedArrayParamLiteral() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze("insert into users (id, name, theses) " + "values (1, 'Marx', [['string1', 'string2']])"); assertThat(analysis.sourceMaps().size(), is(1)); assertThat((Long) analysis.sourceMaps().get(0)[0], is(1L)); assertThat((BytesRef) analysis.sourceMaps().get(0)[1], is(new BytesRef("Marx"))); assertThat((Object[]) ((Object[]) analysis.sourceMaps().get(0)[2])[0], arrayContaining(new Object[]{new BytesRef("string1"), new BytesRef("string2")})); } @Test public void testDynamicNestedArrayParam() throws Exception { e.analyze("insert into users (id, name, theses) values (1, 'Marx', ?)", new Object[]{ new String[][]{ new String[]{"string1"}, new String[]{"string2"} } }); InsertFromValuesAnalyzedStatement analysis = e.analyze("insert into users (id, name, theses) " + "values (1, 'Marx', [['string1', 'string2']])"); assertThat(analysis.sourceMaps().size(), is(1)); assertThat((Long) analysis.sourceMaps().get(0)[0], is(1L)); assertThat((BytesRef) analysis.sourceMaps().get(0)[1], is(new BytesRef("Marx"))); assertThat((Object[]) ((Object[]) analysis.sourceMaps().get(0)[2])[0], arrayContaining(new Object[]{new BytesRef("string1"), new BytesRef("string2")})); } @Test public void testDynamicNestedArrayBulkParam() throws Exception { e.analyze("insert into users (id, name, theses) values (1, 'Marx', ?)", new Object[][]{ new Object[]{ new String[][]{ new String[]{"string1"}, new String[]{"string2"} } } }); InsertFromValuesAnalyzedStatement analysis = e.analyze("insert into users (id, name, theses) " + "values (1, 'Marx', [['string1', 'string2']])"); assertThat(analysis.sourceMaps().size(), is(1)); assertThat((Long) analysis.sourceMaps().get(0)[0], is(1L)); assertThat((BytesRef) analysis.sourceMaps().get(0)[1], is(new BytesRef("Marx"))); assertThat((Object[]) ((Object[]) analysis.sourceMaps().get(0)[2])[0], arrayContaining(new Object[]{new BytesRef("string1"), new BytesRef("string2")})); } @Test public void testInvalidColumnName() throws Exception { expectedException.expect(InvalidColumnNameException.class); expectedException.expectMessage("column name \"newCol[\" is invalid"); e.analyze("insert into users (\"newCol[\") values(test)"); } @Test public void testInsertIntoTableWithNestedObjectPrimaryKeyAndNullInsert() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Primary key value must not be NULL"); e.analyze("insert into nested_pk (o) values (null)"); } @Test public void testNestedPrimaryKeyColumnMustNotBeNull() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Primary key value must not be NULL"); e.analyze("insert into nested_pk (o) values ({b=null})"); } @Test public void testNestedClusteredByColumnMustNotBeNull() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Clustered by value must not be NULL"); e.analyze("insert into nested_clustered (o) values ({c=null})"); } @Test public void testNestedClusteredByColumnMustNotBeNullWholeObject() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Clustered by value must not be NULL"); e.analyze("insert into nested_clustered (o) values (null)"); } @Test public void testInsertIntoTableWithNestedPartitionedByColumnAndNullValue() throws Exception { // caused an AssertionError before... now there should be an entry with value null in the partition map InsertFromValuesAnalyzedStatement statement = e.analyze( "insert into nested_parted (obj) values (null)"); assertThat(statement.partitionMaps().get(0).containsKey("obj.name"), is(true)); assertThat(statement.partitionMaps().get(0).get("obj.name"), nullValue()); } @Test public void testInsertFromValuesWithOnDuplicateKey() throws Exception { InsertFromValuesAnalyzedStatement statement = e.analyze( "insert into users (id, name, other_id) values (1, 'Arthur', 10) " + "on duplicate key update name = substr(values (name), 1, 2), " + "other_id = other_id + 100"); assertThat(statement.onDuplicateKeyAssignments().size(), is(1)); Symbol[] assignments = statement.onDuplicateKeyAssignments().get(0); assertThat(assignments.length, is(2)); assertThat(assignments[0], isLiteral("Ar")); assertThat(assignments[1], isFunction(ArithmeticFunctions.Names.ADD)); Function function = (Function) assignments[1]; assertThat(function.arguments().get(0), isReference("other_id")); } @Test public void testInsertFromValuesWithOnDuplicateKeyInvalidColumnInValues() throws Exception { expectedException.expect(ColumnUnknownException.class); expectedException.expectMessage("Column does_not_exist unknown"); e.analyze("insert into users (id, name) values (1, 'Arthur') " + "on duplicate key update name = values (does_not_exist)"); } @Test public void testInsertFromValuesWithOnDuplicateKeyFunctionInValues() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage( "Argument to VALUES expression must reference a column that is part of the INSERT statement. random() is invalid"); e.analyze("insert into users (id, name) values (1, 'Arthur') " + "on duplicate key update name = values (random())"); } @Test public void testInsertFromValuesWithOnDupKeyValuesWithNotInsertedColumnRef() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Referenced column 'name' isn't part of the column list of the INSERT statement"); e.analyze("insert into users (id) values (1) on duplicate key update name = values(name)"); } @Test public void testInsertFromValuesWithOnDupKeyValuesWithReferenceToNull() throws Exception { InsertFromValuesAnalyzedStatement statement = e.analyze( "insert into users (id, name) values (1, null) on duplicate key update name = values(name)"); assertThat(statement.onDuplicateKeyAssignments().size(), is(1)); Symbol[] assignments = statement.onDuplicateKeyAssignments().get(0); assertThat(assignments.length, is(1)); assertThat(assignments[0], isLiteral(null, DataTypes.STRING)); } @Test public void testInsertFromValuesWithOnDupKeyValuesWithParams() throws Exception { InsertFromValuesAnalyzedStatement statement = e.analyze( "insert into users (id, name) values (1, ?) on duplicate key update name = values(name)", new Object[]{"foobar"}); assertThat(statement.onDuplicateKeyAssignments().size(), is(1)); Symbol[] assignments = statement.onDuplicateKeyAssignments().get(0); assertThat(assignments.length, is(1)); assertThat(assignments[0], isLiteral("foobar")); } @Test public void testInsertFromValuesWithOnDuplicateWithTwoRefsAndDifferentTypes() throws Exception { InsertFromValuesAnalyzedStatement statement = e.analyze( "insert into users (id, name) values (1, 'foobar') " + "on duplicate key update name = awesome"); assertThat(statement.onDuplicateKeyAssignments().size(), is(1)); Symbol symbol = statement.onDuplicateKeyAssignments().get(0)[0]; assertThat(symbol, isFunction("to_string")); } @Test public void testInsertFromMultipleValuesWithOnDuplicateKey() throws Exception { InsertFromValuesAnalyzedStatement statement = e.analyze( "insert into users (id, name) values (1, 'Arthur'), (2, 'Trillian') " + "on duplicate key update name = substr(values (name), 1, 1)"); assertThat(statement.onDuplicateKeyAssignments().size(), is(2)); Symbol[] assignments = statement.onDuplicateKeyAssignments().get(0); assertThat(assignments.length, is(1)); assertThat(assignments[0], isLiteral("A")); assignments = statement.onDuplicateKeyAssignments().get(1); assertThat(assignments.length, is(1)); assertThat(assignments[0], isLiteral("T")); } @Test public void testOnDuplicateKeyUpdateOnObjectColumn() throws Exception { InsertFromValuesAnalyzedStatement statement = e.analyze( "insert into users (id) values (1) on duplicate key update details['foo'] = 'foobar'"); assertThat(statement.onDuplicateKeyAssignments().size(), is(1)); Symbol[] assignments = statement.onDuplicateKeyAssignments().get(0); assertThat(assignments.length, is(1)); assertThat(assignments[0], isLiteral("foobar")); } @Test public void testInvalidLeftSideExpressionInOnDuplicateKey() throws Exception { expectedException.expect(IllegalArgumentException.class); e.analyze("insert into users (id, name) values (1, 'Arthur') on duplicate key update [1, 2] = 1"); } @Test public void testUpdateOnPartitionedColumnShouldRaiseAnError() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for date: Updating a partitioned-by column is not supported"); e.analyze("insert into parted (id) values (1) on duplicate key update date = 0"); } @Test public void testUpdateOnPrimaryKeyColumnShouldRaiseAnError() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for id: Updating a primary key is not supported"); e.analyze("insert into nested_pk (id, o) values (1, {b=1}) on duplicate key update id = 10"); } @Test public void testUpdateOnClusteredByColumnShouldRaiseAnError() throws Exception { expectedException.expect(ColumnValidationException.class); expectedException.expectMessage("Validation failed for id: Updating a clustered-by column is not supported"); e.analyze("insert into users (id) values (1) on duplicate key update id = 10"); } @Test public void testInsertWithGeneratedColumn() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze( "insert into generated_column (ts) values (?)", new Object[]{"2015-11-18T11:11:00"}); assertThat(analysis.sourceMaps().size(), is(1)); assertThat((Long) analysis.sourceMaps().get(0)[0], is(1447845060000L)); assertThat((Long) analysis.sourceMaps().get(0)[1], is(1447804800000L)); } @Test public void testInsertWithGeneratedColumnReferenceValueNullOrNotGiven() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze( "insert into generated_column (ts) values (?)", new Object[]{null}); assertThat(analysis.sourceMaps().size(), is(1)); Object[] values = analysis.sourceMaps().get(0); assertThat(values.length, is(3)); assertThat(values[0], nullValue()); // generated column 'day' assertThat(values[1], nullValue()); // generated column 'name' assertThat((BytesRef) values[2], is(new BytesRef("bar"))); } @Test public void testInsertWithGeneratedColumnWithValueGiven() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze( "insert into generated_column (ts, day) values (?, ?)", new Object[]{"2015-11-18T11:11:00", 1447804800000L}); assertThat(analysis.sourceMaps().size(), is(1)); assertThat((Long) analysis.sourceMaps().get(0)[0], is(1447845060000L)); assertThat((Long) analysis.sourceMaps().get(0)[1], is(1447804800000L)); } @Test public void testInsertWithGeneratedColumnWithInvalidValueGiven() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Given value 1449999900000 for generated column does not match defined generated expression value 1447804800000"); e.analyze("insert into generated_column (ts, day) values (?, ?)", new Object[]{"2015-11-18T11:11:00", 1449999900000L}); } @Test public void testInsertNullValueWithGeneratedColumn() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze( "insert into generated_column (day) values (?)", new Object[]{null}); assertThat(analysis.sourceMaps().size(), is(1)); assertThat(analysis.sourceMaps().get(0)[0], is(Matchers.nullValue())); } @Test public void testInsertMultipleValuesWithGeneratedColumn() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze( "INSERT INTO generated_column (ts, user) values ('1970-01-01', {name='Johnny'}), ('1989-11-09T08:30:00', {name='Egon'})"); assertThat(analysis.columns(), hasSize(4)); assertThat(analysis.columns(), contains(isReference("ts"), isReference("user"), isReference("day"), isReference("name"))); assertThat(analysis.sourceMaps(), hasSize(2)); assertThat(analysis.sourceMaps(), contains( Matchers.arrayContaining(0L, ImmutableMap.<String, Object>of("name", new BytesRef("Johnny")), 0L, new BytesRef("Johnnybar")), Matchers.arrayContaining(626603400000L, ImmutableMap.<String, Object>of("name", new BytesRef("Egon")), 626572800000L, new BytesRef("Egonbar")))); } @Test public void testInsertMultipleValuesWithGeneratedColumnGiven() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze( "INSERT INTO generated_column (ts, user, day) values ('1970-01-01', {name='Johnny'}, '1970-01-01'), ('1989-11-09T08:30:00', {name='Egon'}, '1989-11-09')"); assertThat(analysis.columns(), hasSize(4)); assertThat(analysis.columns(), contains(isReference("ts"), isReference("user"), isReference("day"), isReference("name"))); assertThat(analysis.sourceMaps(), hasSize(2)); assertThat(analysis.sourceMaps(), contains( Matchers.arrayContaining(0L, ImmutableMap.<String, Object>of("name", new BytesRef("Johnny")), 0L, new BytesRef("Johnnybar")), Matchers.arrayContaining(626603400000L, ImmutableMap.<String, Object>of("name", new BytesRef("Egon")), 626572800000L, new BytesRef("Egonbar")))); } @Test public void testInsertGeneratedPrimaryKeyColumn() throws Exception { InsertFromValuesAnalyzedStatement analysis = e.analyze( "INSERT INTO generated_pk_column (serial_no, product_no) values (1, 1)" ); assertThat(analysis.routingValues(), contains("AgEyATI=")); assertThat(analysis.ids().get(0), is(generateId(Arrays.asList(new ColumnIdent("id"), new ColumnIdent("id2")), Arrays.asList(new BytesRef("2"), new BytesRef("2")), new ColumnIdent("id")))); } @Test public void testInsertMultipleValuesTooManyValues() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("INSERT statement contains a VALUES clause with too many elements (3), expected (2)"); e.analyze("INSERT INTO users (id, name) values (1, 'Johnny'), (2, 'Egon', 1234)"); } @Test public void testInsertMultipleValuesWithGeneratedColumnAndTooFewValuesInSecondValues() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Invalid number of values: Got 2 columns specified but 1 values"); e.analyze("INSERT INTO generated_column (ts, user) values ('1970-01-01', {name='Johnny'}), ('1989-11-09T08:30:00')"); } @Test public void testGeneratedPrimaryKeyMissing() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Primary key is required but is missing from the insert statement"); e.analyze("INSERT INTO generated_pk_column (color) values ('green')"); } @Test public void testGeneratedKeyPrimaryKeyPartMissing() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Primary key value must not be NULL"); e.analyze("INSERT INTO generated_pk_column (serial_no) values (1)"); } @Test public void testGeneratedClusteredByMissing() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Clustered by value is required but is missing from the insert statement"); e.analyze("INSERT INTO generated_clustered_by_column (color) values ('black')"); } @Test public void testNestedGeneratedClusteredByMissing() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Clustered by value is required but is missing from the insert statement"); e.analyze("INSERT INTO generated_nested_clustered_by (name) values ('kill-o-zap blaster pistol')"); } @Test public void testNestedGeneratedClusteredBy() throws Exception { InsertFromValuesAnalyzedStatement statement = e.analyze("INSERT INTO generated_nested_clustered_by (o) values ({serial_number=1})"); assertThat(statement.routingValues(), contains(is("2"))); } @Test public void testInsertArrayLiteralWithOneNullValue() throws Exception { InsertFromValuesAnalyzedStatement stmt = e.analyze("insert into users (id, tags) values (1, ['foo', 'bar', null])"); assertThat(stmt.sourceMaps().get(0), is(new Object[]{1L, new Object[]{new BytesRef("foo"), new BytesRef("bar"), null}})); stmt = e.analyze("insert into users (id, tags) values (1, [null, 'foo', 'bar'])"); assertThat(stmt.sourceMaps().get(0), is(new Object[]{1L, new Object[]{null, new BytesRef("foo"), new BytesRef("bar")}})); } @Test public void testInsertArrayLiteralWithOnlyNullValues() throws Exception { InsertFromValuesAnalyzedStatement stmt = e.analyze("insert into users (id, tags) values (1, [null, null])"); assertThat(stmt.sourceMaps().get(0), is(new Object[]{1L, new Object[]{null, null}})); } }