Remove target_columns from index metadata

Patch by Sam Tunnicliffe; reviewed by Sylvain Lebresne for
CASSANDRA-10216
This commit is contained in:
Sam Tunnicliffe 2015-09-16 11:09:02 +01:00
parent 1d94dc2b5b
commit fde97c3b3d
27 changed files with 495 additions and 435 deletions

View File

@ -957,9 +957,18 @@ public final class CFMetaData
{
throw new InvalidRequestException(String.format("Cannot rename non PRIMARY KEY part %s", from));
}
else if (getIndexes().hasIndexFor(def))
if (!getIndexes().isEmpty())
{
throw new InvalidRequestException(String.format("Cannot rename column %s because it is secondary indexed", from));
ColumnFamilyStore store = Keyspace.openAndGetStore(this);
Set<IndexMetadata> dependentIndexes = store.indexManager.getDependentIndexes(def);
if (!dependentIndexes.isEmpty())
throw new InvalidRequestException(String.format("Cannot rename column %s because it has " +
"dependent secondary indexes (%s)",
from,
dependentIndexes.stream()
.map(i -> i.name)
.collect(Collectors.joining(","))));
}
ColumnDefinition newDef = def.withNewName(to);

View File

@ -726,7 +726,8 @@ createIndexStatement returns [CreateIndexStatement expr]
;
indexIdent returns [IndexTarget.Raw id]
: c=cident { $id = IndexTarget.Raw.valuesOf(c); }
: c=cident { $id = IndexTarget.Raw.simpleIndexOn(c); }
| K_VALUES '(' c=cident ')' { $id = IndexTarget.Raw.valuesOf(c); }
| K_KEYS '(' c=cident ')' { $id = IndexTarget.Raw.keysOf(c); }
| K_ENTRIES '(' c=cident ')' { $id = IndexTarget.Raw.keysAndValuesOf(c); }
| K_FULL '(' c=cident ')' { $id = IndexTarget.Raw.fullCollection(c); }

View File

@ -18,6 +18,7 @@
package org.apache.cassandra.cql3.statements;
import java.util.*;
import java.util.stream.Collectors;
import com.google.common.collect.Iterables;
@ -26,6 +27,8 @@ import org.apache.cassandra.config.*;
import org.apache.cassandra.cql3.CFName;
import org.apache.cassandra.cql3.CQL3Type;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.db.marshal.CounterColumnType;
@ -268,17 +271,21 @@ public class AlterTableStatement extends SchemaAlteringStatement
break;
}
// If the dropped column is the only target column of a secondary
// index (and it's only possible to create an index with TargetType.COLUMN
// and a single target right now) we need to also drop the index.
// If the dropped column is required by any secondary indexes
// we reject the operation, as the indexes must be dropped first
Indexes allIndexes = cfm.getIndexes();
Collection<IndexMetadata> indexes = allIndexes.get(def);
for (IndexMetadata index : indexes)
if (!allIndexes.isEmpty())
{
assert index.columns.size() == 1 : String.format("Can't drop column %s as it's a target of multi-column index %s", def.name, index.name);
allIndexes = allIndexes.without(index.name);
ColumnFamilyStore store = Keyspace.openAndGetStore(cfm);
Set<IndexMetadata> dependentIndexes = store.indexManager.getDependentIndexes(def);
if (!dependentIndexes.isEmpty())
throw new InvalidRequestException(String.format("Cannot drop column %s because it has " +
"dependent secondary indexes (%s)",
def,
dependentIndexes.stream()
.map(i -> i.name)
.collect(Collectors.joining(","))));
}
cfm.indexes(allIndexes);
// If a column is dropped which is included in a view, we don't allow the drop to take place.
boolean rejectAlter = false;

View File

@ -22,7 +22,6 @@ import java.util.Map;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -81,9 +80,6 @@ public class CreateIndexStatement extends SchemaAlteringStatement
IndexTarget target = rawTarget.prepare(cfm);
ColumnDefinition cd = cfm.getColumnDefinition(target.column);
if (cd == null)
throw new InvalidRequestException("No column definition found for column " + target.column);
boolean isMap = cd.type instanceof MapType;
boolean isFrozenCollection = cd.type.isCollection() && !cd.type.isMultiCell();
@ -94,7 +90,7 @@ public class CreateIndexStatement extends SchemaAlteringStatement
else
{
validateNotFullIndex(target);
validateIsValuesIndexIfTargetColumnNotCollection(cd, target);
validateIsSimpleIndexIfTargetColumnNotCollection(cd, target);
validateTargetColumnIsMapIfIndexInvolvesKeys(isMap, target);
}
@ -136,7 +132,9 @@ public class CreateIndexStatement extends SchemaAlteringStatement
private void validateForFrozenCollection(IndexTarget target) throws InvalidRequestException
{
if (target.type != IndexTarget.Type.FULL)
throw new InvalidRequestException(String.format("Cannot create index on %s of frozen<map> column %s", target.type, target.column));
throw new InvalidRequestException(String.format("Cannot create %s() index on frozen column %s. " +
"Frozen collections only support full() indexes",
target.type, target.column));
}
private void validateNotFullIndex(IndexTarget target) throws InvalidRequestException
@ -145,11 +143,12 @@ public class CreateIndexStatement extends SchemaAlteringStatement
throw new InvalidRequestException("full() indexes can only be created on frozen collections");
}
private void validateIsValuesIndexIfTargetColumnNotCollection(ColumnDefinition cd, IndexTarget target) throws InvalidRequestException
private void validateIsSimpleIndexIfTargetColumnNotCollection(ColumnDefinition cd, IndexTarget target) throws InvalidRequestException
{
if (!cd.type.isCollection() && target.type != IndexTarget.Type.VALUES)
throw new InvalidRequestException(String.format("Cannot create index on %s of column %s; only non-frozen collections support %s indexes",
target.type, target.column, target.type));
if (!cd.type.isCollection() && target.type != IndexTarget.Type.SIMPLE)
throw new InvalidRequestException(String.format("Cannot create %s() index on %s. " +
"Non-collection columns support only simple indexes",
target.type.toString(), target.column));
}
private void validateTargetColumnIsMapIfIndexInvolvesKeys(boolean isMap, IndexTarget target) throws InvalidRequestException
@ -180,31 +179,20 @@ public class CreateIndexStatement extends SchemaAlteringStatement
throw new InvalidRequestException(String.format("Index %s already exists", acceptedName));
}
IndexMetadata.IndexType indexType;
IndexMetadata.Kind kind;
Map<String, String> indexOptions;
if (properties.isCustom)
{
indexType = IndexMetadata.IndexType.CUSTOM;
kind = IndexMetadata.Kind.CUSTOM;
indexOptions = properties.getOptions();
}
else if (cfm.isCompound())
{
Map<String, String> options = Collections.emptyMap();
// For now, we only allow indexing values for collections, but we could later allow
// to also index map keys, so we record that this is the values we index to make our
// lives easier then.
if (cd.type.isCollection() && cd.type.isMultiCell())
options = ImmutableMap.of(target.type.indexOption(), "");
indexType = IndexMetadata.IndexType.COMPOSITES;
indexOptions = options;
}
else
{
indexType = IndexMetadata.IndexType.KEYS;
indexOptions = Collections.emptyMap();
kind = cfm.isCompound() ? IndexMetadata.Kind.COMPOSITES : IndexMetadata.Kind.KEYS;
}
IndexMetadata index = IndexMetadata.singleColumnIndex(cd, acceptedName, indexType, indexOptions);
IndexMetadata index = IndexMetadata.singleTargetIndex(cfm, target, acceptedName, kind, indexOptions);
// check to disallow creation of an index which duplicates an existing one in all but name
Optional<IndexMetadata> existingIndex = Iterables.tryFind(cfm.getIndexes(), existing -> existing.equalsWithoutName(index));

View File

@ -54,6 +54,11 @@ public class IndexPropDefs extends PropertyDefinitions
if (getRawOptions().containsKey(IndexTarget.CUSTOM_INDEX_OPTION_NAME))
throw new InvalidRequestException(String.format("Cannot specify %s as a CUSTOM option",
IndexTarget.CUSTOM_INDEX_OPTION_NAME));
if (getRawOptions().containsKey(IndexTarget.TARGET_OPTION_NAME))
throw new InvalidRequestException(String.format("Cannot specify %s as a CUSTOM option",
IndexTarget.TARGET_OPTION_NAME));
}
public Map<String, String> getRawOptions() throws SyntaxException

View File

