/*
 * 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;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.cql3.validation.operations.ThriftCQLTester;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.marshal.AsciiType;
import org.apache.cassandra.db.marshal.CounterColumnType;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.marshal.LongType;
import org.apache.cassandra.locator.SimpleStrategy;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.thrift.Cassandra;
import org.apache.cassandra.thrift.CfDef;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnDef;
import org.apache.cassandra.thrift.ColumnOrSuperColumn;
import org.apache.cassandra.thrift.ColumnParent;
import org.apache.cassandra.thrift.ColumnPath;
import org.apache.cassandra.thrift.CounterColumn;
import org.apache.cassandra.thrift.CounterSuperColumn;
import org.apache.cassandra.thrift.Deletion;
import org.apache.cassandra.thrift.KsDef;
import org.apache.cassandra.thrift.Mutation;
import org.apache.cassandra.thrift.SlicePredicate;
import org.apache.cassandra.thrift.SliceRange;
import org.apache.cassandra.thrift.SuperColumn;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;

import static org.apache.cassandra.thrift.ConsistencyLevel.ONE;
import static org.junit.Assert.assertEquals;

public class ThriftIntegrationTest extends ThriftCQLTester
{
    final static AtomicInteger seqNumber = new AtomicInteger();
    final String KEYSPACE = "thrift_compact_table_with_supercolumns_test_" + seqNumber.incrementAndGet();

    @Before
    public void setupSuperColumnFamily() throws Throwable
    {
        StorageService.instance.setRpcReady(true);

        final String denseTableName = createTableName();
        final String sparseTableName =  currentSparseTable();
        final String counterTableName = currentCounterTable();

        CfDef cfDef = new CfDef().setColumn_type("Super")
                                 .setSubcomparator_type(Int32Type.instance.toString())
                                 .setComparator_type(AsciiType.instance.toString())
                                 .setDefault_validation_class(AsciiType.instance.toString())
                                 .setKey_validation_class(AsciiType.instance.toString())
                                 .setKeyspace(KEYSPACE)
                                 .setName(denseTableName);

        CfDef sparseCfDef = new CfDef().setColumn_type("Super")
                                       .setComparator_type(AsciiType.instance.toString())
                                       .setSubcomparator_type(AsciiType.instance.toString())
                                       .setKey_validation_class(AsciiType.instance.toString())
                                       .setColumn_metadata(Arrays.asList(new ColumnDef(ByteBufferUtil.bytes("col1"), LongType.instance.toString()),
                                                                         new ColumnDef(ByteBufferUtil.bytes("col2"), LongType.instance.toString())))
                                       .setKeyspace(KEYSPACE)
                                       .setName(sparseTableName);

        CfDef counterCfDef = new CfDef().setColumn_type("Super")
                                        .setSubcomparator_type(AsciiType.instance.toString())
                                        .setComparator_type(AsciiType.instance.toString())
                                        .setDefault_validation_class(CounterColumnType.instance.toString())
                                        .setKey_validation_class(AsciiType.instance.toString())
                                        .setKeyspace(KEYSPACE)
                                        .setName(counterTableName);

        KsDef ksDef = new KsDef(KEYSPACE,
                                SimpleStrategy.class.getName(),
                                Arrays.asList(cfDef, sparseCfDef, counterCfDef));

        ksDef.setStrategy_options(Collections.singletonMap("replication_factor", "1"));

        Cassandra.Client client = getClient();
        client.system_add_keyspace(ksDef);
        client.set_keyspace(KEYSPACE);
    }

    @After
    public void tearDown() throws Throwable
    {
        getClient().send_system_drop_keyspace(KEYSPACE);
    }

    @Test
    public void testCounterTableReads() throws Throwable
    {
        populateCounterTable();
        beforeAndAfterFlush(this::testCounterTableReadsInternal);
    }

    private void testCounterTableReadsInternal() throws Throwable
    {
        UntypedResultSet resultSet = execute(String.format("select * from %s.%s", KEYSPACE, currentCounterTable()));
        assertRows(resultSet,
                   row("key1", "ck1", "counter1", 10L),
                   row("key1", "ck1", "counter2", 5L),
                   row("key2", "ck1", "counter1", 10L),
                   row("key2", "ck1", "counter2", 5L));
    }

    @Test
    public void testCounterTableThriftUpdates() throws Throwable
    {
        populateCounterTable();

        Cassandra.Client client = getClient();
        Mutation mutation = new Mutation();
        ColumnOrSuperColumn csoc = new ColumnOrSuperColumn();
        csoc.setCounter_super_column(new CounterSuperColumn(ByteBufferUtil.bytes("ck1"),
                                                            Arrays.asList(new CounterColumn(ByteBufferUtil.bytes("counter1"), 1))));
        mutation.setColumn_or_supercolumn(csoc);

        Mutation mutation2 = new Mutation();
        ColumnOrSuperColumn csoc2 = new ColumnOrSuperColumn();
        csoc2.setCounter_super_column(new CounterSuperColumn(ByteBufferUtil.bytes("ck1"),
                                                             Arrays.asList(new CounterColumn(ByteBufferUtil.bytes("counter1"), 100))));
        mutation2.setColumn_or_supercolumn(csoc2);
        client.batch_mutate(Collections.singletonMap(ByteBufferUtil.bytes("key1"),
                                                     Collections.singletonMap(currentCounterTable(), Arrays.asList(mutation))),
                            ONE);
        client.batch_mutate(Collections.singletonMap(ByteBufferUtil.bytes("key2"),
                                                     Collections.singletonMap(currentCounterTable(), Arrays.asList(mutation2))),
                            ONE);

        beforeAndAfterFlush(() -> {
            UntypedResultSet resultSet = execute(String.format("select * from %s.%s", KEYSPACE, currentCounterTable()));
            assertRows(resultSet,
                       row("key1", "ck1", "counter1", 11L),
                       row("key1", "ck1", "counter2", 5L),
                       row("key2", "ck1", "counter1", 110L),
                       row("key2", "ck1", "counter2", 5L));
        });
    }

    @Test
    public void testCounterTableCqlUpdates() throws Throwable
    {
        populateCounterTable();

        execute(String.format("UPDATE %s.%s set value = value + 1 WHERE key = ? AND column1 = ? AND column2 = ?", KEYSPACE, currentCounterTable()),
                "key1", "ck1", "counter1");
        execute(String.format("UPDATE %s.%s set value = value + 100 WHERE key = 'key2' AND column1 = 'ck1' AND column2 = 'counter1'", KEYSPACE, currentCounterTable()));

        execute(String.format("UPDATE %s.%s set value = value - ? WHERE key = 'key1' AND column1 = 'ck1' AND column2 = 'counter2'", KEYSPACE, currentCounterTable()), 2L);
        execute(String.format("UPDATE %s.%s set value = value - ? WHERE key = 'key2' AND column1 = 'ck1' AND column2 = 'counter2'", KEYSPACE, currentCounterTable()), 100L);

        beforeAndAfterFlush(() -> {
            UntypedResultSet resultSet = execute(String.format("select * from %s.%s", KEYSPACE, currentCounterTable()));
            assertRows(resultSet,
                       row("key1", "ck1", "counter1", 11L),
                       row("key1", "ck1", "counter2", 3L),
                       row("key2", "ck1", "counter1", 110L),
                       row("key2", "ck1", "counter2", -95L));
        });
    }

    @Test
    public void testCounterTableCqlDeletes() throws Throwable
    {
        populateCounterTable();

        assertRows(execute(String.format("select * from %s.%s", KEYSPACE, currentCounterTable())),
                   row("key1", "ck1", "counter1", 10L),
                   row("key1", "ck1", "counter2", 5L),
                   row("key2", "ck1", "counter1", 10L),
                   row("key2", "ck1", "counter2", 5L));

        execute(String.format("DELETE value FROM %s.%s WHERE key = ? AND column1 = ? AND column2 = ?", KEYSPACE, currentCounterTable()),
                "key1", "ck1", "counter1");

        assertRows(execute(String.format("select * from %s.%s", KEYSPACE, currentCounterTable())),
                   row("key1", "ck1", "counter2", 5L),
                   row("key2", "ck1", "counter1", 10L),
                   row("key2", "ck1", "counter2", 5L));

        execute(String.format("DELETE FROM %s.%s WHERE key = ? AND column1 = ?", KEYSPACE, currentCounterTable()),
                "key1", "ck1");

        assertRows(execute(String.format("select * from %s.%s", KEYSPACE, currentCounterTable())),
                   row("key2", "ck1", "counter1", 10L),
                   row("key2", "ck1", "counter2", 5L));

        execute(String.format("DELETE FROM %s.%s WHERE key = ?", KEYSPACE, currentCounterTable()),
                "key2");

        assertEmpty(execute(String.format("select * from %s.%s", KEYSPACE, currentCounterTable())));
    }

    @Test
    public void testDenseTableAlter() throws Throwable
    {
        populateDenseTable();

        alterTable(String.format("ALTER TABLE %s.%s RENAME column1 TO renamed_column1", KEYSPACE, currentDenseTable()));
        alterTable(String.format("ALTER TABLE %s.%s RENAME column2 TO renamed_column2", KEYSPACE, currentDenseTable()));
        alterTable(String.format("ALTER TABLE %s.%s RENAME key TO renamed_key", KEYSPACE, currentDenseTable()));
        alterTable(String.format("ALTER TABLE %s.%s RENAME value TO renamed_value", KEYSPACE, currentDenseTable()));

        beforeAndAfterFlush(() -> {
            UntypedResultSet resultSet = execute(String.format("select * from %s.%s", KEYSPACE, currentDenseTable()));
            assertEquals("renamed_key", resultSet.metadata().get(0).name.toString());
            assertEquals("renamed_column1", resultSet.metadata().get(1).name.toString());
                                assertEquals("renamed_column2", resultSet.metadata().get(2).name.toString());
                                assertEquals("renamed_value", resultSet.metadata().get(3).name.toString());
            assertRows(resultSet,
                       row("key1", "val1", 1, "value1"),
                       row("key1", "val1", 2, "value2"),
                       row("key1", "val2", 4, "value4"),
                       row("key1", "val2", 5, "value5"),
                       row("key2", "val1", 1, "value1"),
                       row("key2", "val1", 2, "value2"),
                       row("key2", "val2", 4, "value4"),
                       row("key2", "val2", 5, "value5"));
        });
    }

    @Test
    public void testDenseTableReads() throws Throwable
    {
        populateDenseTable();
        beforeAndAfterFlush(this::testDenseTableReadsInternal);
    }

    private void testDenseTableReadsInternal() throws Throwable
    {
        UntypedResultSet resultSet = execute(String.format("select * from %s.%s", KEYSPACE, currentDenseTable()));
        assertEquals("key", resultSet.metadata().get(0).name.toString());
        assertEquals("column1", resultSet.metadata().get(1).name.toString());
        assertEquals("column2", resultSet.metadata().get(2).name.toString());
        assertEquals("value", resultSet.metadata().get(3).name.toString());


        assertRows(resultSet,
                   row("key1", "val1", 1, "value1"),
                   row("key1", "val1", 2, "value2"),
                   row("key1", "val2", 4, "value4"),
                   row("key1", "val2", 5, "value5"),
                   row("key2", "val1", 1, "value1"),
                   row("key2", "val1", 2, "value2"),
                   row("key2", "val2", 4, "value4"),
                   row("key2", "val2", 5, "value5"));

        assertRows(execute(String.format("select * from %s.%s LIMIT 5", KEYSPACE, currentDenseTable())),
                   row("key1", "val1", 1, "value1"),
                   row("key1", "val1", 2, "value2"),
                   row("key1", "val2", 4, "value4"),
                   row("key1", "val2", 5, "value5"),
                   row("key2", "val1", 1, "value1"));

        assertRows(execute(String.format("select value, column2, column1, key from %s.%s", KEYSPACE, currentDenseTable())),
                   row("value1", 1, "val1", "key1"),
                   row("value2", 2, "val1", "key1"),
                   row("value4", 4, "val2", "key1"),
                   row("value5", 5, "val2", "key1"),
                   row("value1", 1, "val1", "key2"),
                   row("value2", 2, "val1", "key2"),
                   row("value4", 4, "val2", "key2"),
                   row("value5", 5, "val2", "key2"));

        assertRows(execute(String.format("select * from %s.%s WHERE key = ? AND column1 = ?", KEYSPACE, currentDenseTable()), "key1", "val2"),
                   row("key1", "val2", 4, "value4"),
                   row("key1", "val2", 5, "value5"));

        assertRows(execute(String.format("select * from %s.%s where key IN ('key1', 'key2') and column1 = 'val1' and column2 = 2", KEYSPACE, currentDenseTable())),
                   row("key1", "val1", 2, "value2"),
                   row("key2", "val1", 2, "value2"));
        assertRows(execute(String.format("select * from %s.%s where key IN ('key1', 'key2') and column1 = 'val1' and column2 > 1", KEYSPACE, currentDenseTable())),
                   row("key1", "val1", 2, "value2"),
                   row("key2", "val1", 2, "value2"));
        assertRows(execute(String.format("select * from %s.%s where key IN ('key1', 'key2') and column1 = 'val1' and column2 >= 2", KEYSPACE, currentDenseTable())),
                   row("key1", "val1", 2, "value2"),
                   row("key2", "val1", 2, "value2"));
        assertEmpty(execute(String.format("select * from %s.%s where key IN ('key1', 'key2') and column1 = 'val1' and column2 > 2", KEYSPACE, currentDenseTable())));

        assertRows(execute(String.format("select column2, key from %s.%s WHERE key = ? AND column1 = ? and column2 = 5", KEYSPACE, currentDenseTable()), "key1", "val2"),
                   row(5, "key1"));
        assertRows(execute(String.format("select * from %s.%s WHERE key = ? AND column1 = ? and column2 >= ?", KEYSPACE, currentDenseTable()), "key1", "val2", 5),
                   row("key1", "val2", 5, "value5"));
        assertRows(execute(String.format("select * from %s.%s WHERE key = ? AND column1 = ? and column2 > ?", KEYSPACE, currentDenseTable()), "key1", "val2", 4),
                   row("key1", "val2", 5, "value5"));
        assertRows(execute(String.format("select * from %s.%s WHERE key = ? AND column1 = ? and column2 < ?", KEYSPACE, currentDenseTable()), "key1", "val2", 5),
                   row("key1", "val2", 4, "value4"));
        assertRows(execute(String.format("select * from %s.%s WHERE key = ? AND column1 = ? and column2 <= ?", KEYSPACE, currentDenseTable()), "key1", "val2", 5),
                   row("key1", "val2", 4, "value4"),
                   row("key1", "val2", 5, "value5"));

        assertRows(execute(String.format("select * from %s.%s where key = 'key1' and column1 in ('val1', 'val2') and column2 IN (1, 4)", KEYSPACE, currentDenseTable())),
                   row("key1", "val1", 1, "value1"),
                   row("key1", "val2", 4, "value4"));

        assertRows(execute(String.format("select * from %s.%s where key = 'key1' and column1 in ('val1', 'val2')", KEYSPACE, currentDenseTable())),
                   row("key1", "val1", 1, "value1"),
                   row("key1", "val1", 2, "value2"),
                   row("key1", "val2", 4, "value4"),
                   row("key1", "val2", 5, "value5"));

        assertRows(execute(String.format("select * from %s.%s where key = 'key1' and column1 in ('val1', 'val2') and column2 = 1", KEYSPACE, currentDenseTable())),
                   row("key1", "val1", 1, "value1"));

        assertRows(execute(String.format("select * from %s.%s where key = 'key1' and (column1, column2) = ('val2', 4)", KEYSPACE, currentDenseTable())),
                   row("key1", "val2", 4, "value4"));

        assertRows(execute(String.format("select * from %s.%s where key = 'key1' and (column1, column2) >= ('val2', 4)", KEYSPACE, currentDenseTable())),
                   row("key1", "val2", 4, "value4"),
                   row("key1", "val2", 5, "value5"));

        assertRows(execute(String.format("select * from %s.%s where key = 'key1' and (column1, column2) > ('val1', 1)", KEYSPACE, currentDenseTable())),
                   row("key1", "val1", 2, "value2"),
                   row("key1", "val2", 4, "value4"),
                   row("key1", "val2", 5, "value5"));

        assertRows(execute(String.format("select * from %s.%s where key = 'key1' and (column1, column2) > ('val2', 1)", KEYSPACE, currentDenseTable())),
                   row("key1", "val2", 4, "value4"),
                   row("key1", "val2", 5, "value5"));

        resultSet = execute(String.format("select key as a, column1 as b, column2 as c, value as d " +
                                          "from %s.%s WHERE key = ? AND column1 = ?", KEYSPACE, currentDenseTable()), "key1", "val2");
        assertRows(resultSet,
                   row("key1", "val2", 4, "value4"),
                   row("key1", "val2", 5, "value5"));
        assertEquals(resultSet.metadata().get(2).type, Int32Type.instance);
        assertEquals(resultSet.metadata().get(3).type, AsciiType.instance);

        assertRows(execute(String.format("select column2, value from %s.%s WHERE key = ? AND column1 = ?", KEYSPACE, currentDenseTable()), "key1", "val2"),
                   row(4, "value4"),
                   row(5, "value5"));

        assertRows(execute(String.format("select column1, value from %s.%s WHERE key = ? AND column1 = ?", KEYSPACE, currentDenseTable()), "key2", "val1"),
                   row("val1", "value1"),
                   row("val1", "value2"));

        assertInvalidMessage("Secondary indexes are not supported on COMPACT STORAGE tables that have clustering columns",
                             String.format("CREATE INDEX ON %s.%s (column2)", KEYSPACE, currentDenseTable()));
        assertInvalidMessage("Secondary indexes are not supported on COMPACT STORAGE tables that have clustering columns",
                             String.format("CREATE INDEX ON %s.%s (value)", KEYSPACE, currentDenseTable()));

        assertRows(execute(String.format("SELECT JSON * FROM %s.%s WHERE key = ? AND column1 = ?", KEYSPACE, currentDenseTable()), "key1", "val2"),
                   row("{\"key\": \"key1\", \"column1\": \"val2\", \"column2\": 4, \"value\": \"value4\"}"),
                   row("{\"key\": \"key1\", \"column1\": \"val2\", \"column2\": 5, \"value\": \"value5\"}"));
    }

    @Test
    public void testDenseTablePartialCqlInserts() throws Throwable
    {
        assertInvalidMessage("Column value is mandatory for SuperColumn tables",
                             String.format("INSERT INTO %s.%s (key, column1, column2) VALUES ('key1', 'val1', 1)", KEYSPACE, currentDenseTable()));

        // That's slightly different from 2.X, since null map keys are not allowed
        assertInvalidMessage("Column key is mandatory for SuperColumn tables",
                             String.format("INSERT INTO %s.%s (key, column1, value) VALUES ('key1', 'val1', 'value1')", KEYSPACE, currentDenseTable()));

        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key1', 'val1', 1, NULL)", KEYSPACE, currentDenseTable()));
        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key1', 'val1', 1, ?)", KEYSPACE, currentDenseTable()), unset());
        assertEmpty(execute(String.format("select * from %s.%s", KEYSPACE, currentDenseTable())));
    }

    @Test
    public void testDenseTableCqlInserts() throws Throwable
    {
        Cassandra.Client client = getClient();

        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES (?, ?, ?, ?)", KEYSPACE, currentDenseTable()),
                "key1", "val1", 1, "value1");
        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES (?, ?, 2, ?)", KEYSPACE, currentDenseTable()),
                "key1", "val1", "value2");
        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key1', 'val2', 4, 'value4')", KEYSPACE, currentDenseTable()));
        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key1', 'val2', 5, 'value5')", KEYSPACE, currentDenseTable()));

        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key2', 'val1', 1, 'value1')", KEYSPACE, currentDenseTable()));
        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key2', 'val1', 2, 'value2')", KEYSPACE, currentDenseTable()));
        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key2', 'val2', 4, 'value4')", KEYSPACE, currentDenseTable()));
        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key2', 'val2', 5, 'value5')", KEYSPACE, currentDenseTable()));

        ColumnPath path = new ColumnPath(currentDenseTable());
        path.setSuper_column(ByteBufferUtil.bytes("val1"));

        ColumnOrSuperColumn cosc = client.get(ByteBufferUtil.bytes("key1"), path, ONE);
        assertEquals(cosc.getSuper_column().columns.get(0).name, ByteBufferUtil.bytes(1));
        assertEquals(cosc.getSuper_column().columns.get(0).value, ByteBufferUtil.bytes("value1"));
        assertEquals(cosc.getSuper_column().columns.get(1).name, ByteBufferUtil.bytes(2));
        assertEquals(cosc.getSuper_column().columns.get(1).value, ByteBufferUtil.bytes("value2"));
    }

    @Test
    public void testDenseTableCqlUpdates() throws Throwable
    {
        assertInvalidMessage("Column key is mandatory for SuperColumn tables",
                             String.format("UPDATE %s.%s SET column2 = 1, value = 'value1' WHERE key = 'key1' AND column1 = 'val1'", KEYSPACE, currentDenseTable()));
        assertInvalidMessage("Column `column2` of type `int` found in SET part",
                             String.format("UPDATE %s.%s SET column2 = 1, value = 'value1' WHERE key = 'key1' AND column1 = 'val1' AND column2 = 1", KEYSPACE, currentDenseTable()));
        assertInvalidMessage("Some clustering keys are missing: column1",
                             String.format("UPDATE %s.%s SET value = 'value1' WHERE key = 'key1' AND column2 = 1", KEYSPACE, currentDenseTable()));

        execute(String.format("UPDATE %s.%s SET value = 'value1' WHERE key = 'key1' AND column1 = 'val1' AND column2 = 1", KEYSPACE, currentDenseTable()));
        execute(String.format("UPDATE %s.%s SET value = 'value2' WHERE key = 'key1' AND column1 = 'val1' AND column2 = 2", KEYSPACE, currentDenseTable()));

        execute(String.format("UPDATE %s.%s SET value = ? WHERE key = ? AND column1 = ? AND column2 = ?", KEYSPACE, currentDenseTable()),
                "value1", "key2", "val2", 1);
        execute(String.format("UPDATE %s.%s SET value = 'value2' WHERE key = 'key2' AND column1 = ? AND column2 = ?", KEYSPACE, currentDenseTable()),
                "val2", 2);

        Cassandra.Client client = getClient();
        ColumnPath path = new ColumnPath(currentDenseTable());
        path.setSuper_column(ByteBufferUtil.bytes("val1"));

        ColumnOrSuperColumn cosc = client.get(ByteBufferUtil.bytes("key1"), path, ONE);
        assertEquals(cosc.getSuper_column().columns.get(0).name, ByteBufferUtil.bytes(1));
        assertEquals(cosc.getSuper_column().columns.get(0).value, ByteBufferUtil.bytes("value1"));
        assertEquals(cosc.getSuper_column().columns.get(1).name, ByteBufferUtil.bytes(2));
        assertEquals(cosc.getSuper_column().columns.get(1).value, ByteBufferUtil.bytes("value2"));

        path = new ColumnPath(currentDenseTable());
        path.setSuper_column(ByteBufferUtil.bytes("val2"));

        cosc = client.get(ByteBufferUtil.bytes("key2"), path, ONE);
        assertEquals(cosc.getSuper_column().columns.get(0).name, ByteBufferUtil.bytes(1));
        assertEquals(cosc.getSuper_column().columns.get(0).value, ByteBufferUtil.bytes("value1"));
        assertEquals(cosc.getSuper_column().columns.get(1).name, ByteBufferUtil.bytes(2));
        assertEquals(cosc.getSuper_column().columns.get(1).value, ByteBufferUtil.bytes("value2"));
    }


    @Test
    public void testDenseTableCqlDeletes() throws Throwable
    {
        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key1', 'val1', 1, 'value1')", KEYSPACE, currentDenseTable()));
        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key1', 'val1', 2, 'value2')", KEYSPACE, currentDenseTable()));
        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key1', 'val2', 4, 'value4')", KEYSPACE, currentDenseTable()));
        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key1', 'val2', 5, 'value5')", KEYSPACE, currentDenseTable()));

        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key2', 'val1', 1, 'value1')", KEYSPACE, currentDenseTable()));
        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key2', 'val1', 2, 'value2')", KEYSPACE, currentDenseTable()));
        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key2', 'val2', 4, 'value4')", KEYSPACE, currentDenseTable()));
        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key2', 'val2', 5, 'value5')", KEYSPACE, currentDenseTable()));

        execute(String.format("DELETE FROM %s.%s WHERE key = 'key1' AND column1 = 'val2' AND column2 = 5", KEYSPACE, currentDenseTable()));
        assertRows(execute(String.format("SELECT * FROM %s.%s WHERE key = 'key1'", KEYSPACE, currentDenseTable())),
                   row("key1", "val1", 1, "value1"),
                   row("key1", "val1", 2, "value2"),
                   row("key1", "val2", 4, "value4"));
        execute(String.format("DELETE FROM %s.%s WHERE key = 'key1' AND column1 = 'val2'", KEYSPACE, currentDenseTable()));
        assertRows(execute(String.format("SELECT * FROM %s.%s WHERE key = 'key1'", KEYSPACE, currentDenseTable())),
                   row("key1", "val1", 1, "value1"),
                   row("key1", "val1", 2, "value2"));
        execute(String.format("DELETE FROM %s.%s WHERE key = 'key1'", KEYSPACE, currentDenseTable()));
        assertEmpty(execute(String.format("SELECT * FROM %s.%s WHERE key = 'key1'", KEYSPACE, currentDenseTable())));

        Cassandra.Client client = getClient();

        Mutation mutation1 = new Mutation();
        SlicePredicate slicePredicate = new SlicePredicate();
        slicePredicate.setSlice_range(new SliceRange(ByteBufferUtil.bytes("val1"), ByteBufferUtil.bytes("val1"), false, 1));
        Deletion deletion1 = new Deletion();
        deletion1.setTimestamp(FBUtilities.timestampMicros());
        deletion1.setPredicate(slicePredicate);
        mutation1.setDeletion(deletion1);
        client.batch_mutate(Collections.singletonMap(ByteBufferUtil.bytes("key2"),
                                                     Collections.singletonMap(currentDenseTable(), Arrays.asList(mutation1))),
                            ONE);
        assertRows(execute(String.format("SELECT * FROM %s.%s WHERE key = 'key2'", KEYSPACE, currentDenseTable())),
                   row("key2", "val2", 4, "value4"),
                   row("key2", "val2", 5, "value5"));

        Mutation mutation2 = new Mutation();
        Deletion deletion2 = new Deletion();
        deletion2.setTimestamp(FBUtilities.timestampMicros());
        deletion2.setSuper_column(ByteBufferUtil.bytes("val2"));
        mutation2.setDeletion(deletion2);
        client.batch_mutate(Collections.singletonMap(ByteBufferUtil.bytes("key2"),
                                                     Collections.singletonMap(currentDenseTable(), Arrays.asList(mutation2))),
                            ONE);

        assertEmpty(execute(String.format("SELECT * FROM %s.%s WHERE key = 'key2'", KEYSPACE, currentDenseTable())));

        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key1', 'val1', 1, 'value1')", KEYSPACE, currentDenseTable()));
        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key2', 'val1', 1, 'value1')", KEYSPACE, currentDenseTable()));
        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES ('key3', 'val1', 1, 'value1')", KEYSPACE, currentDenseTable()));

        execute(String.format("DELETE FROM %s.%s WHERE key IN ('key1', 'key2')", KEYSPACE, currentDenseTable()));
        assertRows(execute(String.format("SELECT * FROM %s.%s", KEYSPACE, currentDenseTable())),
                   row("key3", "val1", 1, "value1"));

        assertInvalidMessage("Multi-column relations cannot be used in WHERE clauses for UPDATE and DELETE statements",
                             String.format("DELETE FROM %s.%s WHERE key = 'key3' AND (column1, column2) = ('val1', 1)", KEYSPACE, currentDenseTable()));

        assertInvalidMessage("Token relations cannot be used in WHERE clauses for UPDATE and DELETE statements: token(key) > token('key3')",
                             String.format("DELETE FROM %s.%s WHERE token(key) > token('key3')", KEYSPACE, currentDenseTable()));
    }

    @Test
    public void testSparseTableAlter() throws Throwable
    {
        populateSparseTable();

        alterTable(String.format("ALTER TABLE %s.%s RENAME column1 TO renamed_column1", KEYSPACE, currentSparseTable()));
        alterTable(String.format("ALTER TABLE %s.%s RENAME key TO renamed_key", KEYSPACE, currentSparseTable()));
        assertInvalidMessage("Cannot rename non PRIMARY KEY part col1",
                             String.format("ALTER TABLE %s.%s RENAME col1 TO renamed_col1", KEYSPACE, currentSparseTable()));
        assertInvalidMessage("Cannot rename non PRIMARY KEY part col2",
                             String.format("ALTER TABLE %s.%s RENAME col2 TO renamed_col2", KEYSPACE, currentSparseTable()));
        assertInvalidMessage("Cannot rename unknown column column2 in keyspace",
                             String.format("ALTER TABLE %s.%s RENAME column2 TO renamed_column2", KEYSPACE, currentSparseTable()));
        assertInvalidMessage("Cannot rename unknown column value in keyspace",
                             String.format("ALTER TABLE %s.%s RENAME value TO renamed_value", KEYSPACE, currentSparseTable()));


        UntypedResultSet resultSet = execute(String.format("select * from %s.%s", KEYSPACE, currentSparseTable()));
        assertEquals("renamed_key", resultSet.metadata().get(0).name.toString());
        assertEquals("renamed_column1", resultSet.metadata().get(1).name.toString());
        assertEquals("col1", resultSet.metadata().get(2).name.toString());
        assertEquals("col2", resultSet.metadata().get(3).name.toString());

        assertRows(resultSet,
                   row("key1", "val1", 3L, 4L),
                   row("key1", "val2", 3L, 4L),
                   row("key2", "val1", 3L, 4L),
                   row("key2", "val2", 3L, 4L));
    }

    @Test
    public void testSparseTableCqlReads() throws Throwable
    {
        populateSparseTable();
        beforeAndAfterFlush(this::testSparseTableCqlReadsInternal);
    }

    private void testSparseTableCqlReadsInternal() throws Throwable
    {
        UntypedResultSet resultSet = execute(String.format("select * from %s.%s", KEYSPACE, currentSparseTable()));
        assertEquals("key", resultSet.metadata().get(0).name.toString());
        assertEquals("column1", resultSet.metadata().get(1).name.toString());
        assertEquals("col1", resultSet.metadata().get(2).name.toString());
        assertEquals("col2", resultSet.metadata().get(3).name.toString());

        assertRows(resultSet,
                   row("key1", "val1", 3L, 4L),
                   row("key1", "val2", 3L, 4L),
                   row("key2", "val1", 3L, 4L),
                   row("key2", "val2", 3L, 4L));

        assertRows(execute(String.format("select col1, col2, column1, key from %s.%s", KEYSPACE, currentSparseTable())),
                   row(3L, 4L, "val1", "key1"),
                   row(3L, 4L, "val2", "key1"),
                   row(3L, 4L, "val1", "key2"),
                   row(3L, 4L, "val2", "key2"));

        assertInvalidMessage("Undefined column name value",
                             String.format("select value from %s.%s", KEYSPACE, currentSparseTable()));

        assertRows(execute(String.format("select * from %s.%s WHERE key = ? AND column1 = ?", KEYSPACE, currentSparseTable()), "key1", "val2"),
                   row("key1", "val2", 3L, 4L));

        resultSet = execute(String.format("select col1 as a, col2 as b, column1 as c, key as d from %s.%s WHERE key = ? AND column1 = ?", KEYSPACE, currentSparseTable()), "key1", "val2");
        assertRows(resultSet,
                   row(3L, 4L, "val2", "key1"));
        assertEquals(resultSet.metadata().get(0).name, ColumnIdentifier.getInterned("a", true));
        assertEquals(resultSet.metadata().get(1).name, ColumnIdentifier.getInterned("b", true));
        assertEquals(resultSet.metadata().get(2).name, ColumnIdentifier.getInterned("c", true));
        assertEquals(resultSet.metadata().get(3).name, ColumnIdentifier.getInterned("d", true));

        assertRows(execute(String.format("select col1, col2 from %s.%s WHERE key = ? AND column1 = ?", KEYSPACE, currentSparseTable()), "key1", "val2"),
                   row(3L, 4L));

        assertInvalidMessage("Secondary indexes are not supported on COMPACT STORAGE tables that have clustering columns",
                             String.format("CREATE INDEX ON %s.%s (column1)", KEYSPACE, currentSparseTable()));
        assertInvalidMessage("Secondary indexes are not supported on COMPACT STORAGE tables that have clustering columns",
                             String.format("CREATE INDEX ON %s.%s (col1)", KEYSPACE, currentSparseTable()));

        assertRows(execute(String.format("SELECT JSON * FROM %s.%s WHERE key = ? AND column1 = ?", KEYSPACE, currentSparseTable()), "key1", "val2"),
                   row("{\"key\": \"key1\", \"column1\": \"val2\", \"col1\": 3, \"col2\": 4}"));
    }

    @Test
    public void testSparseTableCqlInserts() throws Throwable
    {
        execute(String.format("insert into %s.%s (key, column1, col1, col2) values ('key1', 'val1', 1, 2)", KEYSPACE, currentSparseTable()));
        execute(String.format("insert into %s.%s (key, column1, col1, col2) values ('key1', 'val2', 3, 4)", KEYSPACE, currentSparseTable()));
        execute(String.format("insert into %s.%s (key, column1, col1, col2) values ('key2', 'val1', 5, 6)", KEYSPACE, currentSparseTable()));
        execute(String.format("insert into %s.%s (key, column1, col1, col2) values ('key2', 'val2', 7, 8)", KEYSPACE, currentSparseTable()));

        assertRows(execute(String.format("SELECT * FROM %s.%s", KEYSPACE, currentSparseTable())),
                   row("key1", "val1", 1L, 2L),
                   row("key1", "val2", 3L, 4L),
                   row("key2", "val1", 5L, 6L),
                   row("key2", "val2", 7L, 8L));

        execute(String.format("truncate %s.%s", KEYSPACE, currentSparseTable()));

        execute(String.format("insert into %s.%s (key, column1) values ('key1', 'val1')", KEYSPACE, currentSparseTable()));
        assertRows(execute(String.format("select * from %s.%s", KEYSPACE, currentSparseTable())));

        execute(String.format("insert into %s.%s (key, column1, col1) values ('key1', 'val1', 1)", KEYSPACE, currentSparseTable()));
        execute(String.format("insert into %s.%s (key, column1, col2) values ('key1', 'val1', 2)", KEYSPACE, currentSparseTable()));
        assertRows(execute(String.format("select * from %s.%s", KEYSPACE, currentSparseTable())),
                   row("key1", "val1", 1L, 2L));

        Cassandra.Client client = getClient();
        ColumnPath path = new ColumnPath(currentSparseTable());
        path.setSuper_column(ByteBufferUtil.bytes("val1"));

        ColumnOrSuperColumn cosc = client.get(ByteBufferUtil.bytes("key1"), path, ONE);
        assertEquals(cosc.getSuper_column().columns.get(0).value, ByteBufferUtil.bytes(1L));
        assertEquals(cosc.getSuper_column().columns.get(0).name, ByteBufferUtil.bytes("col1"));
        assertEquals(cosc.getSuper_column().columns.get(1).value, ByteBufferUtil.bytes(2L));
        assertEquals(cosc.getSuper_column().columns.get(1).name, ByteBufferUtil.bytes("col2"));
    }

    @Test
    public void testSparseTableCqlUpdates() throws Throwable
    {
        execute(String.format("UPDATE %s.%s set col1 = 1, col2 = 2 WHERE key = 'key1' AND column1 = 'val1'", KEYSPACE, currentSparseTable()));
        execute(String.format("UPDATE %s.%s set col1 = 3, col2 = 4 WHERE key = 'key1' AND column1 = 'val2'", KEYSPACE, currentSparseTable()));
        execute(String.format("UPDATE %s.%s set col1 = 5, col2 = 6 WHERE key = 'key2' AND column1 = 'val1'", KEYSPACE, currentSparseTable()));
        execute(String.format("UPDATE %s.%s set col1 = 7, col2 = 8 WHERE key = 'key2' AND column1 = 'val2'", KEYSPACE, currentSparseTable()));

        assertRows(execute(String.format("SELECT * FROM %s.%s", KEYSPACE, currentSparseTable())),
                   row("key1", "val1", 1L, 2L),
                   row("key1", "val2", 3L, 4L),
                   row("key2", "val1", 5L, 6L),
                   row("key2", "val2", 7L, 8L));

        Cassandra.Client client = getClient();
        ColumnPath path = new ColumnPath(currentSparseTable());
        path.setSuper_column(ByteBufferUtil.bytes("val1"));

        ColumnOrSuperColumn cosc = client.get(ByteBufferUtil.bytes("key1"), path, ONE);
        assertEquals(cosc.getSuper_column().columns.get(0).value, ByteBufferUtil.bytes(1L));
        assertEquals(cosc.getSuper_column().columns.get(0).name, ByteBufferUtil.bytes("col1"));
        assertEquals(cosc.getSuper_column().columns.get(1).value, ByteBufferUtil.bytes(2L));
        assertEquals(cosc.getSuper_column().columns.get(1).name, ByteBufferUtil.bytes("col2"));
    }

    @Test
    public void testSparseTableCqlDeletes() throws Throwable
    {
        execute(String.format("insert into %s.%s (key, column1, col1, col2) values ('key1', 'val1', 1, 2)", KEYSPACE, currentSparseTable()));
        execute(String.format("insert into %s.%s (key, column1, col1, col2) values ('key1', 'val2', 3, 4)", KEYSPACE, currentSparseTable()));
        execute(String.format("insert into %s.%s (key, column1, col1, col2) values ('key2', 'val1', 5, 6)", KEYSPACE, currentSparseTable()));
        execute(String.format("insert into %s.%s (key, column1, col1, col2) values ('key2', 'val2', 7, 8)", KEYSPACE, currentSparseTable()));

        execute(String.format("DELETE col1 FROM %s.%s WHERE key = 'key1' AND column1 = 'val1'", KEYSPACE, currentSparseTable()));

        assertRows(execute(String.format("SELECT * FROM %s.%s", KEYSPACE, currentSparseTable())),
                   row("key1", "val1", null, 2L),
                   row("key1", "val2", 3L, 4L),
                   row("key2", "val1", 5L, 6L),
                   row("key2", "val2", 7L, 8L));

        execute(String.format("DELETE FROM %s.%s WHERE key = 'key1' AND column1 = 'val2'", KEYSPACE, currentSparseTable()));

        assertRows(execute(String.format("SELECT * FROM %s.%s", KEYSPACE, currentSparseTable())),
                   row("key1", "val1", null, 2L),
                   row("key2", "val1", 5L, 6L),
                   row("key2", "val2", 7L, 8L));

        execute(String.format("DELETE FROM %s.%s WHERE key = 'key2'", KEYSPACE, currentSparseTable()));

        assertRows(execute(String.format("SELECT * FROM %s.%s", KEYSPACE, currentSparseTable())),
                   row("key1", "val1", null, 2L));
    }

    @Test
    public void testInsertJson() throws Throwable
    {
        execute(String.format("INSERT INTO %s.%s JSON ?", KEYSPACE, currentDenseTable()),
                "{\"key\": \"key5\", \"column1\": \"val2\", \"column2\": 4, \"value\": \"value4\"}");
        assertRows(execute(String.format("SELECT * FROM %s.%s", KEYSPACE, currentDenseTable())),
                   row("key5", "val2", 4, "value4"));

        execute(String.format("INSERT INTO %s.%s JSON ?", KEYSPACE, currentSparseTable()),
                "{\"key\": \"key1\", \"column1\": \"val1\", \"col1\": 1, \"col2\": 2}");
        assertRows(execute(String.format("SELECT * FROM %s.%s", KEYSPACE, currentSparseTable())),
                   row("key1", "val1", 1L, 2L));
    }

    @Test
    public void testFiltering() throws Throwable
    {
        assertInvalidMessage("Filtering is not supported on SuperColumn tables",
                             String.format("select * from %s.%s WHERE value = ?", KEYSPACE, currentDenseTable()),
                             "value5");
        assertInvalidMessage("Filtering is not supported on SuperColumn tables",
                             String.format("select * from %s.%s WHERE value = ? ALLOW FILTERING", KEYSPACE, currentDenseTable()),
                             "value5");
        assertInvalidMessage("Filtering is not supported on SuperColumn tables",
                             String.format("SELECT * FROM %s.%s WHERE value = 'value2' ALLOW FILTERING", KEYSPACE, currentDenseTable()));
        assertInvalidMessage("Filtering is not supported on SuperColumn tables",
                             String.format("SELECT * FROM %s.%s WHERE column2 = 1 ALLOW FILTERING", KEYSPACE, currentDenseTable()));
    }

    @Test
    public void testLwt() throws Throwable
    {
        assertRows(execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES (?, ?, ?, ?) IF NOT EXISTS", KEYSPACE, currentDenseTable()),
                           "key1", "val1", 1, "value1"),
                   row(true));
        assertRows(execute(String.format("SELECT * FROM %s.%s", KEYSPACE, currentDenseTable())),
                   row("key1", "val1", 1, "value1"));
        assertRows(execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES (?, ?, ?, ?) IF NOT EXISTS", KEYSPACE, currentDenseTable()),
                           "key1", "val1", 1, "value1"),
                   row(false, "key1", "val1", 1, "value1"));

        // in 2.2 this query was a no-op
        assertInvalidMessage("Lightweight transactions on SuperColumn tables are only supported with supplied SuperColumn key",
                             String.format("UPDATE %s.%s SET value = 'changed' WHERE key = ? AND column1 = ? IF value = ?", KEYSPACE, currentDenseTable()));

        assertRows(execute(String.format("UPDATE %s.%s SET value = 'changed' WHERE key = ? AND column1 = ? AND column2 = ? IF value = ?", KEYSPACE, currentDenseTable()),
                           "key1", "val1", 1, "value1"),
                   row(true));
        assertRows(execute(String.format("SELECT * FROM %s.%s", KEYSPACE, currentDenseTable())),
                   row("key1", "val1", 1, "changed"));
        assertRows(execute(String.format("UPDATE %s.%s SET value = 'changed' WHERE key = ? AND column1 = ? AND column2 = ? IF value = ?", KEYSPACE, currentDenseTable()),
                           "key1", "val1", 1, "value1"),
                   row(false, "changed"));

        assertRows(execute(String.format("UPDATE %s.%s SET value = 'changed2' WHERE key = ? AND column1 = ? AND column2 = ? IF value > ?", KEYSPACE, currentDenseTable()),
                           "key1", "val1", 1, "a"),
                   row(true));
        assertRows(execute(String.format("SELECT * FROM %s.%s", KEYSPACE, currentDenseTable())),
                   row("key1", "val1", 1, "changed2"));
        assertRows(execute(String.format("UPDATE %s.%s SET value = 'changed2' WHERE key = ? AND column1 = ? AND column2 = ? IF value < ?", KEYSPACE, currentDenseTable()),
                           "key1", "val1", 1, "a"),
                   row(false, "changed2"));

        assertInvalidMessage("PRIMARY KEY column 'column2' cannot have IF conditions",
                             String.format("UPDATE %s.%s SET value = 'changed2' WHERE key = ? AND column1 = ? AND column2 = ? IF value > ? AND column2 = ?", KEYSPACE, currentDenseTable()));

        assertInvalidMessage("Lightweight transactions on SuperColumn tables are only supported with supplied SuperColumn key",
                             String.format("UPDATE %s.%s SET value = 'changed2' WHERE key = ? AND column1 = ? IF value > ?", KEYSPACE, currentDenseTable()));

        execute(String.format("DELETE FROM %s.%s WHERE key = 'key1' AND column1 = 'val1' AND column2 = 1 IF EXISTS", KEYSPACE, currentDenseTable()));
        assertEmpty(execute(String.format("SELECT * FROM %s.%s", KEYSPACE, currentDenseTable())));

        execute(String.format("INSERT INTO %s.%s (key, column1, column2, value) VALUES (?, ?, ?, ?)", KEYSPACE, currentDenseTable()),
                "key1", "val1", 1, "value1");

        assertRows(execute(String.format("DELETE FROM %s.%s WHERE key = 'key1' AND column1 = 'val1' AND column2 = 1 IF value = 'value1'", KEYSPACE, currentDenseTable())),
                   row(true));
        assertEmpty(execute(String.format("SELECT * FROM %s.%s", KEYSPACE, currentDenseTable())));

        assertRows(execute(String.format("DELETE FROM %s.%s WHERE key = 'key1' AND column1 = 'val1' AND column2 = 1 IF value = 'value1'", KEYSPACE, currentDenseTable())),
                   row(false));
        assertEmpty(execute(String.format("SELECT * FROM %s.%s", KEYSPACE, currentDenseTable())));
    }

    @Test
    public void testCqlAggregateFunctions() throws Throwable
    {
        populateDenseTable();
        populateSparseTable();

        assertRows(execute(String.format("select count(*) from %s.%s", KEYSPACE, currentDenseTable())),
                   row(8L));
        assertRows(execute(String.format("select count(*) from %s.%s", KEYSPACE, currentSparseTable())),
                   row(4L));

        assertRows(execute(String.format("select count(*) from %s.%s where key = ? AND column1 = ?", KEYSPACE, currentDenseTable()), "key1", "val1"),
                   row(2L));
        assertRows(execute(String.format("select count(*) from %s.%s where key = ? AND column1 = ?", KEYSPACE, currentSparseTable()), "key1", "val1"),
                   row(1L));
        assertRows(execute(String.format("select count(*) from %s.%s where key = ?", KEYSPACE, currentSparseTable()), "key1"),
                   row(2L));

        assertRows(execute(String.format("select max(value) from %s.%s", KEYSPACE, currentDenseTable())),
                   row("value5"));
        assertRows(execute(String.format("select max(col1) from %s.%s", KEYSPACE, currentSparseTable())),
                   row(3L));

        assertRows(execute(String.format("select avg(column2) from %s.%s", KEYSPACE, currentDenseTable())),
                   row(3));
        assertRows(execute(String.format("select avg(col1) from %s.%s", KEYSPACE, currentSparseTable())),
                   row(3L));
    }

    private void populateDenseTable() throws Throwable
    {
        Cassandra.Client client = getClient();

        Mutation mutation = new Mutation();
        ColumnOrSuperColumn csoc = new ColumnOrSuperColumn();
        csoc.setSuper_column(getSuperColumnForInsert(ByteBufferUtil.bytes("val1"),
                                                     Arrays.asList(getColumnForInsert(ByteBufferUtil.bytes(1), ByteBufferUtil.bytes("value1")),
                                                                   getColumnForInsert(ByteBufferUtil.bytes(2), ByteBufferUtil.bytes("value2")))));
        mutation.setColumn_or_supercolumn(csoc);

        Mutation mutation2 = new Mutation();
        ColumnOrSuperColumn csoc2 = new ColumnOrSuperColumn();
        csoc2.setSuper_column(getSuperColumnForInsert(ByteBufferUtil.bytes("val2"),
                                                      Arrays.asList(getColumnForInsert(ByteBufferUtil.bytes(4), ByteBufferUtil.bytes("value4")),
                                                                    getColumnForInsert(ByteBufferUtil.bytes(5), ByteBufferUtil.bytes("value5")))));
        mutation2.setColumn_or_supercolumn(csoc2);

        client.batch_mutate(Collections.singletonMap(ByteBufferUtil.bytes("key1"),
                                                     Collections.singletonMap(currentDenseTable(), Arrays.asList(mutation, mutation2))),
                            ONE);

        client.batch_mutate(Collections.singletonMap(ByteBufferUtil.bytes("key2"),
                                                     Collections.singletonMap(currentDenseTable(), Arrays.asList(mutation, mutation2))),
                            ONE);
    }

    private void populateSparseTable() throws Throwable
    {
        Cassandra.Client client = getClient();

        Mutation mutation = new Mutation();
        ColumnOrSuperColumn csoc = new ColumnOrSuperColumn();
        csoc.setSuper_column(getSuperColumnForInsert(ByteBufferUtil.bytes("val1"),
                                                     Arrays.asList(getColumnForInsert(ByteBufferUtil.bytes("value1"), ByteBufferUtil.bytes(1L)),
                                                                   getColumnForInsert(ByteBufferUtil.bytes("value2"), ByteBufferUtil.bytes(2L)),
                                                                   getColumnForInsert(ByteBufferUtil.bytes("col1"), ByteBufferUtil.bytes(3L)),
                                                                   getColumnForInsert(ByteBufferUtil.bytes("col2"), ByteBufferUtil.bytes(4L)))));
        mutation.setColumn_or_supercolumn(csoc);

        Mutation mutation2 = new Mutation();
        ColumnOrSuperColumn csoc2 = new ColumnOrSuperColumn();
        csoc2.setSuper_column(getSuperColumnForInsert(ByteBufferUtil.bytes("val2"),
                                                      Arrays.asList(getColumnForInsert(ByteBufferUtil.bytes("value1"), ByteBufferUtil.bytes(1L)),
                                                                    getColumnForInsert(ByteBufferUtil.bytes("value2"), ByteBufferUtil.bytes(2L)),
                                                                    getColumnForInsert(ByteBufferUtil.bytes("col1"), ByteBufferUtil.bytes(3L)),
                                                                    getColumnForInsert(ByteBufferUtil.bytes("col2"), ByteBufferUtil.bytes(4L)))));
        mutation2.setColumn_or_supercolumn(csoc2);

        client.batch_mutate(Collections.singletonMap(ByteBufferUtil.bytes("key1"),
                                                     Collections.singletonMap(currentSparseTable(), Arrays.asList(mutation, mutation2))),
                            ONE);

        client.batch_mutate(Collections.singletonMap(ByteBufferUtil.bytes("key2"),
                                                     Collections.singletonMap(currentSparseTable(), Arrays.asList(mutation, mutation2))),
                            ONE);
    }

    private void populateCounterTable() throws Throwable
    {
        Cassandra.Client client = getClient();

        ColumnParent cp = new ColumnParent(currentCounterTable());
        cp.setSuper_column(ByteBufferUtil.bytes("ck1"));
        client.add(ByteBufferUtil.bytes("key1"),
                   cp,
                   new CounterColumn(ByteBufferUtil.bytes("counter1"), 10L),
                   ONE);
        cp = new ColumnParent(currentCounterTable());
        cp.setSuper_column(ByteBufferUtil.bytes("ck1"));
        client.add(ByteBufferUtil.bytes("key1"),
                   cp,
                   new CounterColumn(ByteBufferUtil.bytes("counter2"), 5L),
                   ONE);
        cp = new ColumnParent(currentCounterTable());
        cp.setSuper_column(ByteBufferUtil.bytes("ck1"));
        client.add(ByteBufferUtil.bytes("key2"),
                   cp,
                   new CounterColumn(ByteBufferUtil.bytes("counter1"), 10L),
                   ONE);
        cp = new ColumnParent(currentCounterTable());
        cp.setSuper_column(ByteBufferUtil.bytes("ck1"));
        client.add(ByteBufferUtil.bytes("key2"),
                   cp,
                   new CounterColumn(ByteBufferUtil.bytes("counter2"), 5L),
                   ONE);
    }

    private String currentCounterTable()
    {
        return currentTable() + "_counter";
    }

    private String currentSparseTable()
    {
        return currentTable() + "_sparse";
    }

    private String currentDenseTable()
    {
        return currentTable();
    }

    private Column getColumnForInsert(ByteBuffer columnName, ByteBuffer value)
    {
        Column column = new Column();
        column.setName(columnName);
        column.setValue(value);
        column.setTimestamp(System.currentTimeMillis());
        return column;
    }

    private SuperColumn getSuperColumnForInsert(ByteBuffer columnName, List<Column> columns)
    {
        SuperColumn column = new SuperColumn();
        column.setName(columnName);
        for (Column c : columns)
            column.addToColumns(c);
        return column;
    }

    public void beforeAndAfterFlush(CheckedFunction runnable) throws Throwable
    {
        runnable.apply();
        flushAll();
        runnable.apply();
    }

    private void flushAll()
    {
        for (String cfName : new String[]{ currentTable(), currentSparseTable(), currentCounterTable(), currentDenseTable() })
            Keyspace.open(KEYSPACE).getColumnFamilyStore(cfName).forceBlockingFlush();
    }
}
