/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.cassandra.cql3.validation.operations;

import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.List;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.tcm.membership.NodeVersion;

/* InsertUpdateIfConditionCollectionsTest class has been split into multiple ones because of timeout issues (CASSANDRA-16670)
 * Any changes here check if they apply to the other classes
 * - InsertUpdateIfConditionStaticsTest
 * - InsertUpdateIfConditionCollectionsTest
 * - InsertUpdateIfConditionTest
 */
@RunWith(Parameterized.class)
public class InsertUpdateIfConditionCollectionsTest extends CQLTester
{
    @Parameterized.Parameters(name = "{index}: clusterMinVersion={0}")
    public static Collection<Object[]> data()
    {
        return InsertUpdateIfConditionTest.data();
    }

    @Parameterized.Parameter(0)
    public NodeVersion clusterMinVersion;

    @BeforeClass
    public static void setUpClass()
    {
        InsertUpdateIfConditionTest.setUpClass();
    }

    @Before
    public void before()
    {
        InsertUpdateIfConditionTest.beforeSetup(clusterMinVersion);
    }

    /**
     * Migrated from cql_tests.py:TestCQL.bug_6069_test()
     */
    @Test
    public void testInsertSetIfNotExists() throws Throwable
    {
        createTable("CREATE TABLE %s (k int PRIMARY KEY, s set<int>)");

        assertRows(execute("INSERT INTO %s (k, s) VALUES (0, {1, 2, 3}) IF NOT EXISTS"),
                   row(true));
        assertRows(execute("SELECT * FROM %s "), row(0, set(1, 2, 3)));
    }