@ -17,15 +17,16 @@
*/
package org.apache.cassandra.cql3.statements;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.exceptions.InvalidRequestException;
public class IndexTarget
{
public static final String TARGET_OPTION_NAME = "target";
public static final String CUSTOM_INDEX_OPTION_NAME = "class_name";
/**
@ -33,23 +34,44 @@ public class IndexTarget
*/
public static final String INDEX_KEYS_OPTION_NAME = "index_keys";
/**
* The name of the option used to specify that the index is on the collection values.
*/
public static final String INDEX_VALUES_OPTION_NAME = "index_values";
/**
* The name of the option used to specify that the index is on the collection (map) entries.
*/
public static final String INDEX_ENTRIES_OPTION_NAME = "index_keys_and_values";
/**
* Regex for *unquoted* column names, anything which does not match this pattern must be a quoted name
*/
private static final Pattern COLUMN_IDENTIFIER_PATTERN = Pattern.compile("[a-z_0-9]+");
public final ColumnIdentifier column;
public final boolean quoteName;
public final Type type;
private IndexTarget(ColumnIdentifier column, Type type)
public IndexTarget(ColumnIdentifier column, Type type)
{
this.column = column;
this.type = type;
// if the column name contains anything other than lower case alphanumerics
// or underscores, then it must be quoted when included in the target string
quoteName = !COLUMN_IDENTIFIER_PATTERN.matcher(column.toString()).matches();
}
public String asCqlString(CFMetaData cfm)
{
if (! cfm.getColumnDefinition(column).type.isCollection())
return maybeEscapeQuotedName(column.toString());
return String.format("%s(%s)", type.toString(), maybeEscapeQuotedName(column.toString()));
}
// Quoted column names may themselves contain quotes, these need
// to be escaped with a preceding quote when written out as cql.
// Of course, the escaped name also needs to be wrapped in quotes.
private String maybeEscapeQuotedName(String name)
{
return quoteName ? '\"' + name.replace("\"", "\"\"") + '\"' : name;
}
public static class Raw
@ -63,6 +85,11 @@ public class IndexTarget
this.type = type;
}
public static Raw simpleIndexOn(ColumnIdentifier.Raw c)
{
return new Raw(c, Type.SIMPLE);
}
public static Raw valuesOf(ColumnIdentifier.Raw c)
{
return new Raw(c, Type.VALUES);
@ -85,13 +112,24 @@ public class IndexTarget
public IndexTarget prepare(CFMetaData cfm)
{
return new IndexTarget(column.prepare(cfm), type);
// Until we've prepared the target column, we can't be certain about the target type
// because (for backwards compatibility) an index on a collection's values uses the
// same syntax as an index on a regular column (i.e. the 'values' in
// 'CREATE INDEX on table(values(collection));' is optional). So we correct the target type
// when the target column is a collection & the target type is SIMPLE.
ColumnIdentifier colId = column.prepare(cfm);
ColumnDefinition columnDef = cfm.getColumnDefinition(colId);
if (columnDef == null)
throw new InvalidRequestException("No column definition found for column " + colId);
Type actualType = (type == Type.SIMPLE && columnDef.type.isCollection()) ? Type.VALUES : type;
return new IndexTarget(colId, actualType);
}
}
public static enum Type
{
VALUES, KEYS, KEYS_AND_VALUES, FULL;
VALUES, KEYS, KEYS_AND_VALUES, FULL, SIMPLE;
public String toString()
{
@ -100,44 +138,26 @@ public class IndexTarget
case KEYS: return "keys";
case KEYS_AND_VALUES: return "entries";
case FULL: return "full";
default: return "values";
case VALUES: return "values";
case SIMPLE: return "";
default: return "";
}
}
public String indexOption()
public static Type fromString(String s)
{
switch (this)
{
case KEYS: return INDEX_KEYS_OPTION_NAME;
case KEYS_AND_VALUES: return INDEX_ENTRIES_OPTION_NAME;
case VALUES: return INDEX_VALUES_OPTION_NAME;
default: throw new AssertionError();
}
}
public static Type fromIndexMetadata(IndexMetadata index, CFMetaData cfm)
{
Map<String, String> options = index.options;
if (options.containsKey(INDEX_KEYS_OPTION_NAME))
{
if ("".equals(s))
return SIMPLE;
else if ("values".equals(s))
return VALUES;
else if ("keys".equals(s))
return KEYS;
}
else if (options.containsKey(INDEX_ENTRIES_OPTION_NAME))
{
else if ("entries".equals(s))
return KEYS_AND_VALUES;
}
else
{
ColumnDefinition cd = index.indexedColumn(cfm);
if (cd.type.isCollection() && !cd.type.isMultiCell())
{
return FULL;
}
else
{
return VALUES;
}
}
else if ("full".equals(s))
return FULL;
throw new AssertionError("Unrecognized index target type " + s);
}
}
}

View File

@ -202,6 +202,19 @@ public interface Index
*/
public boolean indexes(PartitionColumns columns);
/**
* Called to determine whether this index targets a specific column.
* Used during schema operations such as when dropping or renaming a column, to check if
* the index will be affected by the change. Typically, if an index answers that it does
* depend upon a column, then schema operations on that column are not permitted until the index
* is dropped or altered.
*
* @param column the column definition to check
* @return true if the index depends on the supplied column being present; false if the column may be
* safely dropped or modified without adversely affecting the index
*/
public boolean dependsOn(ColumnDefinition column);
// TODO : this will change when we decouple indexes from specific columns for real per-row indexes
/**
* Called to determine whether this index can provide a searcher to execute a query on the

View File

@ -31,12 +31,10 @@ import com.google.common.collect.Maps;
import com.google.common.primitives.Longs;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
import org.apache.commons.lang3.StringUtils;
import org.apache.cassandra.db.Directories;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.concurrent.JMXEnabledThreadPoolExecutor;
import org.apache.cassandra.concurrent.NamedThreadFactory;
import org.apache.cassandra.concurrent.StageManager;
@ -188,6 +186,21 @@ public class SecondaryIndexManager implements IndexRegistry
}
}
public Set<IndexMetadata> getDependentIndexes(ColumnDefinition column)
{
if (indexes.isEmpty())
return Collections.emptySet();
Set<IndexMetadata> dependentIndexes = new HashSet<>();
for (Index index : indexes.values())
if (index.dependsOn(column))
dependentIndexes.add(index.getIndexMetadata());
return dependentIndexes;
}
/**
* Called when dropping a Table
*/

View File

