/*
* Copyright (C) 2012-2015 DataStax Inc.
*
* Licensed 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 com.datastax.driver.core;
import com.datastax.driver.core.ColumnMetadata.*;
import com.datastax.driver.core.Token.M3PToken;
import com.datastax.driver.core.utils.CassandraVersion;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.Ints;
import org.testng.annotations.Test;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import static com.datastax.driver.core.Assertions.assertThat;
import static com.datastax.driver.core.ColumnMetadata.*;
import static com.datastax.driver.core.DataType.*;
import static com.datastax.driver.core.IndexMetadata.Kind.*;
@CassandraVersion("1.2.0")
public class IndexMetadataTest extends CCMTestsSupport {
/**
* Column definitions for schema_columns table (legacy pre-3.0 layout).
*/
private static final ColumnDefinitions legacyColumnDefs = new ColumnDefinitions(new ColumnDefinitions.Definition[]{
definition(COLUMN_NAME, text()),
definition(COMPONENT_INDEX, cint()),
definition(KIND_V2, text()),
definition(INDEX_NAME, text()),
definition(INDEX_TYPE, text()),
definition(VALIDATOR, text()),
definition(INDEX_OPTIONS, text())
}, CodecRegistry.DEFAULT_INSTANCE);
/**
* Column definitions for indexes table (post-3.0 layout).
*/
private static final ColumnDefinitions indexColumnDefs = new ColumnDefinitions(new ColumnDefinitions.Definition[]{
definition(IndexMetadata.NAME, text()),
definition(IndexMetadata.KIND, text()),
definition(IndexMetadata.OPTIONS, map(text(), text()))
}, CodecRegistry.DEFAULT_INSTANCE);
private static final TypeCodec<Map<String, String>> MAP_CODEC = TypeCodec.map(TypeCodec.varchar(), TypeCodec.varchar());
private ProtocolVersion protocolVersion;
@Override
public void onTestContextInitialized() {
protocolVersion = cluster().getConfiguration().getProtocolOptions().getProtocolVersion();
String createTable = "CREATE TABLE indexing ("
+ "id int,"
+ "id2 int,"
+ "map_values map<text, int>,"
+ "map_keys map<text, int>,"
+ "map_entries map<text, int>,"
+ "map_all map<text, int>,"
+ "text_column text, "
+ "\"MixedCaseColumn\" list<text>,"
+
// Frozen collections was introduced only in C* 2.1.3
(ccm().getCassandraVersion().compareTo(VersionNumber.parse("2.1.3")) >= 0
?
", map_full frozen<map<text, int>>,"
+ "set_full frozen<set<text>>,"
+ "list_full frozen<list<text>>,"
:
"")
+ "PRIMARY KEY (id, id2));";
execute(createTable);
}
@Test(groups = "short")
public void should_create_metadata_for_simple_index() {
String createValuesIndex = String.format("CREATE INDEX text_column_index ON %s.indexing (text_column);", keyspace);
session().execute(createValuesIndex);
ColumnMetadata column = getColumn("text_column");
IndexMetadata index = getIndex("text_column_index");
assertThat(index)
.hasName("text_column_index")
.hasParent((TableMetadata) column.getParent())
.isNotCustomIndex()
.hasTarget("text_column")
.hasKind(COMPOSITES)
.asCqlQuery(createValuesIndex);
assertThat((TableMetadata) column.getParent()).hasIndex(index);
}
@Test(groups = "short")
@CassandraVersion(value = "2.1", description = "index names with quoted identifiers and collection indexes not supported until 2.1")
public void should_create_metadata_for_values_index_on_mixed_case_column() {
// 3.0 assumes the 'values' keyword if index on a collection
String createValuesIndex = ccm().getCassandraVersion().getMajor() > 2 ?
String.format("CREATE INDEX \"MixedCaseIndex\" ON %s.indexing (values(\"MixedCaseColumn\"));", keyspace) :
String.format("CREATE INDEX \"MixedCaseIndex\" ON %s.indexing (\"MixedCaseColumn\");", keyspace);
session().execute(createValuesIndex);
ColumnMetadata column = getColumn("\"MixedCaseColumn\"");
IndexMetadata index = getIndex("\"MixedCaseIndex\"");
assertThat(index)
.hasName("MixedCaseIndex")
.hasParent((TableMetadata) column.getParent())
.isNotCustomIndex()
.hasTarget(ccm().getCassandraVersion().getMajor() > 2 ? "values(\"MixedCaseColumn\")" : "\"MixedCaseColumn\"")
.hasKind(COMPOSITES)
.asCqlQuery(createValuesIndex);
assertThat((TableMetadata) column.getParent()).hasIndex(index);
}
@Test(groups = "short")
@CassandraVersion("2.1.0")
public void should_create_metadata_for_index_on_map_values() {
// 3.0 assumes the 'values' keyword if index on a collection
String createValuesIndex = ccm().getCassandraVersion().getMajor() > 2 ?
String.format("CREATE INDEX map_values_index ON %s.indexing (values(map_values));", keyspace) :
String.format("CREATE INDEX map_values_index ON %s.indexing (map_values);", keyspace);
session().execute(createValuesIndex);
ColumnMetadata column = getColumn("map_values");
IndexMetadata index = getIndex("map_values_index");
assertThat(index)
.hasName("map_values_index")
.hasParent((TableMetadata) column.getParent())
.isNotCustomIndex()
.hasTarget(ccm().getCassandraVersion().getMajor() > 2 ? "values(map_values)" : "map_values")
.hasKind(COMPOSITES)
.asCqlQuery(createValuesIndex);
assertThat((TableMetadata) column.getParent()).hasIndex(index);
}
@Test(groups = "short")
@CassandraVersion("2.1.0")
public void should_create_metadata_for_index_on_map_keys() {
String createKeysIndex = String.format("CREATE INDEX map_keys_index ON %s.indexing (keys(map_keys));", keyspace);
session().execute(createKeysIndex);
ColumnMetadata column = getColumn("map_keys");
IndexMetadata index = getIndex("map_keys_index");
assertThat(index)
.hasName("map_keys_index")
.hasParent((TableMetadata) column.getParent())
.isNotCustomIndex()
.hasTarget("keys(map_keys)")
.hasKind(COMPOSITES)
.asCqlQuery(createKeysIndex);
assertThat((TableMetadata) column.getParent()).hasIndex(index);
}
@Test(groups = "short")
@CassandraVersion("2.1.3")
public void should_create_metadata_for_full_index_on_map() {
String createFullIndex = String.format("CREATE INDEX map_full_index ON %s.indexing (full(map_full));", keyspace);
session().execute(createFullIndex);
ColumnMetadata column = getColumn("map_full");
IndexMetadata index = getIndex("map_full_index");
assertThat(index)
.hasName("map_full_index")
.hasParent((TableMetadata) column.getParent())
.isNotCustomIndex()
.hasTarget("full(map_full)")
.hasKind(COMPOSITES)
.asCqlQuery(createFullIndex);
assertThat((TableMetadata) column.getParent()).hasIndex(index);
}
@Test(groups = "short")
@CassandraVersion("2.1.3")
public void should_create_metadata_for_full_index_on_set() {
String createFullIndex = String.format("CREATE INDEX set_full_index ON %s.indexing (full(set_full));", keyspace);
session().execute(createFullIndex);
ColumnMetadata column = getColumn("set_full");
IndexMetadata index = getIndex("set_full_index");
assertThat(index)
.hasName("set_full_index")
.hasParent((TableMetadata) column.getParent())
.isNotCustomIndex()
.hasTarget("full(set_full)")
.hasKind(COMPOSITES)
.asCqlQuery(createFullIndex);
assertThat((TableMetadata) column.getParent()).hasIndex(index);
}
@Test(groups = "short")
@CassandraVersion("2.1.3")
public void should_create_metadata_for_full_index_on_list() {
String createFullIndex = String.format("CREATE INDEX list_full_index ON %s.indexing (full(list_full));", keyspace);
session().execute(createFullIndex);
ColumnMetadata column = getColumn("list_full");
IndexMetadata index = getIndex("list_full_index");
assertThat(index)
.hasName("list_full_index")
.hasParent((TableMetadata) column.getParent())
.isNotCustomIndex()
.hasTarget("full(list_full)")
.hasKind(COMPOSITES)
.asCqlQuery(createFullIndex);
assertThat((TableMetadata) column.getParent()).hasIndex(index);
}
@Test(groups = "short")
@CassandraVersion("2.2.0")
public void should_create_metadata_for_index_on_map_entries() {
String createEntriesIndex = String.format("CREATE INDEX map_entries_index ON %s.indexing (entries(map_entries));", keyspace);
session().execute(createEntriesIndex);
ColumnMetadata column = getColumn("map_entries");
IndexMetadata index = getIndex("map_entries_index");
assertThat(index)
.hasName("map_entries_index")
.hasParent((TableMetadata) column.getParent())
.isNotCustomIndex()
.hasTarget("entries(map_entries)")
.hasKind(COMPOSITES)
.asCqlQuery(createEntriesIndex);
assertThat((TableMetadata) column.getParent()).hasIndex(index);
}
@Test(groups = "short")
@CassandraVersion("3.0")
public void should_allow_multiple_indexes_on_map_column() {
String createEntriesIndex = String.format("CREATE INDEX map_all_entries_index ON %s.indexing (entries(map_all));", keyspace);
session().execute(createEntriesIndex);
String createKeysIndex = String.format("CREATE INDEX map_all_keys_index ON %s.indexing (keys(map_all));", keyspace);
session().execute(createKeysIndex);
String createValuesIndex = String.format("CREATE INDEX map_all_values_index ON %s.indexing (values(map_all));", keyspace);
session().execute(createValuesIndex);
ColumnMetadata column = getColumn("map_all");
TableMetadata table = (TableMetadata) column.getParent();
assertThat(getIndex("map_all_entries_index"))
.hasParent(table)
.asCqlQuery(createEntriesIndex);
assertThat(getIndex("map_all_keys_index"))
.hasParent(table)
.asCqlQuery(createKeysIndex);
assertThat(getIndex("map_all_values_index"))
.hasParent(table)
.asCqlQuery(createValuesIndex);
}
@Test(
groups = "short",
description = "This test case builds a ColumnMetadata object programmatically to test custom indices with pre-3.0 layout,"
+ "otherwise, it would require deploying an actual custom index class into the C* test cluster")
public void should_parse_legacy_custom_index_options() {
TableMetadata table = getTable("indexing");
List<ByteBuffer> columnData = ImmutableList.of(
wrap("text_column"), // column name
wrap(0), // component index
wrap("regular"), // column kind
wrap("custom_index"), // index name
wrap("CUSTOM"), // index type
wrap("org.apache.cassandra.db.marshal.UTF8Type"), // validator
wrap("{\"foo\" : \"bar\", \"class_name\" : \"dummy.DummyIndex\"}") // index options
);
Row columnRow = ArrayBackedRow.fromData(legacyColumnDefs, M3PToken.FACTORY, protocolVersion, columnData);
Raw columnRaw = Raw.fromRow(columnRow, VersionNumber.parse("2.1"));
ColumnMetadata column = ColumnMetadata.fromRaw(table, columnRaw, DataType.varchar());
IndexMetadata index = IndexMetadata.fromLegacy(column, columnRaw);
assertThat(index)
.isNotNull()
.hasName("custom_index")
.isCustomIndex()
.hasOption("foo", "bar")
.hasKind(CUSTOM)
.asCqlQuery(String.format("CREATE CUSTOM INDEX custom_index ON %s.indexing (text_column) "
+ "USING 'dummy.DummyIndex' WITH OPTIONS = {'foo' : 'bar'};", keyspace));
}
@Test(
groups = "short",
description = "This test case builds a ColumnMetadata object programmatically to test custom indices with post-3.0 layout,"
+ "otherwise, it would require deploying an actual custom index class into the C* test cluster")
public void should_parse_custom_index_options() {
TableMetadata table = getTable("indexing");
List<ByteBuffer> indexData = ImmutableList.of(
wrap("custom_index"), // index name
wrap("CUSTOM"), // kind
MAP_CODEC.serialize(ImmutableMap.of(
"foo", "bar",
IndexMetadata.CUSTOM_INDEX_OPTION_NAME, "dummy.DummyIndex",
IndexMetadata.TARGET_OPTION_NAME, "a, b, keys(c)"
), protocolVersion) // options
);
Row indexRow = ArrayBackedRow.fromData(indexColumnDefs, M3PToken.FACTORY, protocolVersion, indexData);
IndexMetadata index = IndexMetadata.fromRow(table, indexRow);
assertThat(index)
.isNotNull()
.hasName("custom_index")
.isCustomIndex()
.hasOption("foo", "bar")
.hasTarget("a, b, keys(c)")
.hasKind(CUSTOM)
.asCqlQuery(String.format("CREATE CUSTOM INDEX custom_index ON %s.indexing (a, b, keys(c)) "
+ "USING 'dummy.DummyIndex' WITH OPTIONS = {'foo' : 'bar'};", keyspace));
}
/**
* Validates a special case where a 'KEYS' index was created using thrift. In this particular case the index lacks
* index_options, however the index_options value is a 'null' string rather then a null value.
*
* @test_category metadata
* @expected_result Index properly parsed and is present.
* @jira_ticket JAVA-834
* @since 2.0.11, 2.1.7
*/
@Test(groups = "short")
public void should_parse_with_null_string_index_options() {
TableMetadata table = getTable("indexing");
List<ByteBuffer> data = ImmutableList.of(
wrap("b@706172656e745f70617468"), // column name 'parent_path'
ByteBuffer.allocate(0), // component index (null)
wrap("regular"), // kind
wrap("cfs_archive_parent_path"), // index name
wrap("KEYS"), // index type
wrap("org.apache.cassandra.db.marshal.BytesType"), // validator
wrap("null") // index options
);
Row row = ArrayBackedRow.fromData(legacyColumnDefs, M3PToken.FACTORY, cluster().getConfiguration().getProtocolOptions().getProtocolVersion(), data);
Raw raw = Raw.fromRow(row, VersionNumber.parse("2.1"));
ColumnMetadata column = ColumnMetadata.fromRaw(table, raw, DataType.blob());
IndexMetadata index = IndexMetadata.fromLegacy(column, raw);
assertThat(index)
.isNotNull()
.hasName("cfs_archive_parent_path")
.isNotCustomIndex()
.hasTarget("\"b@706172656e745f70617468\"")
.hasKind(KEYS)
.asCqlQuery(String.format("CREATE INDEX cfs_archive_parent_path ON %s.indexing (\"b@706172656e745f70617468\");", keyspace));
assertThat(index.getOption(IndexMetadata.INDEX_KEYS_OPTION_NAME)).isNull(); // While the index type is KEYS, since it lacks index_options it does not get considered.
}
private static ColumnDefinitions.Definition definition(String name, DataType type) {
return new ColumnDefinitions.Definition("ks", "table", name, type);
}
private static ByteBuffer wrap(String value) {
return ByteBuffer.wrap(value.getBytes());
}
private static ByteBuffer wrap(int number) {
return ByteBuffer.wrap(Ints.toByteArray(number));
}
private ColumnMetadata getColumn(String name) {
return getColumn(name, true);
}
private ColumnMetadata getColumn(String name, boolean fromTable) {
AbstractTableMetadata target = fromTable ? getTable("indexing") : getMaterializedView("mv1");
return target.getColumn(name);
}
private IndexMetadata getIndex(String name) {
return getTable("indexing").getIndex(name);
}
private TableMetadata getTable(String name) {
return cluster().getMetadata().getKeyspace(keyspace).getTable(name);
}
private MaterializedViewMetadata getMaterializedView(String name) {
return cluster().getMetadata().getKeyspace(keyspace).getMaterializedView(name);
}
}