    @Test
    public void testWholeUDT() throws Throwable
    {
        String typename = createType("CREATE TYPE %s (a int, b text)");
        String myType = KEYSPACE + '.' + typename;

        for (boolean frozen : new boolean[] {false, true})
        {
            createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, v %s)",
                                      frozen
                                      ? "frozen<" + myType + ">"
                                      : myType));

            Object v = userType("a", 0, "b", "abc");
            execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);

            checkAppliesUDT("v = {a: 0, b: 'abc'}", v);
            checkAppliesUDT("v != null", v);
            checkAppliesUDT("v != {a: 1, b: 'abc'}", v);
            checkAppliesUDT("v != {a: 0, b: 'def'}", v);
            checkAppliesUDT("v > {a: -1, b: 'abc'}", v);
            checkAppliesUDT("v > {a: 0, b: 'aaa'}", v);
            checkAppliesUDT("v > {a: 0}", v);
            checkAppliesUDT("v >= {a: 0, b: 'aaa'}", v);
            checkAppliesUDT("v >= {a: 0, b: 'abc'}", v);
            checkAppliesUDT("v < {a: 0, b: 'zzz'}", v);
            checkAppliesUDT("v < {a: 1, b: 'abc'}", v);
            checkAppliesUDT("v < {a: 1}", v);
            checkAppliesUDT("v <= {a: 0, b: 'zzz'}", v);
            checkAppliesUDT("v <= {a: 0, b: 'abc'}", v);
            checkAppliesUDT("v IN (null, {a: 0, b: 'abc'}, {a: 1})", v);

            // multiple conditions
            checkAppliesUDT("v > {a: -1, b: 'abc'} AND v > {a: 0}", v);
            checkAppliesUDT("v != null AND v IN ({a: 0, b: 'abc'})", v);

            // should not apply
            checkDoesNotApplyUDT("v = {a: 0, b: 'def'}", v);
            checkDoesNotApplyUDT("v = {a: 1, b: 'abc'}", v);
            checkDoesNotApplyUDT("v = null", v);
            checkDoesNotApplyUDT("v != {a: 0, b: 'abc'}", v);
            checkDoesNotApplyUDT("v > {a: 1, b: 'abc'}", v);
            checkDoesNotApplyUDT("v > {a: 0, b: 'zzz'}", v);
            checkDoesNotApplyUDT("v >= {a: 1, b: 'abc'}", v);
            checkDoesNotApplyUDT("v >= {a: 0, b: 'zzz'}", v);
            checkDoesNotApplyUDT("v < {a: -1, b: 'abc'}", v);
            checkDoesNotApplyUDT("v < {a: 0, b: 'aaa'}", v);
            checkDoesNotApplyUDT("v <= {a: -1, b: 'abc'}", v);
            checkDoesNotApplyUDT("v <= {a: 0, b: 'aaa'}", v);
            checkDoesNotApplyUDT("v IN ({a: 0}, {b: 'abc'}, {a: 0, b: 'def'}, null)", v);
            checkDoesNotApplyUDT("v IN ()", v);

            // multiple conditions
            checkDoesNotApplyUDT("v IN () AND v IN ({a: 0, b: 'abc'})", v);
            checkDoesNotApplyUDT("v > {a: 0, b: 'aaa'} AND v < {a: 0, b: 'aaa'}", v);

            // invalid conditions
            checkInvalidUDT("v = {a: 1, b: 'abc', c: 'foo'}", v, InvalidRequestException.class);
            checkInvalidUDT("v = {foo: 'foo'}", v, InvalidRequestException.class);
            checkInvalidUDT("v < {a: 1, b: 'abc', c: 'foo'}", v, InvalidRequestException.class);
            checkInvalidUDT("v < null", v, InvalidRequestException.class);
            checkInvalidUDT("v <= {a: 1, b: 'abc', c: 'foo'}", v, InvalidRequestException.class);
            checkInvalidUDT("v <= null", v, InvalidRequestException.class);
            checkInvalidUDT("v > {a: 1, b: 'abc', c: 'foo'}", v, InvalidRequestException.class);
            checkInvalidUDT("v > null", v, InvalidRequestException.class);
            checkInvalidUDT("v >= {a: 1, b: 'abc', c: 'foo'}", v, InvalidRequestException.class);
            checkInvalidUDT("v >= null", v, InvalidRequestException.class);
            checkInvalidUDT("v IN null", v, SyntaxException.class);
            checkInvalidUDT("v IN 367", v, SyntaxException.class);
            checkInvalidUDT("v CONTAINS KEY 123", v, InvalidRequestException.class);
            checkInvalidUDT("v CONTAINS 'bar'", v, InvalidRequestException.class);

            /////////////////// null suffix on stored udt ////////////////////
            v = userType("a", 0, "b", null);
            execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);

            checkAppliesUDT("v = {a: 0}", v);
            checkAppliesUDT("v = {a: 0, b: null}", v);
            checkAppliesUDT("v != null", v);
            checkAppliesUDT("v != {a: 1, b: null}", v);
            checkAppliesUDT("v != {a: 1}", v);
            checkAppliesUDT("v != {a: 0, b: 'def'}", v);
            checkAppliesUDT("v > {a: -1, b: 'abc'}", v);
            checkAppliesUDT("v > {a: -1}", v);
            checkAppliesUDT("v >= {a: 0}", v);
            checkAppliesUDT("v >= {a: -1, b: 'abc'}", v);
            checkAppliesUDT("v < {a: 0, b: 'zzz'}", v);
            checkAppliesUDT("v < {a: 1, b: 'abc'}", v);
            checkAppliesUDT("v < {a: 1}", v);
            checkAppliesUDT("v <= {a: 0, b: 'zzz'}", v);
            checkAppliesUDT("v <= {a: 0}", v);
            checkAppliesUDT("v IN (null, {a: 0, b: 'abc'}, {a: 0})", v);

            // multiple conditions
            checkAppliesUDT("v > {a: -1, b: 'abc'} AND v >= {a: 0}", v);
            checkAppliesUDT("v != null AND v IN ({a: 0}, {a: 0, b: null})", v);

            // should not apply
            checkDoesNotApplyUDT("v = {a: 0, b: 'def'}", v);
            checkDoesNotApplyUDT("v = {a: 1}", v);
            checkDoesNotApplyUDT("v = {b: 'abc'}", v);
            checkDoesNotApplyUDT("v = null", v);
            checkDoesNotApplyUDT("v != {a: 0}", v);
            checkDoesNotApplyUDT("v != {a: 0, b: null}", v);
            checkDoesNotApplyUDT("v > {a: 1, b: 'abc'}", v);
            checkDoesNotApplyUDT("v > {a: 0}", v);
            checkDoesNotApplyUDT("v >= {a: 1, b: 'abc'}", v);
            checkDoesNotApplyUDT("v >= {a: 1}", v);
            checkDoesNotApplyUDT("v < {a: -1, b: 'abc'}", v);
            checkDoesNotApplyUDT("v < {a: -1}", v);
            checkDoesNotApplyUDT("v < {a: 0}", v);
            checkDoesNotApplyUDT("v <= {a: -1, b: 'abc'}", v);
            checkDoesNotApplyUDT("v <= {a: -1}", v);
            checkDoesNotApplyUDT("v IN ({a: 1}, {b: 'abc'}, {a: 0, b: 'def'}, null)", v);
            checkDoesNotApplyUDT("v IN ()", v);

            // multiple conditions
            checkDoesNotApplyUDT("v IN () AND v IN ({a: 0})", v);
            checkDoesNotApplyUDT("v > {a: -1} AND v < {a: 0}", v);

            /////////////////// null prefix on stored udt ////////////////////
            v = userType("a", null, "b", "abc");
            execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);

            checkAppliesUDT("v = {a: null, b: 'abc'}", v);
            checkAppliesUDT("v = {b: 'abc'}", v);
            checkAppliesUDT("v != null", v);
            checkAppliesUDT("v != {a: 0, b: 'abc'}", v);
            checkAppliesUDT("v != {a: 0}", v);
            checkAppliesUDT("v != {b: 'def'}", v);
            checkAppliesUDT("v > {a: null, b: 'aaa'}", v);
            checkAppliesUDT("v > {b: 'aaa'}", v);
            checkAppliesUDT("v >= {a: null, b: 'aaa'}", v);
            checkAppliesUDT("v >= {b: 'abc'}", v);
            checkAppliesUDT("v < {a: null, b: 'zzz'}", v);
            checkAppliesUDT("v < {a: 0, b: 'abc'}", v);
            checkAppliesUDT("v < {a: 0}", v);
            checkAppliesUDT("v < {b: 'zzz'}", v);
            checkAppliesUDT("v <= {a: null, b: 'zzz'}", v);
            checkAppliesUDT("v <= {a: 0}", v);
            checkAppliesUDT("v <= {b: 'abc'}", v);
            checkAppliesUDT("v IN (null, {a: null, b: 'abc'}, {a: 0})", v);
            checkAppliesUDT("v IN (null, {a: 0, b: 'abc'}, {b: 'abc'})", v);

            // multiple conditions
            checkAppliesUDT("v > {b: 'aaa'} AND v >= {b: 'abc'}", v);
            checkAppliesUDT("v != null AND v IN ({a: 0}, {a: null, b: 'abc'})", v);

            // should not apply
            checkDoesNotApplyUDT("v = {a: 0, b: 'def'}", v);
            checkDoesNotApplyUDT("v = {a: 1}", v);
            checkDoesNotApplyUDT("v = {b: 'def'}", v);
            checkDoesNotApplyUDT("v = null", v);
            checkDoesNotApplyUDT("v != {b: 'abc'}", v);
            checkDoesNotApplyUDT("v != {a: null, b: 'abc'}", v);
            checkDoesNotApplyUDT("v > {a: 1, b: 'abc'}", v);
            checkDoesNotApplyUDT("v > {a: null, b: 'zzz'}", v);
            checkDoesNotApplyUDT("v > {b: 'zzz'}", v);
            checkDoesNotApplyUDT("v >= {a: null, b: 'zzz'}", v);
            checkDoesNotApplyUDT("v >= {a: 1}", v);
            checkDoesNotApplyUDT("v >= {b: 'zzz'}", v);
            checkDoesNotApplyUDT("v < {a: null, b: 'aaa'}", v);
            checkDoesNotApplyUDT("v < {b: 'aaa'}", v);
            checkDoesNotApplyUDT("v <= {a: null, b: 'aaa'}", v);
            checkDoesNotApplyUDT("v <= {b: 'aaa'}", v);
            checkDoesNotApplyUDT("v IN ({a: 1}, {a: 1, b: 'abc'}, {a: null, b: 'def'}, null)", v);
            checkDoesNotApplyUDT("v IN ()", v);

            // multiple conditions
            checkDoesNotApplyUDT("v IN () AND v IN ({b: 'abc'})", v);
            checkDoesNotApplyUDT("v IN () AND v IN ({a: null, b: 'abc'})", v);
            checkDoesNotApplyUDT("v > {a: -1} AND v < {a: 0}", v);

            /////////////////// null udt ////////////////////
            v = null;
            execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);

            checkAppliesUDT("v = null", v);
            checkAppliesUDT("v IN (null, {a: null, b: 'abc'}, {a: 0})", v);
            checkAppliesUDT("v IN (null, {a: 0, b: 'abc'}, {b: 'abc'})", v);

            // multiple conditions
            checkAppliesUDT("v = null AND v IN (null, {a: 0}, {a: null, b: 'abc'})", v);

            // should not apply
            checkDoesNotApplyUDT("v = {a: 0, b: 'def'}", v);
            checkDoesNotApplyUDT("v = {a: 1}", v);
            checkDoesNotApplyUDT("v = {b: 'def'}", v);
            checkDoesNotApplyUDT("v != null", v);
            checkDoesNotApplyUDT("v > {a: 1, b: 'abc'}", v);
            checkDoesNotApplyUDT("v > {a: null, b: 'zzz'}", v);
            checkDoesNotApplyUDT("v > {b: 'zzz'}", v);
            checkDoesNotApplyUDT("v >= {a: null, b: 'zzz'}", v);
            checkDoesNotApplyUDT("v >= {a: 1}", v);
            checkDoesNotApplyUDT("v >= {b: 'zzz'}", v);
            checkDoesNotApplyUDT("v < {a: null, b: 'aaa'}", v);
            checkDoesNotApplyUDT("v < {b: 'aaa'}", v);
            checkDoesNotApplyUDT("v <= {a: null, b: 'aaa'}", v);
            checkDoesNotApplyUDT("v <= {b: 'aaa'}", v);
            checkDoesNotApplyUDT("v IN ({a: 1}, {a: 1, b: 'abc'}, {a: null, b: 'def'})", v);
            checkDoesNotApplyUDT("v IN ()", v);

            // multiple conditions
            checkDoesNotApplyUDT("v IN () AND v IN ({b: 'abc'})", v);
            checkDoesNotApplyUDT("v > {a: -1} AND v < {a: 0}", v);

        }
    }

    @Test
    public void testUDTField() throws Throwable
    {
        String typename = createType("CREATE TYPE %s (a int, b text)");
        String myType = KEYSPACE + '.' + typename;

        for (boolean frozen : new boolean[] {false, true})
        {
            createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, v %s)",
                                      frozen
                                      ? "frozen<" + myType + ">"
                                      : myType));

            Object v = userType("a", 0, "b", "abc");
            execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);

            checkAppliesUDT("v.a = 0", v);
            checkAppliesUDT("v.b = 'abc'", v);
            checkAppliesUDT("v.a < 1", v);
            checkAppliesUDT("v.b < 'zzz'", v);
            checkAppliesUDT("v.b <= 'bar'", v);
            checkAppliesUDT("v.b > 'aaa'", v);
            checkAppliesUDT("v.b >= 'abc'", v);
            checkAppliesUDT("v.a != -1", v);
            checkAppliesUDT("v.b != 'xxx'", v);
            checkAppliesUDT("v.a != null", v);
            checkAppliesUDT("v.b != null", v);
            checkAppliesUDT("v.a IN (null, 0, 1)", v);
            checkAppliesUDT("v.b IN (null, 'xxx', 'abc')", v);
            checkAppliesUDT("v.b > 'aaa' AND v.b < 'zzz'", v);
            checkAppliesUDT("v.a = 0 AND v.b > 'aaa'", v);

            // do not apply
            checkDoesNotApplyUDT("v.a = -1", v);
            checkDoesNotApplyUDT("v.b = 'xxx'", v);
            checkDoesNotApplyUDT("v.a < -1", v);
            checkDoesNotApplyUDT("v.b < 'aaa'", v);
            checkDoesNotApplyUDT("v.b <= 'aaa'", v);
            checkDoesNotApplyUDT("v.b > 'zzz'", v);
            checkDoesNotApplyUDT("v.b >= 'zzz'", v);
            checkDoesNotApplyUDT("v.a != 0", v);
            checkDoesNotApplyUDT("v.b != 'abc'", v);
            checkDoesNotApplyUDT("v.a IN (null, -1)", v);
            checkDoesNotApplyUDT("v.b IN (null, 'xxx')", v);
            checkDoesNotApplyUDT("v.a IN ()", v);
            checkDoesNotApplyUDT("v.b IN ()", v);
            checkDoesNotApplyUDT("v.b != null AND v.b IN ()", v);

            // invalid
            checkInvalidUDT("v.c = null", v, InvalidRequestException.class);
            checkInvalidUDT("v.a < null", v, InvalidRequestException.class);
            checkInvalidUDT("v.a <= null", v, InvalidRequestException.class);
            checkInvalidUDT("v.a > null", v, InvalidRequestException.class);
            checkInvalidUDT("v.a >= null", v, InvalidRequestException.class);
            checkInvalidUDT("v.a IN null", v, SyntaxException.class);
            checkInvalidUDT("v.a IN 367", v, SyntaxException.class);
            checkInvalidUDT("v.b IN (1, 2, 3)", v, InvalidRequestException.class);
            checkInvalidUDT("v.a CONTAINS 367", v, SyntaxException.class);
            checkInvalidUDT("v.a CONTAINS KEY 367", v, SyntaxException.class);

            /////////////// null suffix on udt ////////////////
            v = userType("a", 0, "b", null);
            execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);

            checkAppliesUDT("v.a = 0", v);
            checkAppliesUDT("v.b = null", v);
            checkAppliesUDT("v.b != 'xxx'", v);
            checkAppliesUDT("v.a != null", v);
            checkAppliesUDT("v.a IN (null, 0, 1)", v);
            checkAppliesUDT("v.b IN (null, 'xxx', 'abc')", v);
            checkAppliesUDT("v.a = 0 AND v.b = null", v);

            // do not apply
            checkDoesNotApplyUDT("v.b = 'abc'", v);
            checkDoesNotApplyUDT("v.a < -1", v);
            checkDoesNotApplyUDT("v.b < 'aaa'", v);
            checkDoesNotApplyUDT("v.b <= 'aaa'", v);
            checkDoesNotApplyUDT("v.b > 'zzz'", v);
            checkDoesNotApplyUDT("v.b >= 'zzz'", v);
            checkDoesNotApplyUDT("v.a != 0", v);
            checkDoesNotApplyUDT("v.b != null", v);
            checkDoesNotApplyUDT("v.a IN (null, -1)", v);
            checkDoesNotApplyUDT("v.b IN ('xxx', 'abc')", v);
            checkDoesNotApplyUDT("v.a IN ()", v);
            checkDoesNotApplyUDT("v.b IN ()", v);
            checkDoesNotApplyUDT("v.b != null AND v.b IN ()", v);

            /////////////// null prefix on udt ////////////////
            v = userType("a", null, "b", "abc");
            execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);

            checkAppliesUDT("v.a = null", v);
            checkAppliesUDT("v.b = 'abc'", v);
            checkAppliesUDT("v.a != 0", v);
            checkAppliesUDT("v.b != null", v);
            checkAppliesUDT("v.a IN (null, 0, 1)", v);
            checkAppliesUDT("v.b IN (null, 'xxx', 'abc')", v);
            checkAppliesUDT("v.a = null AND v.b = 'abc'", v);

            // do not apply
            checkDoesNotApplyUDT("v.a = 0", v);
            checkDoesNotApplyUDT("v.a < -1", v);
            checkDoesNotApplyUDT("v.b >= 'zzz'", v);
            checkDoesNotApplyUDT("v.a != null", v);
            checkDoesNotApplyUDT("v.b != 'abc'", v);
            checkDoesNotApplyUDT("v.a IN (-1, 0)", v);
            checkDoesNotApplyUDT("v.b IN (null, 'xxx')", v);
            checkDoesNotApplyUDT("v.a IN ()", v);
            checkDoesNotApplyUDT("v.b IN ()", v);
            checkDoesNotApplyUDT("v.b != null AND v.b IN ()", v);

            /////////////// null udt ////////////////
            v = null;
            execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);

            checkAppliesUDT("v.a = null", v);
            checkAppliesUDT("v.b = null", v);
            checkAppliesUDT("v.a != 0", v);
            checkAppliesUDT("v.b != 'abc'", v);
            checkAppliesUDT("v.a IN (null, 0, 1)", v);
            checkAppliesUDT("v.b IN (null, 'xxx', 'abc')", v);
            checkAppliesUDT("v.a = null AND v.b = null", v);

            // do not apply
            checkDoesNotApplyUDT("v.a = 0", v);
            checkDoesNotApplyUDT("v.a < -1", v);
            checkDoesNotApplyUDT("v.b >= 'zzz'", v);
            checkDoesNotApplyUDT("v.a != null", v);
            checkDoesNotApplyUDT("v.b != null", v);
            checkDoesNotApplyUDT("v.a IN (-1, 0)", v);
            checkDoesNotApplyUDT("v.b IN ('xxx', 'abc')", v);
            checkDoesNotApplyUDT("v.a IN ()", v);
            checkDoesNotApplyUDT("v.b IN ()", v);
            checkDoesNotApplyUDT("v.b != null AND v.b IN ()", v);
        }
    }

    void checkAppliesUDT(String condition, Object value) throws Throwable
    {
        // UPDATE statement
        assertRows(execute("UPDATE %s SET v = ? WHERE k = 0 IF " + condition, value), row(true));
        assertRows(execute("SELECT * FROM %s"), row(0, value));
        // DELETE statement
        assertRows(execute("DELETE FROM %s WHERE k = 0 IF " + condition), row(true));
        assertEmpty(execute("SELECT * FROM %s"));
        execute("INSERT INTO %s (k, v) VALUES (0, ?)", value);
    }

    void checkDoesNotApplyUDT(String condition, Object value) throws Throwable
    {
        // UPDATE statement
        assertRows(execute("UPDATE %s SET v = ? WHERE k = 0 IF " + condition, value),
                   row(false, value));
        assertRows(execute("SELECT * FROM %s"), row(0, value));
        // DELETE statement
        assertRows(execute("DELETE FROM %s WHERE k = 0 IF " + condition),
                   row(false, value));
        assertRows(execute("SELECT * FROM %s"), row(0, value));
    }

    void checkInvalidUDT(String condition, Object value, Class<? extends Throwable> expected) throws Throwable
    {
        // UPDATE statement
        assertInvalidThrow(expected, "UPDATE %s SET v = ?  WHERE k = 0 IF " + condition, value);
        assertRows(execute("SELECT * FROM %s"), row(0, value));
        // DELETE statement
        assertInvalidThrow(expected, "DELETE FROM %s WHERE k = 0 IF " + condition);
        assertRows(execute("SELECT * FROM %s"), row(0, value));
    }

    /**
     * Migrated from cql_tests.py:TestCQL.whole_list_conditional_test()
     */
    @Test
    public void testWholeList() throws Throwable
    {
        for (boolean frozen : new boolean[] {false, true})
        {
            createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, l %s)",
                                      frozen
                                      ? "frozen<list<text>>"
                                      : "list<text>"));

            execute("INSERT INTO %s(k, l) VALUES (0, ['foo', 'bar', 'foobar'])");

            check_applies_list("l = ['foo', 'bar', 'foobar']");
            check_applies_list("l != ['baz']");
            check_applies_list("l > ['a']");
            check_applies_list("l >= ['a']");
            check_applies_list("l < ['z']");
            check_applies_list("l <= ['z']");
            check_applies_list("l IN (null, ['foo', 'bar', 'foobar'], ['a'])");
            check_applies_list("l CONTAINS 'bar'");

            // multiple conditions
            check_applies_list("l > ['aaa', 'bbb'] AND l > ['aaa']");
            check_applies_list("l != null AND l IN (['foo', 'bar', 'foobar'])");
            check_applies_list("l CONTAINS 'foo' AND l CONTAINS 'foobar'");

            // should not apply
            check_does_not_apply_list("l = ['baz']");
            check_does_not_apply_list("l != ['foo', 'bar', 'foobar']");
            check_does_not_apply_list("l > ['z']");
            check_does_not_apply_list("l >= ['z']");
            check_does_not_apply_list("l < ['a']");
            check_does_not_apply_list("l <= ['a']");
            check_does_not_apply_list("l IN (['a'], null)");
            check_does_not_apply_list("l IN ()");
            check_does_not_apply_list("l CONTAINS 'baz'");

            // multiple conditions
            check_does_not_apply_list("l IN () AND l IN (['foo', 'bar', 'foobar'])");
            check_does_not_apply_list("l > ['zzz'] AND l < ['zzz']");
            check_does_not_apply_list("l CONTAINS 'bar' AND l CONTAINS 'baz'");

            check_invalid_list("l = [null]", InvalidRequestException.class);
            check_invalid_list("l < null", InvalidRequestException.class);
            check_invalid_list("l <= null", InvalidRequestException.class);
            check_invalid_list("l > null", InvalidRequestException.class);
            check_invalid_list("l >= null", InvalidRequestException.class);
            check_invalid_list("l IN null", SyntaxException.class);
            check_invalid_list("l IN 367", SyntaxException.class);
            check_invalid_list("l CONTAINS KEY 123", InvalidRequestException.class);
            check_invalid_list("l CONTAINS null", InvalidRequestException.class);
        }
    }

    void check_applies_list(String condition) throws Throwable
    {
        // UPDATE statement
        assertRows(execute("UPDATE %s SET l = ['foo', 'bar', 'foobar'] WHERE k=0 IF " + condition), row(true));
        assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", "foobar")));
        // DELETE statement
        assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition), row(true));
        assertEmpty(execute("SELECT * FROM %s"));
        execute("INSERT INTO %s(k, l) VALUES (0, ['foo', 'bar', 'foobar'])");
    }

    void check_does_not_apply_list(String condition) throws Throwable
    {
        // UPDATE statement
        assertRows(execute("UPDATE %s SET l = ['foo', 'bar', 'foobar'] WHERE k=0 IF " + condition),
                   row(false, list("foo", "bar", "foobar")));
        assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", "foobar")));
        // DELETE statement
        assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition),
                   row(false, list("foo", "bar", "foobar")));
        assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", "foobar")));
    }

    void check_invalid_list(String condition, Class<? extends Throwable> expected) throws Throwable
    {
        // UPDATE statement
        assertInvalidThrow(expected, "UPDATE %s SET l = ['foo', 'bar', 'foobar'] WHERE k=0 IF " + condition);
        assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", "foobar")));
        // DELETE statement
        assertInvalidThrow(expected, "DELETE FROM %s WHERE k=0 IF " + condition);
        assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", "foobar")));
    }

    /**
     * Migrated from cql_tests.py:TestCQL.list_item_conditional_test()
     */
    @Test
    public void testListItem() throws Throwable
    {
        for (boolean frozen : new boolean[]{ false, true })
        {
            createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, l %s)",
                                      frozen
                                      ? "frozen<list<text>>"
                                      : "list<text>"));

            execute("INSERT INTO %s(k, l) VALUES (0, ['foo', 'bar', 'foobar'])");

            assertInvalidMessage("Invalid null value for list element access",
                                 "DELETE FROM %s WHERE k=0 IF l[?] = ?", null, "foobar");
            assertInvalidMessage("Invalid negative list index -2",
                                 "DELETE FROM %s WHERE k=0 IF l[?] = ?", -2, "foobar");
            assertInvalidSyntax("DELETE FROM %s WHERE k=0 IF l[?] CONTAINS ?", 0, "bar");
            assertInvalidSyntax("DELETE FROM %s WHERE k=0 IF l[?] CONTAINS KEY ?", 0, "bar");
            assertRows(execute("DELETE FROM %s WHERE k=0 IF l[?] = ?", 1, null), row(false, list("foo", "bar", "foobar")));
            assertRows(execute("DELETE FROM %s WHERE k=0 IF l[?] = ?", 1, "foobar"), row(false, list("foo", "bar", "foobar")));
            assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", "foobar")));

            assertRows(execute("DELETE FROM %s WHERE k=0 IF l[?] = ?", 1, "bar"), row(true));
            assertEmpty(execute("SELECT * FROM %s"));
        }
    }

    /**
     * Test expanded functionality from CASSANDRA-6839,
     * migrated from cql_tests.py:TestCQL.expanded_list_item_conditional_test()
     */
    @Test
    public void testExpandedListItem() throws Throwable
    {
        for (boolean frozen : new boolean[] {false, true})
        {
            createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, l %s)",
                                      frozen
                                      ? "frozen<list<text>>"
                                      : "list<text>"));

            execute("INSERT INTO %s (k, l) VALUES (0, ['foo', 'bar', 'foobar'])");

            check_applies_list("l[1] < 'zzz'");
            check_applies_list("l[1] <= 'bar'");
            check_applies_list("l[1] > 'aaa'");
            check_applies_list("l[1] >= 'bar'");
            check_applies_list("l[1] != 'xxx'");
            check_applies_list("l[1] != null");
            check_applies_list("l[1] IN (null, 'xxx', 'bar')");
            check_applies_list("l[1] > 'aaa' AND l[1] < 'zzz'");

            // check beyond end of list
            check_applies_list("l[3] = null");
            check_applies_list("l[3] IN (null, 'xxx', 'bar')");

            check_does_not_apply_list("l[1] < 'aaa'");
            check_does_not_apply_list("l[1] <= 'aaa'");
            check_does_not_apply_list("l[1] > 'zzz'");
            check_does_not_apply_list("l[1] >= 'zzz'");
            check_does_not_apply_list("l[1] != 'bar'");
            check_does_not_apply_list("l[1] IN (null, 'xxx')");
            check_does_not_apply_list("l[1] IN ()");
            check_does_not_apply_list("l[1] != null AND l[1] IN ()");

            // check beyond end of list
            check_does_not_apply_list("l[3] != null");
            check_does_not_apply_list("l[3] = 'xxx'");

            check_invalid_list("l[1] < null", InvalidRequestException.class);
            check_invalid_list("l[1] <= null", InvalidRequestException.class);
            check_invalid_list("l[1] > null", InvalidRequestException.class);
            check_invalid_list("l[1] >= null", InvalidRequestException.class);
            check_invalid_list("l[1] IN null", SyntaxException.class);
            check_invalid_list("l[1] IN 367", SyntaxException.class);
            check_invalid_list("l[1] IN (1, 2, 3)", InvalidRequestException.class);
            check_invalid_list("l[1] CONTAINS 367", SyntaxException.class);
            check_invalid_list("l[1] CONTAINS KEY 367", SyntaxException.class);
            check_invalid_list("l[null] = null", InvalidRequestException.class);
        }
    }

    /**
     * Migrated from cql_tests.py:TestCQL.whole_set_conditional_test()
     */
    @Test
    public void testWholeSet() throws Throwable
    {
        for (boolean frozen : new boolean[] {false, true})
        {
            createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, s %s)",
                                      frozen
                                      ? "frozen<set<text>>"
                                      : "set<text>"));

            execute("INSERT INTO %s (k, s) VALUES (0, {'bar', 'foo'})");

            check_applies_set("s = {'bar', 'foo'}");
            check_applies_set("s = {'foo', 'bar'}");
            check_applies_set("s != {'baz'}");
            check_applies_set("s > {'a'}");
            check_applies_set("s >= {'a'}");
            check_applies_set("s < {'z'}");
            check_applies_set("s <= {'z'}");
            check_applies_set("s IN (null, {'bar', 'foo'}, {'a'})");
            check_applies_set("s CONTAINS 'foo'");

            // multiple conditions
            check_applies_set("s > {'a'} AND s < {'z'}");
            check_applies_set("s IN (null, {'bar', 'foo'}, {'a'}) AND s IN ({'a'}, {'bar', 'foo'}, null)");
            check_applies_set("s CONTAINS 'foo' AND s CONTAINS 'bar'");

            // should not apply
            check_does_not_apply_set("s = {'baz'}");
            check_does_not_apply_set("s != {'bar', 'foo'}");
            check_does_not_apply_set("s > {'z'}");
            check_does_not_apply_set("s >= {'z'}");
            check_does_not_apply_set("s < {'a'}");
            check_does_not_apply_set("s <= {'a'}");
            check_does_not_apply_set("s IN ({'a'}, null)");
            check_does_not_apply_set("s IN ()");
            check_does_not_apply_set("s != null AND s IN ()");
            check_does_not_apply_set("s CONTAINS 'baz'");

            check_invalid_set("s = {null}", InvalidRequestException.class);
            check_invalid_set("s < null", InvalidRequestException.class);
            check_invalid_set("s <= null", InvalidRequestException.class);
            check_invalid_set("s > null", InvalidRequestException.class);
            check_invalid_set("s >= null", InvalidRequestException.class);
            check_invalid_set("s IN null", SyntaxException.class);
            check_invalid_set("s IN 367", SyntaxException.class);
            check_invalid_set("s CONTAINS null", InvalidRequestException.class);
            check_invalid_set("s CONTAINS KEY 123", InvalidRequestException.class);

            // element access is not allow for sets
            check_invalid_set("s['foo'] = 'foobar'", InvalidRequestException.class);
        }
    }

    void check_applies_set(String condition) throws Throwable
    {
        // UPDATE statement
        assertRows(execute("UPDATE %s SET s = {'bar', 'foo'} WHERE k=0 IF " + condition), row(true));
        assertRows(execute("SELECT * FROM %s"), row(0, set("bar", "foo")));
        // DELETE statement
        assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition), row(true));
        assertEmpty(execute("SELECT * FROM %s"));
        execute("INSERT INTO %s (k, s) VALUES (0, {'bar', 'foo'})");
    }

    void check_does_not_apply_set(String condition) throws Throwable
    {
        // UPDATE statement
        assertRows(execute("UPDATE %s SET s = {'bar', 'foo'} WHERE k=0 IF " + condition), row(false, set("bar", "foo")));
        assertRows(execute("SELECT * FROM %s"), row(0, set("bar", "foo")));
        // DELETE statement
        assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition), row(false, set("bar", "foo")));
        assertRows(execute("SELECT * FROM %s"), row(0, set("bar", "foo")));
    }

    void check_invalid_set(String condition, Class<? extends Throwable> expected) throws Throwable
    {
        // UPDATE statement
        assertInvalidThrow(expected, "UPDATE %s SET s = {'bar', 'foo'} WHERE k=0 IF " + condition);
        assertRows(execute("SELECT * FROM %s"), row(0, set("bar", "foo")));
        // DELETE statement
        assertInvalidThrow(expected, "DELETE FROM %s WHERE k=0 IF " + condition);
        assertRows(execute("SELECT * FROM %s"), row(0, set("bar", "foo")));
    }

    /**
     * Migrated from cql_tests.py:TestCQL.whole_map_conditional_test()
     */
    @Test
    public void testWholeMap() throws Throwable
    {
        for (boolean frozen : new boolean[] {false, true})
        {
            createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, m %s)",
                                      frozen
                                      ? "frozen<map<text, text>>"
                                      : "map<text, text>"));

            execute("INSERT INTO %s (k, m) VALUES (0, {'foo' : 'bar'})");

            check_applies_map("m = {'foo': 'bar'}");
            check_applies_map("m > {'a': 'a'}");
            check_applies_map("m >= {'a': 'a'}");
            check_applies_map("m < {'z': 'z'}");
            check_applies_map("m <= {'z': 'z'}");
            check_applies_map("m != {'a': 'a'}");
            check_applies_map("m IN (null, {'a': 'a'}, {'foo': 'bar'})");
            check_applies_map("m CONTAINS 'bar'");
            check_applies_map("m CONTAINS KEY 'foo'");

            // multiple conditions
            check_applies_map("m > {'a': 'a'} AND m < {'z': 'z'}");
            check_applies_map("m != null AND m IN (null, {'a': 'a'}, {'foo': 'bar'})");
            check_applies_map("m CONTAINS 'bar' AND m CONTAINS KEY 'foo'");

            // should not apply
            check_does_not_apply_map("m = {'a': 'a'}");
            check_does_not_apply_map("m > {'z': 'z'}");
            check_does_not_apply_map("m >= {'z': 'z'}");
            check_does_not_apply_map("m < {'a': 'a'}");
            check_does_not_apply_map("m <= {'a': 'a'}");
            check_does_not_apply_map("m != {'foo': 'bar'}");
            check_does_not_apply_map("m IN ({'a': 'a'}, null)");
            check_does_not_apply_map("m IN ()");
            check_does_not_apply_map("m = null AND m != null");
            check_does_not_apply_map("m CONTAINS 'foo'");
            check_does_not_apply_map("m CONTAINS KEY 'bar'");

            check_invalid_map("m = {null: null}", InvalidRequestException.class);
            check_invalid_map("m = {'a': null}", InvalidRequestException.class);
            check_invalid_map("m = {null: 'a'}", InvalidRequestException.class);
            check_invalid_map("m CONTAINS null", InvalidRequestException.class);
            check_invalid_map("m CONTAINS KEY null", InvalidRequestException.class);
            check_invalid_map("m < null", InvalidRequestException.class);
            check_invalid_map("m IN null", SyntaxException.class);
        }
    }

    /**
     * Migrated from cql_tests.py:TestCQL.map_item_conditional_test()
     */
    @Test
    public void testMapItem() throws Throwable
    {
        for (boolean frozen : new boolean[]{ false, true })
        {
            createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, m %s)",
                                      frozen
                                      ? "frozen<map<text, text>>"
                                      : "map<text, text>"));

            execute("INSERT INTO %s (k, m) VALUES (0, {'foo' : 'bar'})");
            assertInvalidMessage("Invalid null value for map element access",
                                 "DELETE FROM %s WHERE k=0 IF m[?] = ?", null, "foo");
            assertInvalidSyntax("DELETE FROM %s WHERE k=0 IF m[?] CONTAINS ?", "foo", "bar");
            assertInvalidSyntax("DELETE FROM %s WHERE k=0 IF m[?] CONTAINS KEY ?", "foo", "bar");
            assertRows(execute("DELETE FROM %s WHERE k=0 IF m[?] = ?", "foo", "foo"), row(false, map("foo", "bar")));
            assertRows(execute("DELETE FROM %s WHERE k=0 IF m[?] = ?", "foo", null), row(false, map("foo", "bar")));
            assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));

            assertRows(execute("DELETE FROM %s WHERE k=0 IF m[?] = ?", "foo", "bar"), row(true));
            assertEmpty(execute("SELECT * FROM %s"));

            execute("INSERT INTO %s(k, m) VALUES (1, null)");
            if (frozen)
                assertInvalidMessage("Invalid operation (m['foo'] = 'bar') for frozen collection column m",
                                     "UPDATE %s set m['foo'] = 'bar', m['bar'] = 'foo' WHERE k = 1 IF m[?] IN (?, ?)", "foo", "blah", null);
            else
                assertRows(execute("UPDATE %s set m['foo'] = 'bar', m['bar'] = 'foo' WHERE k = 1 IF m[?] IN (?, ?)", "foo", "blah", null), row(true));
        }
    }

    @Test
    public void testListConditionsWithNullValues() throws Throwable
    {
        for (boolean frozen : new boolean[]{true, false})
        {
            String type = frozen ? "frozen<list<text>>" : "list<text>";
            createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, l %s)", type));
            execute("INSERT INTO %s (k, l) VALUES (0, null)");
            execute("INSERT INTO %s (k, l) VALUES (1, null)");

            for (String operator : new String[]{ ">", "<", ">=", "<=", "=" })
            {
                assertRows(execute("UPDATE %s SET l = ? WHERE k = 0 IF l " + operator + " ?", list("test"), list("comparison")), row(false, null));
            }

            assertRows(execute("UPDATE %s SET l = ? WHERE k = 0 IF l != NULL", list("test")), row(false, null));
            assertRows(execute("UPDATE %s SET l = ? WHERE k = 0 IF l = NULL", list("test")), row(true));
            assertRows(execute("SELECT l FROM %s WHERE k = 0"), row(list("test")));

            if (!frozen)
            {
                assertRows(execute("UPDATE %s SET l = ? WHERE k = 1 IF l != []", list("test")), row(false, null));
                assertRows(execute("UPDATE %s SET l = ? WHERE k = 1 IF l = []", list("test")), row(true));
                assertRows(execute("SELECT l FROM %s WHERE k = 1"), row(list("test")));
            }

            for (String operator : new String[]{ ">", "<", ">=", "<=" })
            {
                assertInvalidMessage("Invalid comparison with null for operator \"" + operator + '"',
                                     "UPDATE %s SET l = ? WHERE k = 0 IF l " + operator + " NULL", list("test"));

                if (!frozen)
                    assertInvalidMessage("Invalid comparison with an empty list for operator \"" + operator + '"',
                                         "UPDATE %s SET l = ? WHERE k = 0 IF l " + operator + " []", list("test"));
            }
        }
    }

    @Test
    public void testSetConditionsWithNullValues() throws Throwable
    {
        for (boolean frozen : new boolean[]{true, false})
        {
            String type = frozen ? "frozen<set<text>>" : "set<text>";
            createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, s %s)", type));
            execute("INSERT INTO %s (k, s) VALUES (0, null)");
            execute("INSERT INTO %s (k, s) VALUES (1, null)");

            for (String operator : new String[]{ ">", "<", ">=", "<=", "=" })
            {
                assertRows(execute("UPDATE %s SET s = ? WHERE k = 0 IF s " + operator + " ?", set("test"), set("comparison")), row(false, null));
            }

            assertRows(execute("UPDATE %s SET s = ? WHERE k = 0 IF s != NULL", set("test")), row(false, null));
            assertRows(execute("UPDATE %s SET s = ? WHERE k = 0 IF s = NULL", set("test")), row(true));
            assertRows(execute("SELECT s FROM %s WHERE k = 0"), row(set("test")));

            if (!frozen)
            {
                assertRows(execute("UPDATE %s SET s = ? WHERE k = 1 IF s != {}", set("test")), row(false, null));
                assertRows(execute("UPDATE %s SET s = ? WHERE k = 1 IF s = {}", set("test")), row(true));
                assertRows(execute("SELECT s FROM %s WHERE k = 1"), row(set("test")));
            }

            for (String operator : new String[]{ ">", "<", ">=", "<=" })
            {
                assertInvalidMessage("Invalid comparison with null for operator \"" + operator + '"',
                                     "UPDATE %s SET s = ? WHERE k = 0 IF s " + operator + " NULL", set("test"));

                if (!frozen)
                    assertInvalidMessage("Invalid comparison with an empty set for operator \"" + operator + '"',
                                         "UPDATE %s SET s = ? WHERE k = 0 IF s " + operator + " {}", set("test"));
            }
        }
    }

    @Test
    public void testMapConditionsWithNullValues() throws Throwable
    {
        for (boolean frozen : new boolean[]{true, false})
        {
            String type = frozen ? "frozen<map<text,int>>" : "map<text,int>";
            createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, m %s)", type));
            execute("INSERT INTO %s (k, m) VALUES (0, null)");
            execute("INSERT INTO %s (k, m) VALUES (1, null)");

            for (String operator : new String[]{ ">", "<", ">=", "<=", "=" })
            {
                assertRows(execute("UPDATE %s SET m = ? WHERE k = 0 IF m " + operator + " ?", map("test", 3), map("comparison", 2)), row(false, null));
            }

            assertRows(execute("UPDATE %s SET m = ? WHERE k = 0 IF m != NULL", map("test", 3)), row(false, null));
            assertRows(execute("UPDATE %s SET m = ? WHERE k = 0 IF m = NULL", map("test", 3)), row(true));
            assertRows(execute("SELECT m FROM %s WHERE k = 0"), row(map("test", 3)));

            if (!frozen)
            {
                assertRows(execute("UPDATE %s SET m = ? WHERE k = 1 IF m != {}", map("test", 3)), row(false, null));
                assertRows(execute("UPDATE %s SET m = ? WHERE k = 1 IF m = {}", map("test", 3)), row(true));
                assertRows(execute("SELECT m FROM %s WHERE k = 1"), row(map("test", 3)));
            }

            for (String operator : new String[]{ ">", "<", ">=", "<=" })
            {
                assertInvalidMessage("Invalid comparison with null for operator \"" + operator + '"',
                                     "UPDATE %s SET m = ? WHERE k = 0 IF m " + operator + " NULL", map("test", 3));

                if (!frozen)
                    assertInvalidMessage("Invalid comparison with an empty map for operator \"" + operator + '"',
                                         "UPDATE %s SET m = ? WHERE k = 0 IF m " + operator + " {}", map("test", 3));
            }
        }
    }

    @Test
    public void testUdtConditionsWithNullValues() throws Throwable
    {
        for (boolean frozen : new boolean[]{true, false})
        {
            String userType = createType("CREATE TYPE %s (a int, b int)");
            String type = frozen ? "frozen<" + userType + '>' : userType;
            createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, t %s)", type));
            execute("INSERT INTO %s (k, t) VALUES (0, null)");

            for (String operator : new String[]{ ">", "<", ">=", "<=", "=" })
            {
                assertRows(execute("UPDATE %s SET t = ? WHERE k = 0 IF t " + operator + " ?",
                                   userType("a", 1, "b", 2), userType("a", 4, "b", 5)),
                           row(false, null));
            }

            assertRows(execute("UPDATE %s SET t = ? WHERE k = 0 IF t != NULL", userType("a", 1, "b", 2)), row(false, null));
            assertRows(execute("UPDATE %s SET t = ? WHERE k = 0 IF t = NULL", userType("a", 1, "b", 2)), row(true));
            assertRows(execute("SELECT t FROM %s WHERE k = 0"), row(userType("a", 1, "b", 2)));

            for (String operator : new String[]{ ">", "<", ">=", "<=" })
            {
                assertInvalidMessage("Invalid comparison with null for operator \"" + operator + '"',
                                     "UPDATE %s SET t = ? WHERE k = 0 IF t " + operator + " NULL", userType("a", 1, "b", 2));
            }
        }
    }

    /**
     * Test expanded functionality from CASSANDRA-6839,
     * migrated from cql_tests.py:TestCQL.expanded_map_item_conditional_test()
     */
    @Test
    public void testExpandedMapItem() throws Throwable
    {
        for (boolean frozen : new boolean[]{ false, true })
        {
            createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, m %s)",
                                      frozen
                                      ? "frozen<map<text, text>>"
                                      : "map<text, text>"));

            execute("INSERT INTO %s (k, m) VALUES (0, {'foo' : 'bar'})");

            check_applies_map("m['xxx'] = null");
            check_applies_map("m['foo'] < 'zzz'");
            check_applies_map("m['foo'] <= 'bar'");
            check_applies_map("m['foo'] > 'aaa'");
            check_applies_map("m['foo'] >= 'bar'");
            check_applies_map("m['foo'] != 'xxx'");
            check_applies_map("m['foo'] != null");
            check_applies_map("m['foo'] IN (null, 'xxx', 'bar')");
            check_applies_map("m['xxx'] IN (null, 'xxx', 'bar')"); // m['xxx'] is not set

            // multiple conditions
            check_applies_map("m['foo'] < 'zzz' AND m['foo'] > 'aaa'");

            check_does_not_apply_map("m['foo'] < 'aaa'");
            check_does_not_apply_map("m['foo'] <= 'aaa'");
            check_does_not_apply_map("m['foo'] > 'zzz'");
            check_does_not_apply_map("m['foo'] >= 'zzz'");
            check_does_not_apply_map("m['foo'] != 'bar'");
            check_does_not_apply_map("m['xxx'] != null");  // m['xxx'] is not set
            check_does_not_apply_map("m['foo'] IN (null, 'xxx')");
            check_does_not_apply_map("m['foo'] IN ()");
            check_does_not_apply_map("m['foo'] != null AND m['foo'] = null");

            check_invalid_map("m['foo'] < null", InvalidRequestException.class);
            check_invalid_map("m['foo'] <= null", InvalidRequestException.class);
            check_invalid_map("m['foo'] > null", InvalidRequestException.class);
            check_invalid_map("m['foo'] >= null", InvalidRequestException.class);
            check_invalid_map("m['foo'] IN null", SyntaxException.class);
            check_invalid_map("m['foo'] IN 367", SyntaxException.class);
            check_invalid_map("m['foo'] IN (1, 2, 3)", InvalidRequestException.class);
            check_invalid_map("m['foo'] CONTAINS 367", SyntaxException.class);
            check_invalid_map("m['foo'] CONTAINS KEY 367", SyntaxException.class);
            check_invalid_map("m[null] = null", InvalidRequestException.class);
        }
    }

    void check_applies_map(String condition) throws Throwable
    {
        // UPDATE statement
        assertRows(execute("UPDATE %s SET m = {'foo': 'bar'} WHERE k=0 IF " + condition), row(true));
        assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
        // DELETE statement
        assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition), row(true));
        assertEmpty(execute("SELECT * FROM %s"));
        execute("INSERT INTO %s (k, m) VALUES (0, {'foo' : 'bar'})");
    }

    void check_does_not_apply_map(String condition) throws Throwable
    {
        assertRows(execute("UPDATE %s SET m = {'foo': 'bar'} WHERE k=0 IF " + condition), row(false, map("foo", "bar")));
        assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
        // DELETE statement
        assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition), row(false, map("foo", "bar")));
        assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
    }

    void check_invalid_map(String condition, Class<? extends Throwable> expected) throws Throwable
    {
        // UPDATE statement
        assertInvalidThrow(expected, "UPDATE %s SET m = {'foo': 'bar'} WHERE k=0 IF " + condition);
        assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
        // DELETE statement
        assertInvalidThrow(expected, "DELETE FROM %s WHERE k=0 IF " + condition);
        assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
    }

    @Test
    public void testInMarkerWithUDTs() throws Throwable
    {
        String typename = createType("CREATE TYPE %s (a int, b text)");
        String myType = KEYSPACE + '.' + typename;

        for (boolean frozen : new boolean[] {false, true})
        {
            createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, v %s)",
                                      frozen
                                      ? "frozen<" + myType + ">"
                                      : myType));

            Object v = userType("a", 0, "b", "abc");
            execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);

            // Does not apply
            assertRows(execute("UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v IN (?, ?)", userType("a", 1, "b", "abc"), userType("a", 0, "b", "ac")),
                       row(false, v));
            assertRows(execute("UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v IN (?, ?)", userType("a", 1, "b", "abc"), null),
                       row(false, v));
            assertRows(execute("UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v IN (?, ?)", null, null),
                       row(false, v));
            assertRows(execute("UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v IN (?, ?)", userType("a", 1, "b", "abc"), unset()),
                       row(false, v));
            assertRows(execute("UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v IN (?, ?)", unset(), unset()),
                       row(false, v));
            assertRows(execute("UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v IN ?", list(userType("a", 1, "b", "abc"), userType("a", 0, "b", "ac"))),
                       row(false, v));
            assertRows(execute("UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v.a IN (?, ?)", 1, 2),
                       row(false, v));
            assertRows(execute("UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v.a IN (?, ?)", 1, null),
                       row(false, v));
            assertRows(execute("UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v.a IN ?", list(1, 2)),
                       row(false, v));
            assertRows(execute("UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v.a IN (?, ?)", 1, unset()),
                       row(false, v));

            // Does apply
            assertRows(execute("UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v IN (?, ?)", userType("a", 0, "b", "abc"), userType("a", 0, "b", "ac")),
                       row(true));
            assertRows(execute("UPDATE %s SET v = {a: 1, b: 'bc'} WHERE k = 0 IF v IN (?, ?)", userType("a", 0, "b", "bc"), null),
                       row(true));
            assertRows(execute("UPDATE %s SET v = {a: 0, b: 'abc'} WHERE k = 0 IF v IN ?", list(userType("a", 1, "b", "bc"), userType("a", 0, "b", "ac"))),
                       row(true));
            assertRows(execute("UPDATE %s SET v = {a: 1, b: 'bc'} WHERE k = 0 IF v IN (?, ?, ?)", userType("a", 1, "b", "bc"), unset(), userType("a", 0, "b", "abc")),
                       row(true));
            assertRows(execute("UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v.a IN (?, ?)", 1, 0),
                       row(true));
            assertRows(execute("UPDATE %s SET v = {a: 0, b: 'abc'} WHERE k = 0 IF v.a IN (?, ?)", 0, null),
                       row(true));
            assertRows(execute("UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v.a IN ?", list(0, 1)),
                       row(true));
            assertRows(execute("UPDATE %s SET v = {a: 0, b: 'abc'} WHERE k = 0 IF v.a IN (?, ?, ?)", 1, unset(), 0),
                       row(true));

            assertInvalidMessage("Invalid null list in IN condition",
                                 "UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v IN ?", (List<ByteBuffer>) null);
            assertInvalidMessage("Invalid 'unset' value in condition",
                                 "UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v IN ?", unset());
            assertInvalidMessage("Invalid 'unset' value in condition",
                                 "UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v.a IN ?", unset());
        }
    }

    @Test
    public void testNonFrozenEmptyCollection() throws Throwable
    {
        createTable("CREATE TABLE %s (k int PRIMARY KEY, l list<text>)");
        execute("INSERT INTO %s (k, l) VALUES (0, null)");

        // Does apply
        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l = ?", (ByteBuffer) null),
                   row(true));
        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l = ?", list()),
                   row(true));
        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l != ?", list("bar")),
                   row(true));
        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l IN (?, ?)", null, list("bar")),
                   row(true));

        // Does not apply
        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l < ?", list("a")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l <= ?", list("a")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l = ?", list("bar")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l < ?", list("a")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l <= ?", list("a")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l > ?", list("a")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l >= ?", list("a")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l CONTAINS ?", "bar"),
                   row(false, null));

        assertInvalidMessage("Invalid 'unset' value in condition",
                             "UPDATE %s SET l = null WHERE k = 0 IF l CONTAINS ?", unset());

        assertInvalidMessage("Invalid comparison with null for operator \"CONTAINS\"",
                             "UPDATE %s SET l = null WHERE k = 0 IF l CONTAINS ?", (ByteBuffer) null);

        createTable("CREATE TABLE %s (k int PRIMARY KEY, s set<text>)");
        execute("INSERT INTO %s (k, s) VALUES (0, null)");

        // Does apply
        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s = ?", (ByteBuffer) null),
                   row(true));
        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s = ?", set()),
                   row(true));
        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s != ?", set("bar")),
                   row(true));
        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s IN (?, ?)", null, set("bar")),
                   row(true));

        // Does not apply
        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s = ?", set("bar")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s < ?", set("a")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s <= ?", set("a")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s > ?", set("a")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s >= ?", set("a")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s CONTAINS ?", "bar"),
                   row(false, null));

        assertInvalidMessage("Invalid 'unset' value in condition",
                             "UPDATE %s SET s = null WHERE k = 0 IF s CONTAINS ?", unset());

        assertInvalidMessage("Invalid comparison with null for operator \"CONTAINS\"",
                             "UPDATE %s SET s = null WHERE k = 0 IF s CONTAINS ?", (ByteBuffer) null);

        createTable("CREATE TABLE %s (k int PRIMARY KEY, m map<text, text>) ");
        execute("INSERT INTO %s (k, m) VALUES (0, null)");

        // Does apply
        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m = ?", (ByteBuffer) null),
                   row(true));
        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m = ?", map()),
                   row(true));
        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m != ?", map("foo","bar")),
                   row(true));
        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m IN (?, ?)", null, map("foo","bar")),
                   row(true));

        // Does not apply
        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m = ?", map("foo","bar")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m < ?", map("a","a")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m <= ?", map("a","a")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m > ?", map("a", "a")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m >= ?", map("a", "a")),
                   row(false, null));
        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m CONTAINS ?", "bar"),
                   row(false, null));
        assertRows(execute("UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS KEY ?", "foo"),
                   row(false, null));

        assertInvalidMessage("Invalid 'unset' value in condition",
                             "UPDATE %s SET m = null WHERE k = 0 IF m CONTAINS ?", unset());
        assertInvalidMessage("Invalid 'unset' value in condition",
                             "UPDATE %s SET m = null WHERE k = 0 IF m CONTAINS KEY ?", unset());
        assertInvalidMessage("Invalid comparison with null for operator \"CONTAINS\"",
                             "UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS ?", (ByteBuffer) null);
        assertInvalidMessage("Invalid comparison with null for operator \"CONTAINS KEY\"",
                             "UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS KEY ?", (ByteBuffer) null);
    }

}