@ -5,14 +5,18 @@ import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.Operator;
import org.apache.cassandra.cql3.statements.IndexTarget;
import org.apache.cassandra.db.*;
@ -38,6 +42,7 @@ import org.apache.cassandra.io.sstable.ReducingKeyIterator;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.concurrent.OpOrder;
import org.apache.cassandra.utils.concurrent.Refs;
@ -49,13 +54,15 @@ public abstract class CassandraIndex implements Index
{
private static final Logger logger = LoggerFactory.getLogger(CassandraIndex.class);
public static final Pattern TARGET_REGEX = Pattern.compile("^(keys|entries|values|full)\\((.+)\\)$");
public final ColumnFamilyStore baseCfs;
protected IndexMetadata metadata;
protected ColumnFamilyStore indexCfs;
protected ColumnDefinition indexedColumn;
protected CassandraIndexFunctions functions;
public CassandraIndex(ColumnFamilyStore baseCfs, IndexMetadata indexDef)
protected CassandraIndex(ColumnFamilyStore baseCfs, IndexMetadata indexDef)
{
this.baseCfs = baseCfs;
setMetadata(indexDef);
@ -69,7 +76,7 @@ public abstract class CassandraIndex implements Index
*/
protected boolean supportsOperator(ColumnDefinition indexedColumn, Operator operator)
{
return operator.equals(Operator.EQ);
return operator == Operator.EQ;
}
/**
@ -77,7 +84,6 @@ public abstract class CassandraIndex implements Index
* The clustering columns in the index table encode the values required to retrieve the correct data from the base
* table and varies depending on the kind of the indexed column. See indexCfsMetadata for more details
* Used whenever a row in the index table is written or deleted.
* @param metadata
* @param partitionKey from the base data being indexed
* @param prefix from the base data being indexed
* @param path from the base data being indexed
@ -90,7 +96,6 @@ public abstract class CassandraIndex implements Index
/**
* Used at search time to convert a row in the index table into a simple struct containing the values required
* to retrieve the corresponding row from the base table.
* @param metadata
* @param indexedValue the partition key of the indexed table (i.e. the value that was indexed)
* @param indexEntry a row from the index table
* @return
@ -102,7 +107,6 @@ public abstract class CassandraIndex implements Index
* Check whether a value retrieved from an index is still valid by comparing it to current row from the base table.
* Used at read time to identify out of date index entries so that they can be excluded from search results and
* repaired
* @param metadata required to get the indexed column definition
* @param row the current row from the primary data table
* @param indexValue the value we retrieved from the index
* @param nowInSec
@ -112,7 +116,6 @@ public abstract class CassandraIndex implements Index
/**
* Extract the value to be inserted into the index from the components of the base data
* @param metadata
* @param partitionKey from the primary data
* @param clustering from the primary data
* @param path from the primary data
@ -197,14 +200,14 @@ public abstract class CassandraIndex implements Index
private void setMetadata(IndexMetadata indexDef)
{
metadata = indexDef;
functions = getFunctions(baseCfs.metadata, indexDef);
Pair<ColumnDefinition, IndexTarget.Type> target = parseTarget(baseCfs.metadata, indexDef);
functions = getFunctions(indexDef, target);
CFMetaData cfm = indexCfsMetadata(baseCfs.metadata, indexDef);
indexCfs = ColumnFamilyStore.createColumnFamilyStore(baseCfs.keyspace,
cfm.cfName,
cfm,
baseCfs.getTracker().loadsstables);
assert indexDef.columns.size() == 1 : "Build in indexes on multiple target columns are not supported";
indexedColumn = indexDef.indexedColumn(baseCfs.metadata);
indexedColumn = target.left;
}
public Callable<?> getTruncateTask(final long truncatedAt)
@ -227,6 +230,11 @@ public abstract class CassandraIndex implements Index
return isPrimaryKeyIndex() || columns.contains(indexedColumn);
}
public boolean dependsOn(ColumnDefinition column)
{
return indexedColumn.name.equals(column.name);
}
public boolean supportsExpression(ColumnDefinition column, Operator operator)
{
return indexedColumn.name.equals(column.name)
@ -269,7 +277,7 @@ public abstract class CassandraIndex implements Index
if (target.isPresent())
{
target.get().validateForIndexing();
switch (getIndexMetadata().indexType)
switch (getIndexMetadata().kind)
{
case COMPOSITES:
return new CompositesSearcher(command, target.get(), this);
@ -277,7 +285,7 @@ public abstract class CassandraIndex implements Index
return new KeysSearcher(command, target.get(), this);
default:
throw new IllegalStateException(String.format("Unsupported index type %s for index %s on %s",
metadata.indexType,
metadata.kind,
metadata.name,
indexedColumn.name.toString()));
}
@ -531,7 +539,7 @@ public abstract class CassandraIndex implements Index
private void validatePartitionKey(DecoratedKey partitionKey) throws InvalidRequestException
{
assert indexedColumn.isPartitionKey();
validateIndexedValue(getIndexedValue(partitionKey.getKey(), null, null ));
validateIndexedValue(getIndexedValue(partitionKey.getKey(), null, null));
}
private void validateClusterings(PartitionUpdate update) throws InvalidRequestException
@ -613,7 +621,7 @@ public abstract class CassandraIndex implements Index
Collection<ColumnFamilyStore> cfss = Collections.singleton(indexCfs);
CompactionManager.instance.interruptCompactionForCFs(cfss, true);
CompactionManager.instance.waitForCessation(cfss);
indexCfs.keyspace.writeOrder.awaitNewBarrier();
Keyspace.writeOrder.awaitNewBarrier();
indexCfs.forceBlockingFlush();
indexCfs.readOrdering.awaitNewBarrier();
indexCfs.invalidate();
@ -695,8 +703,9 @@ public abstract class CassandraIndex implements Index
*/
public static final CFMetaData indexCfsMetadata(CFMetaData baseCfsMetadata, IndexMetadata indexMetadata)
{
CassandraIndexFunctions utils = getFunctions(baseCfsMetadata, indexMetadata);
ColumnDefinition indexedColumn = indexMetadata.indexedColumn(baseCfsMetadata);
Pair<ColumnDefinition, IndexTarget.Type> target = parseTarget(baseCfsMetadata, indexMetadata);
CassandraIndexFunctions utils = getFunctions(indexMetadata, target);
ColumnDefinition indexedColumn = target.left;
AbstractType<?> indexedValueType = utils.getIndexedValueType(indexedColumn);
CFMetaData.Builder builder = CFMetaData.Builder.create(baseCfsMetadata.ksName,
baseCfsMetadata.indexColumnFamilyName(indexMetadata))
@ -715,17 +724,65 @@ public abstract class CassandraIndex implements Index
* @param indexMetadata
* @return
*/
public static final CassandraIndex newIndex(ColumnFamilyStore baseCfs, IndexMetadata indexMetadata)
public static CassandraIndex newIndex(ColumnFamilyStore baseCfs, IndexMetadata indexMetadata)
{
return getFunctions(baseCfs.metadata, indexMetadata).newIndexInstance(baseCfs, indexMetadata);
return getFunctions(indexMetadata, parseTarget(baseCfs.metadata, indexMetadata)).newIndexInstance(baseCfs, indexMetadata);
}
private static CassandraIndexFunctions getFunctions(CFMetaData baseCfMetadata, IndexMetadata indexDef)
// Public because it's also used to convert index metadata into a thrift-compatible format
public static Pair<ColumnDefinition, IndexTarget.Type> parseTarget(CFMetaData cfm,
IndexMetadata indexDef)
{
String target = indexDef.options.get("target");
assert target != null : String.format("No target definition found for index %s", indexDef.name);
// if the regex matches then the target is in the form "keys(foo)", "entries(bar)" etc
// if not, then it must be a simple column name and implictly its type is VALUES
Matcher matcher = TARGET_REGEX.matcher(target);
String columnName;
IndexTarget.Type targetType;
if (matcher.matches())
{
targetType = IndexTarget.Type.fromString(matcher.group(1));
columnName = matcher.group(2);
}
else
{
columnName = target;
targetType = IndexTarget.Type.VALUES;
}
// in the case of a quoted column name the name in the target string
// will be enclosed in quotes, which we need to unwrap. It may also
// include quote characters internally, escaped like so:
// abc"def -> abc""def.
// Because the target string is stored in a CQL compatible form, we
// need to un-escape any such quotes to get the actual column name
if (columnName.startsWith("\""))
{
columnName = StringUtils.substring(StringUtils.substring(columnName, 1), 0, -1);
columnName = columnName.replaceAll("\"\"", "\"");
}
// if it's not a CQL table, we can't assume that the column name is utf8, so
// in that case we have to do a linear scan of the cfm's columns to get the matching one
if (cfm.isCQLTable())
return Pair.create(cfm.getColumnDefinition(new ColumnIdentifier(columnName, true)), targetType);
else
for (ColumnDefinition column : cfm.allColumns())
if (column.name.toString().equals(columnName))
return Pair.create(column, targetType);
throw new RuntimeException(String.format("Unable to parse targets for index %s (%s)", indexDef.name, target));
}
static CassandraIndexFunctions getFunctions(IndexMetadata indexDef,
Pair<ColumnDefinition, IndexTarget.Type> target)
{
if (indexDef.isKeys())
return CassandraIndexFunctions.KEYS_INDEX_FUNCTIONS;
ColumnDefinition indexedColumn = indexDef.indexedColumn(baseCfMetadata);
ColumnDefinition indexedColumn = target.left;
if (indexedColumn.type.isCollection() && indexedColumn.type.isMultiCell())
{
switch (((CollectionType)indexedColumn.type).kind)
@ -735,12 +792,16 @@ public abstract class CassandraIndex implements Index
case SET:
return CassandraIndexFunctions.COLLECTION_KEY_INDEX_FUNCTIONS;
case MAP:
if (indexDef.options.containsKey(IndexTarget.INDEX_KEYS_OPTION_NAME))
return CassandraIndexFunctions.COLLECTION_KEY_INDEX_FUNCTIONS;
else if (indexDef.options.containsKey(IndexTarget.INDEX_ENTRIES_OPTION_NAME))
return CassandraIndexFunctions.COLLECTION_ENTRY_INDEX_FUNCTIONS;
else
return CassandraIndexFunctions.COLLECTION_VALUE_INDEX_FUNCTIONS;
switch (target.right)
{
case KEYS:
return CassandraIndexFunctions.COLLECTION_KEY_INDEX_FUNCTIONS;
case KEYS_AND_VALUES:
return CassandraIndexFunctions.COLLECTION_ENTRY_INDEX_FUNCTIONS;
case VALUES:
return CassandraIndexFunctions.COLLECTION_VALUE_INDEX_FUNCTIONS;
}
throw new AssertionError();
}
}

View File

@ -20,11 +20,13 @@ package org.apache.cassandra.schema;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.slf4j.Logger;
@ -50,53 +52,78 @@ public final class IndexMetadata
public static final Serializer serializer = new Serializer();
public enum IndexType
public enum Kind
{
KEYS, CUSTOM, COMPOSITES
}
public enum TargetType
{
COLUMN, ROW
}
// UUID for serialization. This is a deterministic UUID generated from the index name
// Both the id and name are guaranteed unique per keyspace.
public final UUID id;
public final String name;
public final IndexType indexType;
public final TargetType targetType;
public final Kind kind;
public final Map<String, String> options;
public final Set<ColumnIdentifier> columns;
private IndexMetadata(String name,
Map<String, String> options,
IndexType indexType,
TargetType targetType,
Set<ColumnIdentifier> columns)
Kind kind)
{
this.id = UUID.nameUUIDFromBytes(name.getBytes());
this.name = name;
this.options = options == null ? ImmutableMap.of() : ImmutableMap.copyOf(options);
this.indexType = indexType;
this.targetType = targetType;
this.columns = columns == null ? ImmutableSet.of() : ImmutableSet.copyOf(columns);
this.kind = kind;
}
public static IndexMetadata singleColumnIndex(ColumnIdentifier column,
String name,
IndexType type,
Map<String, String> options)
public static IndexMetadata fromLegacyMetadata(CFMetaData cfm,
ColumnDefinition column,
String name,
Kind kind,
Map<String, String> options)
{
return new IndexMetadata(name, options, type, TargetType.COLUMN, Collections.singleton(column));
Map<String, String> newOptions = new HashMap<>();
if (options != null)
newOptions.putAll(options);
IndexTarget target;
if (newOptions.containsKey(IndexTarget.INDEX_KEYS_OPTION_NAME))
{
newOptions.remove(IndexTarget.INDEX_KEYS_OPTION_NAME);
target = new IndexTarget(column.name, IndexTarget.Type.KEYS);
}
else if (newOptions.containsKey(IndexTarget.INDEX_ENTRIES_OPTION_NAME))
{
newOptions.remove(IndexTarget.INDEX_KEYS_OPTION_NAME);
target = new IndexTarget(column.name, IndexTarget.Type.KEYS_AND_VALUES);
}
else
{
if (column.type.isCollection() && !column.type.isMultiCell())
{
target = new IndexTarget(column.name, IndexTarget.Type.FULL);
}
else
{
target = new IndexTarget(column.name, IndexTarget.Type.VALUES);
}
}
newOptions.put(IndexTarget.TARGET_OPTION_NAME, target.asCqlString(cfm));
return new IndexMetadata(name, newOptions, kind);
}
public static IndexMetadata singleColumnIndex(ColumnDefinition column,
public static IndexMetadata fromSchemaMetadata(String name, Kind kind, Map<String, String> options)
{
return new IndexMetadata(name, options, kind);
}
public static IndexMetadata singleTargetIndex(CFMetaData cfm,
IndexTarget target,
String name,
IndexType type,
Kind kind,
Map<String, String> options)
{
return singleColumnIndex(column.name, name, type, options);
Map<String, String> newOptions = new HashMap<>(options);
newOptions.put(IndexTarget.TARGET_OPTION_NAME, target.asCqlString(cfm));
return new IndexMetadata(name, newOptions, kind);
}
public static boolean isNameValid(String name)
@ -115,13 +142,10 @@ public final class IndexMetadata
if (!isNameValid(name))
throw new ConfigurationException("Illegal index name " + name);
if (indexType == null)
throw new ConfigurationException("Index type is null for index " + name);
if (kind == null)
throw new ConfigurationException("Index kind is null for index " + name);
if (targetType == null)
throw new ConfigurationException("Target type is null for index " + name);
if (indexType == IndexMetadata.IndexType.CUSTOM)
if (kind == Kind.CUSTOM)
{
if (options == null || !options.containsKey(IndexTarget.CUSTOM_INDEX_OPTION_NAME))
throw new ConfigurationException(String.format("Required option missing for index %s : %s",
@ -169,47 +193,29 @@ public final class IndexMetadata
}
}
// to be removed in CASSANDRA-10124 with multi-target & row based indexes
public ColumnDefinition indexedColumn(CFMetaData cfm)
{
return cfm.getColumnDefinition(columns.iterator().next());
}
public boolean isCustom()
{
return indexType == IndexType.CUSTOM;
return kind == Kind.CUSTOM;
}
public boolean isKeys()
{
return indexType == IndexType.KEYS;
return kind == Kind.KEYS;
}
public boolean isComposites()
{
return indexType == IndexType.COMPOSITES;
}
public boolean isRowIndex()
{
return targetType == TargetType.ROW;
}
public boolean isColumnIndex()
{
return targetType == TargetType.COLUMN;
return kind == Kind.COMPOSITES;
}
public int hashCode()
{
return Objects.hashCode(id, name, indexType, targetType, options, columns);
return Objects.hashCode(id, name, kind, options);
}
public boolean equalsWithoutName(IndexMetadata other)
{
return Objects.equal(indexType, other.indexType)
&& Objects.equal(targetType, other.targetType)
&& Objects.equal(columns, other.columns)
return Objects.equal(kind, other.kind)
&& Objects.equal(options, other.options);
}
@ -231,9 +237,7 @@ public final class IndexMetadata
return new ToStringBuilder(this)
.append("id", id.toString())
.append("name", name)
.append("indexType", indexType)
.append("targetType", targetType)
.append("columns", columns)
.append("kind", kind)
.append("options", options)
.build();
}
@ -255,6 +259,5 @@ public final class IndexMetadata
{
return UUIDSerializer.serializer.serializedSize(metadata.id, version);
}
}
}

View File

@ -21,9 +21,7 @@ package org.apache.cassandra.schema;
import java.util.*;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.config.Schema;
import org.apache.cassandra.cql3.ColumnIdentifier;
@ -42,13 +40,11 @@ public class Indexes implements Iterable<IndexMetadata>
{
private final ImmutableMap<String, IndexMetadata> indexesByName;
private final ImmutableMap<UUID, IndexMetadata> indexesById;
private final ImmutableMultimap<ColumnIdentifier, IndexMetadata> indexesByColumn;
private Indexes(Builder builder)
{
indexesByName = builder.indexesByName.build();
indexesById = builder.indexesById.build();
indexesByColumn = builder.indexesByColumn.build();
}
public static Builder builder()
@ -120,28 +116,6 @@ public class Indexes implements Iterable<IndexMetadata>
return indexesById.containsKey(id);
}
/**
* Get the index associated with the specified column. This may be removed or modified as support is added
* for indexes with multiple target columns and with TargetType.ROW
*
* @param column a column definition for which an {@link IndexMetadata} is being sought
* @return an empty {@link Optional} if the named index is not found; a non-empty optional of {@link IndexMetadata} otherwise
*/
public Collection<IndexMetadata> get(ColumnDefinition column)
{
return indexesByColumn.get(column.name);
}
/**
* Answer true if an index is associated with the specified column.
* @param column
* @return
*/
public boolean hasIndexFor(ColumnDefinition column)
{
return !indexesByColumn.get(column.name).isEmpty();
}
/**
* Create a SecondaryIndexes instance with the provided index added
*/
@ -206,7 +180,6 @@ public class Indexes implements Iterable<IndexMetadata>
{
final ImmutableMap.Builder<String, IndexMetadata> indexesByName = new ImmutableMap.Builder<>();
final ImmutableMap.Builder<UUID, IndexMetadata> indexesById = new ImmutableMap.Builder<>();
final ImmutableMultimap.Builder<ColumnIdentifier, IndexMetadata> indexesByColumn = new ImmutableMultimap.Builder<>();
private Builder()
{
@ -221,13 +194,6 @@ public class Indexes implements Iterable<IndexMetadata>
{
indexesByName.put(index.name, index);
indexesById.put(index.id, index);
// All indexes are column indexes at the moment
if (index.isColumnIndex())
{
for (ColumnIdentifier target : index.columns)
indexesByColumn.put(target, index);
}
return this;
}

View File

@ -296,15 +296,6 @@ public final class LegacySchemaMigrator
isStaticCompactTable,
needsUpgrade);
Indexes indexes = createIndexesFromColumnRows(columnRows,
ksName,
cfName,
rawComparator,
subComparator,
isSuper,
isCQLTable,
isStaticCompactTable,
needsUpgrade);
if (needsUpgrade)
{
@ -328,6 +319,17 @@ public final class LegacySchemaMigrator
false, // legacy schema did not contain views
columnDefs,
DatabaseDescriptor.getPartitioner());
Indexes indexes = createIndexesFromColumnRows(cfm,
columnRows,
ksName,
cfName,
rawComparator,
subComparator,
isSuper,
isCQLTable,
isStaticCompactTable,
needsUpgrade);
cfm.indexes(indexes);
if (tableRow.has("dropped_columns"))
@ -591,7 +593,8 @@ public final class LegacySchemaMigrator
return new ColumnDefinition(keyspace, table, name, validator, componentIndex, kind);
}
private static Indexes createIndexesFromColumnRows(UntypedResultSet rows,
private static Indexes createIndexesFromColumnRows(CFMetaData cfm,
UntypedResultSet rows,
String keyspace,
String table,
AbstractType<?> rawComparator,
@ -605,11 +608,11 @@ public final class LegacySchemaMigrator
for (UntypedResultSet.Row row : rows)
{
IndexMetadata.IndexType indexType = null;
IndexMetadata.Kind kind = null;
if (row.has("index_type"))
indexType = IndexMetadata.IndexType.valueOf(row.getString("index_type"));
kind = IndexMetadata.Kind.valueOf(row.getString("index_type"));
if (indexType == null)
if (kind == null)
continue;
Map<String, String> indexOptions = null;
@ -630,7 +633,7 @@ public final class LegacySchemaMigrator
isStaticCompactTable,
needsUpgrade);
indexes.add(IndexMetadata.singleColumnIndex(column, indexName, indexType, indexOptions));
indexes.add(IndexMetadata.fromLegacyMetadata(cfm, column, indexName, kind, indexOptions));
}
return indexes.build();

View File

@ -177,10 +177,8 @@ public final class SchemaKeyspace
+ "keyspace_name text,"
+ "table_name text,"
+ "index_name text,"
+ "index_type text,"
+ "kind text,"
+ "options frozen<map<text, text>>,"
+ "target_columns frozen<set<text>>,"
+ "target_type text,"
+ "PRIMARY KEY ((keyspace_name), table_name, index_name))");
private static final CFMetaData Types =
@ -1449,10 +1447,8 @@ public final class SchemaKeyspace
{
RowUpdateBuilder builder = new RowUpdateBuilder(Indexes, timestamp, mutation).clustering(table.cfName, index.name);
builder.add("index_type", index.indexType.toString());
builder.add("kind", index.kind.toString());
builder.frozenMap("options", index.options);
builder.frozenSet("target_columns", index.columns.stream().map(ColumnIdentifier::toString).collect(Collectors.toSet()));
builder.add("target_type", index.targetType.toString());
builder.build();
}
@ -1481,46 +1477,16 @@ public final class SchemaKeyspace
{
Indexes.Builder indexes = org.apache.cassandra.schema.Indexes.builder();
String query = String.format("SELECT * FROM %s.%s", NAME, INDEXES);
QueryProcessor.resultify(query, partition).forEach(row -> indexes.add(createIndexMetadataFromIndexesRow(cfm, row)));
QueryProcessor.resultify(query, partition).forEach(row -> indexes.add(createIndexMetadataFromIndexesRow(row)));
return indexes.build();
}
private static IndexMetadata createIndexMetadataFromIndexesRow(CFMetaData cfm, UntypedResultSet.Row row)
private static IndexMetadata createIndexMetadataFromIndexesRow(UntypedResultSet.Row row)
{
String name = row.getString("index_name");
IndexMetadata.IndexType type = IndexMetadata.IndexType.valueOf(row.getString("index_type"));
IndexMetadata.TargetType targetType = IndexMetadata.TargetType.valueOf(row.getString("target_type"));
IndexMetadata.Kind type = IndexMetadata.Kind.valueOf(row.getString("kind"));
Map<String, String> options = row.getFrozenTextMap("options");
if (options == null)
options = Collections.emptyMap();
Set<String> targetColumnNames = row.getFrozenSet("target_columns", UTF8Type.instance);
assert targetType == IndexMetadata.TargetType.COLUMN : "Per row indexes with dynamic target columns are not supported yet";
Set<ColumnIdentifier> targetColumns = new HashSet<>();
// if it's not a CQL table, we can't assume that the column name is utf8, so
// in that case we have to do a linear scan of the cfm's columns to get the matching one
if (targetColumnNames != null)
{
assert targetColumnNames.size() == 1 : "Secondary indexes targetting multiple columns are not supported yet";
targetColumnNames.forEach(targetColumnName -> {
if (cfm.isCQLTable())
targetColumns.add(ColumnIdentifier.getInterned(targetColumnName, true));
else
findColumnIdentifierWithName(targetColumnName, cfm.allColumns()).ifPresent(targetColumns::add);
});
}
return IndexMetadata.singleColumnIndex(targetColumns.iterator().next(), name, type, options);
}
private static Optional<ColumnIdentifier> findColumnIdentifierWithName(String name,
Iterable<ColumnDefinition> columns)
{
for (ColumnDefinition column : columns)
if (column.name.toString().equals(name))
return Optional.of(column.name);
return Optional.empty();
return IndexMetadata.fromSchemaMetadata(name, type, options);
}
/*

View File

@ -18,6 +18,7 @@
package org.apache.cassandra.thrift;
import java.util.*;
import java.util.regex.Matcher;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
@ -26,6 +27,7 @@ import com.google.common.collect.Maps;
import org.apache.cassandra.config.*;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.Operator;
import org.apache.cassandra.cql3.statements.IndexTarget;
import org.apache.cassandra.db.CompactTables;
import org.apache.cassandra.db.LegacyLayout;
import org.apache.cassandra.db.WriteType;
@ -33,6 +35,7 @@ import org.apache.cassandra.db.compaction.AbstractCompactionStrategy;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.index.internal.CassandraIndex;
import org.apache.cassandra.io.compress.ICompressor;
import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.locator.LocalStrategy;
@ -287,7 +290,8 @@ public class ThriftConversion
DatabaseDescriptor.getPartitioner());
// Convert any secondary indexes defined in the thrift column_metadata
newCFMD.indexes(indexDefsFromThrift(cf_def.keyspace,
newCFMD.indexes(indexDefsFromThrift(newCFMD,
cf_def.keyspace,
cf_def.name,
rawComparator,
subComparator,
@ -526,7 +530,8 @@ public class ThriftConversion
return defs;
}
private static Indexes indexDefsFromThrift(String ksName,
private static Indexes indexDefsFromThrift(CFMetaData cfm,
String ksName,
String cfName,
AbstractType<?> thriftComparator,
AbstractType<?> thriftSubComparator,
@ -554,12 +559,12 @@ public class ThriftConversion
indexNames.add(indexName);
Map<String, String> indexOptions = def.getIndex_options();
IndexMetadata.IndexType indexType = IndexMetadata.IndexType.valueOf(def.index_type.name());
if (indexOptions != null && indexOptions.containsKey(IndexTarget.TARGET_OPTION_NAME))
throw new ConfigurationException("Reserved index option 'target' cannot be used");
indexes.add(IndexMetadata.singleColumnIndex(column,
indexName,
indexType,
indexOptions));
IndexMetadata.Kind kind = IndexMetadata.Kind.valueOf(def.index_type.name());
indexes.add(IndexMetadata.fromLegacyMetadata(cfm, column, indexName, kind, indexOptions));
}
}
return indexes.build();
@ -572,22 +577,44 @@ public class ThriftConversion
cd.setName(ByteBufferUtil.clone(column.name.bytes));
cd.setValidation_class(column.type.toString());
Collection<IndexMetadata> indexes = cfMetaData.getIndexes().get(column);
// we include the index in the ColumnDef iff
// * it is the only index on the column
// * it is the only target column for the index
if (indexes.size() == 1)
// we include the index in the ColumnDef iff its targets are compatible with
// pre-3.0 indexes AND it is the only index defined on the given column, that is:
// * it is the only index on the column (i.e. with this column as its target)
// * it has only a single target, which matches the pattern for pre-3.0 indexes
// i.e. keys/values/entries/full, with exactly 1 argument that matches the
// column name OR a simple column name (for indexes on non-collection columns)
// n.b. it's a guess that using a pre-compiled regex and checking the group is
// cheaper than compiling a new regex for each column, but as this isn't on
// any hot path this hasn't been verified yet.
IndexMetadata matchedIndex = null;
for (IndexMetadata index : cfMetaData.getIndexes())
{
IndexMetadata index = indexes.iterator().next();
if (index.columns.size() == 1)
String target = index.options.get(IndexTarget.TARGET_OPTION_NAME);
Matcher m = CassandraIndex.TARGET_REGEX.matcher(target);
if (target.equals(column.name.toString()) ||
(m.matches() && m.group(2).equals(column.name.toString())))
{
cd.setIndex_type(org.apache.cassandra.thrift.IndexType.valueOf(index.indexType.name()));
cd.setIndex_name(index.name);
cd.setIndex_options(index.options == null || index.options.isEmpty()
? null
: Maps.newHashMap(index.options));
// we already found an index for this column, we've no option but to
// ignore both of them (and any others we've yet to find)
if (matchedIndex != null)
return cd;
matchedIndex = index;
}
}
if (matchedIndex != null)
{
cd.setIndex_type(org.apache.cassandra.thrift.IndexType.valueOf(matchedIndex.kind.name()));
cd.setIndex_name(matchedIndex.name);
Map<String, String> filteredOptions = Maps.filterKeys(matchedIndex.options,
s -> !IndexTarget.TARGET_OPTION_NAME.equals(s));
cd.setIndex_options(filteredOptions.isEmpty()
? null
: Maps.newHashMap(matchedIndex.options));
}
return cd;
}

View File

@ -299,9 +299,10 @@ public class SchemaLoader
cfm.indexes(
cfm.getIndexes()
.with(IndexMetadata.singleColumnIndex(indexedColumn,
.with(IndexMetadata.singleTargetIndex(cfm,
new IndexTarget(indexedColumn.name, IndexTarget.Type.VALUES),
"indexe1",
IndexMetadata.IndexType.CUSTOM,
IndexMetadata.Kind.CUSTOM,
indexOptions)));
return cfm;
}
@ -410,9 +411,11 @@ public class SchemaLoader
if (withIndex)
cfm.indexes(
cfm.getIndexes()
.with(IndexMetadata.singleColumnIndex(cfm.getColumnDefinition(new ColumnIdentifier("birthdate", true)),
.with(IndexMetadata.singleTargetIndex(cfm,
new IndexTarget(new ColumnIdentifier("birthdate", true),
IndexTarget.Type.VALUES),
"birthdate_key_index",
IndexMetadata.IndexType.COMPOSITES,
IndexMetadata.Kind.COMPOSITES,
Collections.EMPTY_MAP)));
return cfm.compression(getCompressionParameters());
@ -430,9 +433,11 @@ public class SchemaLoader
if (withIndex)
cfm.indexes(
cfm.getIndexes()
.with(IndexMetadata.singleColumnIndex(cfm.getColumnDefinition(new ColumnIdentifier("birthdate", true)),
.with(IndexMetadata.singleTargetIndex(cfm,
new IndexTarget(new ColumnIdentifier("birthdate", true),
IndexTarget.Type.VALUES),
"birthdate_composite_index",
IndexMetadata.IndexType.KEYS,
IndexMetadata.Kind.KEYS,
Collections.EMPTY_MAP)));

View File

@ -601,8 +601,10 @@ public class FrozenCollectionsTest extends CQLTester
// for now, we don't support indexing values or keys of collections in the primary key
assertInvalidIndexCreationWithMessage("CREATE INDEX ON %s (full(a))", "Cannot create secondary index on partition key column");
assertInvalidIndexCreationWithMessage("CREATE INDEX ON %s (keys(a))", "Cannot create index on keys of frozen<map> column");
assertInvalidIndexCreationWithMessage("CREATE INDEX ON %s (keys(b))", "Cannot create index on keys of frozen<map> column");
assertInvalidIndexCreationWithMessage("CREATE INDEX ON %s (keys(a))", "Cannot create keys() index on frozen column a. " +
"Frozen collections only support full() indexes");
assertInvalidIndexCreationWithMessage("CREATE INDEX ON %s (keys(b))", "Cannot create keys() index on frozen column b. " +
"Frozen collections only support full() indexes");
createTable("CREATE TABLE %s (a int, b frozen<list<int>>, c frozen<set<int>>, d frozen<map<int, text>>, PRIMARY KEY (a, b))");

View File

@ -27,15 +27,19 @@ import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.statements.IndexTarget;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.index.SecondaryIndexManager;
import org.apache.cassandra.index.StubIndex;
import org.apache.cassandra.schema.IndexMetadata;
import static org.apache.cassandra.Util.throwAssert;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@ -209,7 +213,7 @@ public class SecondaryIndexTest extends CQLTester
assertInvalidThrow(SyntaxException.class, String.format("CREATE TABLE %s (key varchar PRIMARY KEY, password varchar, gender varchar) WITH compression_parameters:sstable_compressor = 'DeflateCompressor'", tableName));
assertInvalidThrow(ConfigurationException.class, String.format("CREATE TABLE %s (key varchar PRIMARY KEY, password varchar, gender varchar) WITH compression = { 'sstable_compressor': 'DeflateCompressor' }",
tableName));
tableName));
}
/**
@ -462,6 +466,67 @@ public class SecondaryIndexTest extends CQLTester
assertRows(execute("select count(*) from %s where app_name='foo' and account='bar' and last_access > 4 allow filtering"), row(1L));
}
@Test
public void testSyntaxVariationsForIndexOnCollectionsValue() throws Throwable
{
createTable("CREATE TABLE %s (k int, m map<int, int>, l list<int>, s set<int>, PRIMARY KEY (k))");
createAndDropCollectionValuesIndex("m");
createAndDropCollectionValuesIndex("l");
createAndDropCollectionValuesIndex("s");
}
private void createAndDropCollectionValuesIndex(String columnName) throws Throwable
{
String indexName = columnName + "_idx";
SecondaryIndexManager indexManager = getCurrentColumnFamilyStore().indexManager;
createIndex(String.format("CREATE INDEX %s on %%s(%s)", indexName, columnName));
IndexMetadata indexDef = indexManager.getIndexByName(indexName).getIndexMetadata();
assertEquals(String.format("values(%s)", columnName), indexDef.options.get(IndexTarget.TARGET_OPTION_NAME));
dropIndex(String.format("DROP INDEX %s.%s", KEYSPACE, indexName));
assertFalse(indexManager.hasIndexes());
createIndex(String.format("CREATE INDEX %s on %%s(values(%s))", indexName, columnName));
assertEquals(indexDef, indexManager.getIndexByName(indexName).getIndexMetadata());
dropIndex(String.format("DROP INDEX %s.%s", KEYSPACE, indexName));
}
@Test
public void testCreateIndexWithQuotedColumnNames() throws Throwable
{
createTable("CREATE TABLE %s (" +
" k int," +
" v int, " +
" lower_case_map map<int, int>," +
" \"MixedCaseMap\" map<int, int>," +
" lower_case_frozen_list frozen<list<int>>," +
" \"UPPER_CASE_FROZEN_LIST\" frozen<list<int>>," +
" \"set name with spaces\" set<int>," +
" \"column_name_with\"\"escaped quote\" int," +
" PRIMARY KEY (k))");
createAndDropIndexWithQuotedColumnIdentifier("\"v\"");
createAndDropIndexWithQuotedColumnIdentifier("keys(\"lower_case_map\")");
createAndDropIndexWithQuotedColumnIdentifier("keys(\"MixedCaseMap\")");
createAndDropIndexWithQuotedColumnIdentifier("full(\"lower_case_frozen_list\")");
createAndDropIndexWithQuotedColumnIdentifier("full(\"UPPER_CASE_FROZEN_LIST\")");
createAndDropIndexWithQuotedColumnIdentifier("values(\"set name with spaces\")");
createAndDropIndexWithQuotedColumnIdentifier("\"column_name_with\"\"escaped quote\"");
}
private void createAndDropIndexWithQuotedColumnIdentifier(String target) throws Throwable
{
String indexName = "test_mixed_case_idx";
createIndex(String.format("CREATE INDEX %s ON %%s(%s)", indexName, target));
SecondaryIndexManager indexManager = getCurrentColumnFamilyStore().indexManager;
IndexMetadata indexDef = indexManager.getIndexByName(indexName).getIndexMetadata();
dropIndex(String.format("DROP INDEX %s.%s", KEYSPACE, indexName));
// verify we can re-create the index using the target string
createIndex(String.format("CREATE INDEX %s ON %%s(%s)",
indexName, indexDef.options.get(IndexTarget.TARGET_OPTION_NAME)));
assertEquals(indexDef, indexManager.getIndexByName(indexName).getIndexMetadata());
dropIndex(String.format("DROP INDEX %s.%s", KEYSPACE, indexName));
}
/**
* Test for CASSANDRA-5732, Can not query secondary index
* migrated from cql_tests.py:TestCQL.bug_5732_test(),

View File

@ -114,9 +114,7 @@ public class CleanupTest
assertEquals(LOOPS, Util.getAll(Util.cmd(cfs).build()).size());
ColumnDefinition cdef = cfs.metadata.getColumnDefinition(COLUMN);
String indexName = cfs.metadata.getIndexes()
.get(cdef)
.iterator().next().name;
String indexName = "birthdate_key_index";
long start = System.nanoTime();
while (!cfs.getBuiltIndexes().contains(indexName) && System.nanoTime() - start < TimeUnit.SECONDS.toNanos(10))
Thread.sleep(10);

View File

@ -34,6 +34,7 @@ import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.config.Config.DiskFailurePolicy;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.statements.IndexTarget;
import org.apache.cassandra.db.Directories.DataDirectory;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.index.internal.CassandraIndex;
@ -168,9 +169,10 @@ public class DirectoriesTest
.addClusteringColumn("col", UTF8Type.instance)
.build();
ColumnDefinition col = PARENT_CFM.getColumnDefinition(ByteBufferUtil.bytes("col"));
IndexMetadata indexDef = IndexMetadata.singleColumnIndex(col,
IndexMetadata indexDef = IndexMetadata.singleTargetIndex(PARENT_CFM,
new IndexTarget(col.name, IndexTarget.Type.VALUES),
"idx",
IndexMetadata.IndexType.KEYS,
IndexMetadata.Kind.KEYS,
Collections.emptyMap());
PARENT_CFM.indexes(PARENT_CFM.getIndexes().with(indexDef));
CFMetaData INDEX_CFM = CassandraIndex.indexCfsMetadata(PARENT_CFM, indexDef);

View File

@ -464,9 +464,10 @@ public class RangeTombstoneTest
cfs.disableAutoCompaction();
ColumnDefinition cd = cfs.metadata.getColumnDefinition(indexedColumnName).copy();
IndexMetadata indexDef = IndexMetadata.singleColumnIndex(cd,
IndexMetadata indexDef = IndexMetadata.singleTargetIndex(cfs.metadata,
new IndexTarget(cd.name, IndexTarget.Type.VALUES),
"test_index",
IndexMetadata.IndexType.CUSTOM,
IndexMetadata.Kind.CUSTOM,
ImmutableMap.of(IndexTarget.CUSTOM_INDEX_OPTION_NAME,
StubIndex.class.getName()));
@ -560,9 +561,10 @@ public class RangeTombstoneTest
cfs.disableAutoCompaction();
ColumnDefinition cd = cfs.metadata.getColumnDefinition(indexedColumnName).copy();
IndexMetadata indexDef = IndexMetadata.singleColumnIndex(cd,
IndexMetadata indexDef = IndexMetadata.singleTargetIndex(cfs.metadata,
new IndexTarget(cd.name,IndexTarget.Type.VALUES),
"test_index",
IndexMetadata.IndexType.CUSTOM,
IndexMetadata.Kind.CUSTOM,
ImmutableMap.of(IndexTarget.CUSTOM_INDEX_OPTION_NAME,
StubIndex.class.getName()));
@ -574,11 +576,7 @@ public class RangeTombstoneTest
if (rebuild != null)
rebuild.get();
StubIndex index = (StubIndex)cfs.indexManager.listIndexes()
.stream()
.filter(i -> "test_index".equals(i.getIndexName()))
.findFirst()
.orElseThrow(() -> new RuntimeException(new AssertionError("Index not found")));
StubIndex index = (StubIndex)cfs.indexManager.getIndexByName("test_index");
index.reset();
UpdateBuilder.create(cfs.metadata, key).withTimestamp(0).newRow(1).add("val", 1).applyUnsafe();

View File

@ -29,12 +29,11 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.index.SecondaryIndexManager;
import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.Util;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.Operator;
import org.apache.cassandra.cql3.statements.IndexTarget;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.partitions.*;
import org.apache.cassandra.db.rows.Row;
@ -44,6 +43,8 @@ import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.schema.KeyspaceParams;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import static org.apache.cassandra.Util.throwAssert;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@ -63,8 +64,8 @@ public class SecondaryIndexTest
SchemaLoader.prepareServer();
SchemaLoader.createKeyspace(KEYSPACE1,
KeyspaceParams.simple(1),
SchemaLoader.compositeIndexCFMD(KEYSPACE1, WITH_COMPOSITE_INDEX, true) .gcGraceSeconds(0),
SchemaLoader.compositeIndexCFMD(KEYSPACE1, COMPOSITE_INDEX_TO_BE_ADDED, false) .gcGraceSeconds(0),
SchemaLoader.compositeIndexCFMD(KEYSPACE1, WITH_COMPOSITE_INDEX, true).gcGraceSeconds(0),
SchemaLoader.compositeIndexCFMD(KEYSPACE1, COMPOSITE_INDEX_TO_BE_ADDED, false).gcGraceSeconds(0),
SchemaLoader.keysIndexCFMD(KEYSPACE1, WITH_KEYS_INDEX, true).gcGraceSeconds(0));
}
@ -435,10 +436,12 @@ public class SecondaryIndexTest
// create a row and update the birthdate value, test that the index query fetches the new version
new RowUpdateBuilder(cfs.metadata, 0, "k1").clustering("c").add("birthdate", 1L).build().applyUnsafe();
String indexName = "birthdate_index";
ColumnDefinition old = cfs.metadata.getColumnDefinition(ByteBufferUtil.bytes("birthdate"));
IndexMetadata indexDef = IndexMetadata.singleColumnIndex(old,
"birthdate_index",
IndexMetadata.IndexType.COMPOSITES,
IndexMetadata indexDef = IndexMetadata.singleTargetIndex(cfs.metadata,
new IndexTarget(old.name, IndexTarget.Type.VALUES),
indexName,
IndexMetadata.Kind.COMPOSITES,
Collections.EMPTY_MAP);
cfs.metadata.indexes(cfs.metadata.getIndexes().with(indexDef));
Future<?> future = cfs.indexManager.addIndex(indexDef);
@ -446,15 +449,11 @@ public class SecondaryIndexTest
// we had a bug (CASSANDRA-2244) where index would get created but not flushed -- check for that
// the way we find the index cfs is a bit convoluted at the moment
ColumnDefinition cDef = cfs.metadata.getColumnDefinition(ByteBufferUtil.bytes("birthdate"));
String indexName = getIndexNameForColumn(cfs, cDef);
assertNotNull(indexName);
boolean flushed = false;
for (ColumnFamilyStore indexCfs : cfs.indexManager.getAllIndexColumnFamilyStores())
{
if (SecondaryIndexManager.getIndexName(indexCfs).equals(indexName))
flushed = indexCfs.getLiveSSTables().size() > 0;
}
ColumnFamilyStore indexCfs = cfs.indexManager.getIndex(indexDef)
.getBackingTable()
.orElseThrow(throwAssert("Index not found"));
flushed = !indexCfs.getLiveSSTables().isEmpty();
assertTrue(flushed);
assertIndexedOne(cfs, ByteBufferUtil.bytes("birthdate"), 1L);
@ -469,18 +468,6 @@ public class SecondaryIndexTest
assertIndexedOne(cfs, ByteBufferUtil.bytes("birthdate"), 1L);
}
private String getIndexNameForColumn(ColumnFamilyStore cfs, ColumnDefinition column)
{
// this is mega-ugly because there is a mismatch between the name of an index
// stored in schema metadata & the name used to refer to that index in other
// places (such as system.IndexInfo). Hopefully this is temporary and
// Index.getIndexName() can be made equalivalent to Index.getIndexMetadata().name
// (ideally even removing the former completely)
Collection<IndexMetadata> indexes = cfs.metadata.getIndexes().get(column);
assertEquals(1, indexes.size());
return cfs.indexManager.getIndex(indexes.iterator().next()).getIndexName();
}
@Test
public void testKeysSearcherSimple() throws Exception
{

View File

@ -23,7 +23,6 @@ import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.Operator;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.RowFilter;
@ -70,6 +69,11 @@ public class StubIndex implements Index
return false;
}
public boolean dependsOn(ColumnDefinition column)
{
return false;
}
public boolean supportsExpression(ColumnDefinition column, Operator operator)
{
return operator == Operator.EQ;

View File

@ -20,27 +20,27 @@ import org.apache.cassandra.db.compaction.CompactionManager;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.lifecycle.SSTableSet;
import org.apache.cassandra.db.lifecycle.View;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.db.partitions.PartitionIterator;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.index.IndexRegistry;
import org.apache.cassandra.index.SecondaryIndexBuilder;
import org.apache.cassandra.index.internal.composites.CompositesSearcher;
import org.apache.cassandra.index.internal.keys.KeysSearcher;
import org.apache.cassandra.index.transactions.IndexTransaction;
import org.apache.cassandra.index.transactions.UpdateTransaction;
import org.apache.cassandra.io.sstable.ReducingKeyIterator;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.concurrent.OpOrder;
import org.apache.cassandra.utils.concurrent.Refs;
import static org.apache.cassandra.index.internal.CassandraIndex.getFunctions;
import static org.apache.cassandra.index.internal.CassandraIndex.indexCfsMetadata;
import static org.apache.cassandra.index.internal.CassandraIndex.parseTarget;
/**
* Clone of KeysIndex used in CassandraIndexTest#testCustomIndexWithCFS to verify
* behaviour of flushing CFS backed CUSTOM indexes
@ -145,14 +145,14 @@ public class CustomCassandraIndex implements Index
private void setMetadata(IndexMetadata indexDef)
{
metadata = indexDef;
functions = getFunctions(baseCfs.metadata, indexDef);
Pair<ColumnDefinition, IndexTarget.Type> target = parseTarget(baseCfs.metadata, indexDef);
functions = getFunctions(indexDef, target);
CFMetaData cfm = indexCfsMetadata(baseCfs.metadata, indexDef);
indexCfs = ColumnFamilyStore.createColumnFamilyStore(baseCfs.keyspace,
cfm.cfName,
cfm,
baseCfs.getTracker().loadsstables);
assert indexDef.columns.size() == 1 : "Build in indexes on multiple target columns are not supported";
indexedColumn = indexDef.indexedColumn(baseCfs.metadata);
indexedColumn = target.left;
}
public Callable<?> getTruncateTask(final long truncatedAt)
@ -174,6 +174,11 @@ public class CustomCassandraIndex implements Index
return isPrimaryKeyIndex() || columns.contains(indexedColumn);
}
public boolean dependsOn(ColumnDefinition column)
{
return column.equals(indexedColumn);
}
public boolean supportsExpression(ColumnDefinition column, Operator operator)
{
return indexedColumn.name.equals(column.name)
@ -668,76 +673,4 @@ public class CustomCassandraIndex implements Index
.map(SSTableReader::toString)
.collect(Collectors.joining(", "));
}
/**
* Construct the CFMetadata for an index table, the clustering columns in the index table
* vary dependent on the kind of the indexed value.
* @param baseCfsMetadata
* @param indexMetadata
* @return
*/
public static final CFMetaData indexCfsMetadata(CFMetaData baseCfsMetadata, IndexMetadata indexMetadata)
{
CassandraIndexFunctions utils = getFunctions(baseCfsMetadata, indexMetadata);
ColumnDefinition indexedColumn = indexMetadata.indexedColumn(baseCfsMetadata);
AbstractType<?> indexedValueType = utils.getIndexedValueType(indexedColumn);
CFMetaData.Builder builder = CFMetaData.Builder.create(baseCfsMetadata.ksName,
baseCfsMetadata.indexColumnFamilyName(indexMetadata))
.withId(baseCfsMetadata.cfId)
.withPartitioner(new LocalPartitioner(indexedValueType))
.addPartitionKey(indexedColumn.name, indexedColumn.type);
builder.addClusteringColumn("partition_key", baseCfsMetadata.partitioner.partitionOrdering());
builder = utils.addIndexClusteringColumns(builder, baseCfsMetadata, indexedColumn);
return builder.build().reloadIndexMetadataProperties(baseCfsMetadata);
}
/**
* Factory method for new CassandraIndex instances
* @param baseCfs
* @param indexMetadata
* @return
*/
public static final CassandraIndex newIndex(ColumnFamilyStore baseCfs, IndexMetadata indexMetadata)
{
return getFunctions(baseCfs.metadata, indexMetadata).newIndexInstance(baseCfs, indexMetadata);
}
private static CassandraIndexFunctions getFunctions(CFMetaData baseCfMetadata, IndexMetadata indexDef)
{
if (indexDef.isKeys())
return CassandraIndexFunctions.KEYS_INDEX_FUNCTIONS;
ColumnDefinition indexedColumn = indexDef.indexedColumn(baseCfMetadata);
if (indexedColumn.type.isCollection() && indexedColumn.type.isMultiCell())
{
switch (((CollectionType)indexedColumn.type).kind)
{
case LIST:
return CassandraIndexFunctions.COLLECTION_VALUE_INDEX_FUNCTIONS;
case SET:
return CassandraIndexFunctions.COLLECTION_KEY_INDEX_FUNCTIONS;
case MAP:
if (indexDef.options.containsKey(IndexTarget.INDEX_KEYS_OPTION_NAME))
return CassandraIndexFunctions.COLLECTION_KEY_INDEX_FUNCTIONS;
else if (indexDef.options.containsKey(IndexTarget.INDEX_ENTRIES_OPTION_NAME))
return CassandraIndexFunctions.COLLECTION_ENTRY_INDEX_FUNCTIONS;
else
return CassandraIndexFunctions.COLLECTION_VALUE_INDEX_FUNCTIONS;
}
}
switch (indexedColumn.kind)
{
case CLUSTERING:
return CassandraIndexFunctions.CLUSTERING_COLUMN_INDEX_FUNCTIONS;
case REGULAR:
return CassandraIndexFunctions.REGULAR_COLUMN_INDEX_FUNCTIONS;
case PARTITION_KEY:
return CassandraIndexFunctions.PARTITION_KEY_INDEX_FUNCTIONS;
//case COMPACT_VALUE:
// return new CompositesIndexOnCompactValue();
}
throw new AssertionError();
}
}

