io.crate.analyze.InsertFromValuesAnalyzerTest.java Source code

Java tutorial

Introduction

Here is the source code for io.crate.analyze.InsertFromValuesAnalyzerTest.java

Source

/*
 * 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.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.*;
import io.crate.metadata.sys.MetaDataSysModule;
import io.crate.metadata.table.SchemaInfo;
import io.crate.metadata.table.TableInfo;
import io.crate.metadata.table.TestingTableInfo;
import io.crate.operation.predicate.PredicateModule;
import io.crate.operation.scalar.ScalarFunctionModule;
import io.crate.operation.scalar.arithmetic.AddFunction;
import io.crate.testing.MockedClusterServiceModule;
import io.crate.types.DataTypes;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.inject.Module;
import org.hamcrest.Matchers;
import org.junit.Test;

import java.util.*;

import static io.crate.testing.TestingHelpers.*;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.core.Is.is;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class InsertFromValuesAnalyzerTest extends BaseAnalyzerTest {

    private static final TableIdent TEST_ALIAS_TABLE_IDENT = new TableIdent(null, "alias");
    private static final TableInfo TEST_ALIAS_TABLE_INFO = new TestingTableInfo.Builder(TEST_ALIAS_TABLE_IDENT,
            new Routing()).add("bla", DataTypes.STRING, null).isAlias(true).build();

    private static final TableIdent NESTED_CLUSTERED_TABLE_IDENT = new TableIdent(null, "nested_clustered");
    private static final TableInfo NESTED_CLUSTERED_TABLE_INFO = new TestingTableInfo.Builder(
            NESTED_CLUSTERED_TABLE_IDENT, new Routing()).add("o", DataTypes.OBJECT, null)
                    .add("o", DataTypes.STRING, Arrays.asList("c")).clusteredBy("o.c").build();

    static class TestMetaDataModule extends MetaDataModule {
        @Override
        protected void bindSchemas() {
            super.bindSchemas();
            SchemaInfo schemaInfo = mock(SchemaInfo.class);
            when(schemaInfo.getTableInfo(TEST_DOC_TABLE_IDENT.name())).thenReturn(userTableInfo);
            when(schemaInfo.getTableInfo(TEST_ALIAS_TABLE_IDENT.name())).thenReturn(TEST_ALIAS_TABLE_INFO);
            when(schemaInfo.getTableInfo(NESTED_PK_TABLE_IDENT.name())).thenReturn(nestedPkTableInfo);
            when(schemaInfo.getTableInfo(TEST_PARTITIONED_TABLE_IDENT.name()))
                    .thenReturn(TEST_PARTITIONED_TABLE_INFO);
            when(schemaInfo.getTableInfo(TEST_NESTED_PARTITIONED_TABLE_IDENT.name()))
                    .thenReturn(TEST_NESTED_PARTITIONED_TABLE_INFO);
            when(schemaInfo.getTableInfo(DEEPLY_NESTED_TABLE_IDENT.name())).thenReturn(DEEPLY_NESTED_TABLE_INFO);
            when(schemaInfo.getTableInfo(NESTED_CLUSTERED_TABLE_IDENT.name()))
                    .thenReturn(NESTED_CLUSTERED_TABLE_INFO);
            schemaBinder.addBinding(Schemas.DEFAULT_SCHEMA_NAME).toInstance(schemaInfo);
        }

        @Override
        protected void bindFunctions() {
            super.bindFunctions();
            functionBinder.addBinding(ABS_FUNCTION_INFO.ident()).to(AbsFunction.class);
        }
    }

    @Override
    protected List<Module> getModules() {
        List<Module> modules = super.getModules();
        modules.addAll(Arrays.<Module>asList(new MockedClusterServiceModule(), new TestMetaDataModule(),
                new MetaDataSysModule(), new ScalarFunctionModule(), new PredicateModule()));
        return modules;
    }

    @Test
    public void testInsertWithColumns() throws Exception {
        InsertFromValuesAnalyzedStatement analysis = (InsertFromValuesAnalyzedStatement) analyze(
                "insert into users (id, name) values (1, 'Trillian')");
        assertThat(analysis.tableInfo().ident(), is(TEST_DOC_TABLE_IDENT));
        assertThat(analysis.columns().size(), is(2));

        assertThat(analysis.columns().get(0).info().ident().columnIdent().name(), is("id"));
        assertEquals(DataTypes.LONG, analysis.columns().get(0).valueType());

        assertThat(analysis.columns().get(1).info().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 = (InsertFromValuesAnalyzedStatement) analyze(
                "insert into users (name, id) values ('Trillian', 2)");
        assertThat(analysis.tableInfo().ident(), is(TEST_DOC_TABLE_IDENT));
        assertThat(analysis.columns().size(), is(2));

        assertThat(analysis.columns().get(0).info().ident().columnIdent().name(), is("name"));
        assertEquals(DataTypes.STRING, analysis.columns().get(0).valueType());

        assertThat(analysis.columns().get(1).info().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 {
        analyze("insert into users (name, id) values ('Trillian', 2, true)");
    }

    @Test(expected = IllegalArgumentException.class)
    public void testInsertWithColumnsAndTooLessValues() throws Exception {
        analyze("insert into users (name, id) values ('Trillian')");
    }

    @Test(expected = ValidationException.class)
    public void testInsertWithWrongType() throws Exception {
        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 can not be cast to 'byte'");
        analyze("insert into users (name, id, bytes) values ('Trillian', 4, 1234)");
    }

    @Test(expected = ValidationException.class)
    public void testInsertWithWrongParameterType() throws Exception {
        analyze("insert into users (name, id) values (?, ?)", new Object[] { 1, true });
    }

    @Test(expected = IllegalArgumentException.class)
    public void testInsertSameReferenceRepeated() throws Exception {
        analyze("insert into users (name, name) values ('Trillian', 'Ford')");
    }

    @Test
    public void testInsertWithConvertedTypes() throws Exception {
        InsertFromValuesAnalyzedStatement analysis = (InsertFromValuesAnalyzedStatement) 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 = (InsertFromValuesAnalyzedStatement) analyze(
                "insert into users (id, name) values (ABS(-1), 'Trillian')");
        assertThat(analysis.tableInfo().ident(), is(TEST_DOC_TABLE_IDENT));
        assertThat(analysis.columns().size(), is(2));

        assertThat(analysis.columns().get(0).info().ident().columnIdent().name(), is("id"));
        assertEquals(DataTypes.LONG, analysis.columns().get(0).valueType());

        assertThat(analysis.columns().get(1).info().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 = (InsertFromValuesAnalyzedStatement) analyze(
                "insert into users values (1, 1, 'Trillian')");
        assertThat(analysis.tableInfo().ident(), is(TEST_DOC_TABLE_IDENT));
        assertThat(analysis.columns().size(), is(3));

        assertThat(analysis.columns().get(0).info().ident().columnIdent().name(), is("id"));
        assertEquals(DataTypes.LONG, analysis.columns().get(0).valueType());

        assertThat(analysis.columns().get(1).info().ident().columnIdent().name(), is("other_id"));
        assertEquals(DataTypes.LONG, analysis.columns().get(1).valueType());

        assertThat(analysis.columns().get(2).info().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 = (InsertFromValuesAnalyzedStatement) analyze(
                "insert into users values (1)");
        assertThat(analysis.tableInfo().ident(), is(TEST_DOC_TABLE_IDENT));
        assertThat(analysis.columns().size(), is(1));

        assertThat(analysis.columns().get(0).info().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 {
        analyze("insert into sys.nodes (id, name) values (666, 'evilNode')");
    }

    @Test
    public void testInsertIntoAliasTable() throws Exception {
        expectedException.expect(UnsupportedOperationException.class);
        expectedException.expectMessage("alias is an alias. Write, Drop or Alter operations are not supported");
        analyze("insert into alias (bla) values ('blubb')");
    }

    @Test(expected = IllegalArgumentException.class)
    public void testInsertWithoutPrimaryKey() throws Exception {
        analyze("insert into users (name) values ('Trillian')");
    }

    @Test(expected = IllegalArgumentException.class)
    public void testNullPrimaryKey() throws Exception {
        analyze("insert into users (id) values (?)", new Object[] { null });
    }

    @Test
    public void testNullLiterals() throws Exception {
        InsertFromValuesAnalyzedStatement analysis = (InsertFromValuesAnalyzedStatement) 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 = (InsertFromValuesAnalyzedStatement) 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:
        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>() } });
    }

    @Test
    public void testInsertObjectArrayParameter() throws Exception {
        InsertFromValuesAnalyzedStatement analysis = (InsertFromValuesAnalyzedStatement) 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 {
        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 {
        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");
        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");
        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 = (InsertFromValuesAnalyzedStatement) 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((String) ((Map) arrayValue[0]).get("name"), is("cool"));
        assertThat((String) ((Map) arrayValue[1]).get("name"), is("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 = (InsertFromValuesAnalyzedStatement) 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 {
        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 = (InsertFromValuesAnalyzedStatement) 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 = (InsertFromValuesAnalyzedStatement) 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 {
        analyze("insert into nested_pk (id) values (?)", new Object[] { 1 });
    }

    @Test(expected = IllegalArgumentException.class)
    public void testMissingNestedPkInMap() throws Exception {
        analyze("insert into nested_pk (id, o) values (?, ?)", new Object[] { 1, new HashMap<String, Object>() });
    }

    @Test
    public void testTwistedNestedPk() throws Exception {
        InsertFromValuesAnalyzedStatement analysis = (InsertFromValuesAnalyzedStatement) 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.compile(pkColumns, clusteredBy).apply(pkValues);
    }

    @Test
    public void testInsertMultipleValues() throws Exception {
        InsertFromValuesAnalyzedStatement analysis = (InsertFromValuesAnalyzedStatement) 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 = (InsertFromValuesAnalyzedStatement) 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 = (InsertFromValuesAnalyzedStatement) 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 = (InsertFromValuesAnalyzedStatement) analyze(
                "insert into parted (id, name, date) " + "values (?, ?, ?), (?, ?, ?), (?, ?, ?)",
                new Object[] { 1, "Trillian", 13963670051500L, 2, "Ford", 0L, 3, "Zaphod", null });
        validateBulkIndexPartitionedTableAnalysis(analysis);
        // bulk args
        analysis = (InsertFromValuesAnalyzedStatement) 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 of type 'MATCH_PREDICATE' in insert statement");
        analyze("insert into users (id, awesome) values (1, match(name, 'bar'))");
    }

    @Test
    public void testInsertNestedPartitionedColumn() throws Exception {
        InsertFromValuesAnalyzedStatement analysis = (InsertFromValuesAnalyzedStatement) 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 = (InsertFromValuesAnalyzedStatement) 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 = (InsertFromValuesAnalyzedStatement) 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!' can not be cast to 'long'");
        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");
        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 = (InsertFromValuesAnalyzedStatement) 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 = (InsertFromValuesAnalyzedStatement) 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 = (InsertFromValuesAnalyzedStatement) 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")));

        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")));

        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");
        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");
        analyze("insert into users (id, name) values (?, ?)", new Object[][] { new Object[] {}, new Object[] {} });
    }

    @Test
    public void testRejectNestedArrayLiteral() throws Exception {
        expectedException.expect(ColumnValidationException.class);
        expectedException.expectMessage("Validation failed for tags: Invalid datatype 'string_array_array'");

        analyze("insert into users (id, name, tags) values (42, 'Deep Thought', [['the', 'answer'], ['what''s', 'the', 'question', '?']])");

    }

    @Test
    public void testRejectNestedArrayParam() throws Exception {
        expectedException.expect(ColumnValidationException.class);
        expectedException.expectMessage("Validation failed for tags: Invalid datatype 'string_array_array'");

        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 testRejectNestedArrayBulkParam() throws Exception {
        expectedException.expect(ColumnValidationException.class);
        expectedException.expectMessage("Validation failed for tags: Invalid datatype 'string_array_array'");

        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 testRejectDynamicNestedArrayLiteral() throws Exception {
        expectedException.expect(ColumnValidationException.class);
        expectedException.expectMessage("Validation failed for theses: Invalid datatype 'string_array_array'");

        analyze("insert into users (id, name, theses) values (1, 'Marx', [["
                + "'Feuerbach, mit dem abstrakten Denken nicht zufrieden, appellirt an die sinnliche Anschauung', "
                + "'aber er fasst die Sinnlichkeit nicht als praktische, menschlich-sinnliche Thtigkeit']])");
    }

    @Test
    public void testRejectDynamicNestedArrayParam() throws Exception {
        expectedException.expect(ColumnValidationException.class);
        expectedException.expectMessage("Validation failed for theses: Invalid datatype 'string_array_array'");

        analyze("insert into users (id, name, theses) values (1, 'Marx', ?)", new Object[] { new String[][] {
                new String[] {
                        "Feuerbach, mit dem abstrakten Denken nicht zufrieden, appellirt an die sinnliche Anschauung" },
                new String[] {
                        "aber er fasst die Sinnlichkeit nicht als praktische, menschlich-sinnliche Thtigkeit" } } });
    }

    @Test
    public void testRejectDynamicNestedArrayBulkParam() throws Exception {
        expectedException.expect(ColumnValidationException.class);
        expectedException.expectMessage("Validation failed for theses: Invalid datatype 'string_array_array'");

        analyze("insert into users (id, name, theses) values (1, 'Marx', ?)",
                new Object[][] { new Object[] { new String[][] { new String[] {
                        "Feuerbach, mit dem abstrakten Denken nicht zufrieden, appellirt an die sinnliche Anschauung" },
                        new String[] {
                                "aber er fasst die Sinnlichkeit nicht als praktische, menschlich-sinnliche Thtigkeit" } } } });
    }

    @Test
    public void testInvalidColumnName() throws Exception {
        expectedException.expect(InvalidColumnNameException.class);
        expectedException.expectMessage("column name \"newCol[\" is invalid");
        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");
        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");
        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");
        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");
        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 = ((InsertFromValuesAnalyzedStatement) 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 = (InsertFromValuesAnalyzedStatement) 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(AddFunction.NAME));
        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");
        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");
        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");
        analyze("insert into users (id) values (1) on duplicate key update name = values(name)");
    }

    @Test
    public void testInsertFromValuesWithOnDupKeyValuesWithReferenceToNull() throws Exception {
        InsertFromValuesAnalyzedStatement statement = (InsertFromValuesAnalyzedStatement) 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 = (InsertFromValuesAnalyzedStatement) 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 = (InsertFromValuesAnalyzedStatement) 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("toString"));
    }

    @Test
    public void testInsertFromMultipleValuesWithOnDuplicateKey() throws Exception {
        InsertFromValuesAnalyzedStatement statement = (InsertFromValuesAnalyzedStatement) 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 = (InsertFromValuesAnalyzedStatement) 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);
        analyze("insert into users (id, name) values (1, 'Arthur') on duplicate key update [1, 2] = 1");
    }

    @Test
    public void testUpdateOnPartitionedColumnShouldRaiseAnError() throws Exception {
        expectedException.expect(UnsupportedOperationException.class);
        expectedException.expectMessage("Updating a partitioned-by column is not supported");
        analyze("insert into parted (id) values (1) on duplicate key update date = 0");
    }

    @Test
    public void testUpdateOnPrimaryKeyColumnShouldRaiseAnError() throws Exception {
        expectedException.expect(UnsupportedOperationException.class);
        expectedException.expectMessage("Updating a primary key is not supported");
        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(UnsupportedOperationException.class);
        expectedException.expectMessage("Updating a clustered-by column is not supported");
        analyze("insert into users (id) values (1) on duplicate key update id = 10");
    }
}