View File

@ -44,14 +44,13 @@ import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.db.marshal.BytesType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.locator.OldNetworkTopologyStrategy;
import org.apache.cassandra.service.MigrationManager;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import static org.apache.cassandra.Util.throwAssert;
import static org.apache.cassandra.cql3.CQLTester.assertRows;
import static org.apache.cassandra.cql3.CQLTester.row;
import static org.junit.Assert.assertEquals;
@ -501,6 +500,7 @@ public class DefsTest
// persist keyspace definition in the system keyspace
SchemaKeyspace.makeCreateKeyspaceMutation(Schema.instance.getKSMetaData(KEYSPACE6), FBUtilities.timestampMicros()).applyUnsafe();
ColumnFamilyStore cfs = Keyspace.open(KEYSPACE6).getColumnFamilyStore(TABLE1i);
String indexName = "birthdate_key_index";
// insert some data. save the sstable descriptor so we can make sure it's marked for delete after the drop
QueryProcessor.executeInternal(String.format(
@ -510,37 +510,17 @@ public class DefsTest
"key0", "col0", 1L, 1L);
cfs.forceBlockingFlush();
ColumnDefinition indexedColumn = cfs.metadata.getColumnDefinition(ByteBufferUtil.bytes("birthdate"));
IndexMetadata index = cfs.metadata.getIndexes()
.get(indexedColumn)
.iterator()
.next();
ColumnFamilyStore indexCfs = cfs.indexManager.listIndexes()
.stream()
.filter(i -> i.getIndexMetadata().equals(index))
.map(Index::getBackingTable)
.findFirst()
.orElseThrow(() -> new AssertionError("Index not found"))
.orElseThrow(() -> new AssertionError("Index has no backing table"));
ColumnFamilyStore indexCfs = cfs.indexManager.getIndexByName(indexName)
.getBackingTable()
.orElseThrow(throwAssert("Cannot access index cfs"));
Descriptor desc = indexCfs.getLiveSSTables().iterator().next().descriptor;
// drop the index
CFMetaData meta = cfs.metadata.copy();
// We currently have a mismatch between IndexMetadata.name (which is simply the name
// of the index) and what gets returned from SecondaryIndex#getIndexName() (usually, this
// defaults to <tablename>.<indexname>.
// IndexMetadata takes its lead from the prior implementation of ColumnDefinition.name
// which did not include the table name.
// This mismatch causes some other, long standing inconsistencies:
// nodetool rebuild_index <ks> <tbl> <idx> - <idx> must be qualified, i.e. include the redundant table name
// without it, the rebuild silently fails
// system.IndexInfo (which is also exposed over JMX as CF.BuildIndexes) uses the form <tbl>.<idx>
// cqlsh> describe index [<ks>.]<idx> - here <idx> must not be qualified by the table name.
//
// This should get resolved as part of #9459 by better separating the index name from the
// name of it's underlying CFS (if it as one), as the comment in CFMetaData#indexColumnFamilyName promises
// Then we will be able to just use the value of SI#getIndexName() when removing an index from CFMetaData
IndexMetadata existing = meta.getIndexes().iterator().next();
IndexMetadata existing = cfs.metadata.getIndexes()
.get(indexName)
.orElseThrow(throwAssert("Index not found"));
meta.indexes(meta.getIndexes().without(existing.name));
MigrationManager.announceColumnFamilyUpdate(meta, false);

View File

@ -34,6 +34,7 @@ import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.functions.*;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.index.internal.CassandraIndex;
import org.apache.cassandra.thrift.ThriftConversion;
import static java.lang.String.format;
@ -490,7 +491,7 @@ public class LegacySchemaMigratorTest
{
IndexMetadata i = index.get();
adder.add("index_name", i.name);
adder.add("index_type", i.indexType.toString());
adder.add("index_type", i.kind.toString());
adder.add("index_options", json(i.options));
}
else
@ -504,11 +505,14 @@ public class LegacySchemaMigratorTest
}
private static Optional<IndexMetadata> findIndexForColumn(Indexes indexes,
CFMetaData table,
ColumnDefinition column)
CFMetaData table,
ColumnDefinition column)
{
// makes the assumptions that the string option denoting the
// index targets can be parsed by CassandraIndex.parseTarget
// which should be true for any pre-3.0 index
for (IndexMetadata index : indexes)
if (index.indexedColumn(table).equals(column))
if (CassandraIndex.parseTarget(table, index).left.equals(column))
return Optional.of(index);
return Optional.empty();