mirror of https://github.com/apache/cassandra
Allow value/element indexing on frozen collections in SAI
patch by Sunil Ramchandra Pawar; reviewed by Caleb Rackliffe, David Capwell, and Andres de la Peña for CASSANDRA-18492
This commit is contained in:
parent
b50152af22
commit
8d325d50ed
|
|
@ -1,4 +1,5 @@
|
|||
5.1
|
||||
* Allow value/element indexing on frozen collections in SAI (CASSANDRA-18492)
|
||||
* Add tool to offline dump cluster metadata and the log (CASSANDRA-21129)
|
||||
* Send client warnings when writing to a large partition (CASSANDRA-17258)
|
||||
* Harden the possible range of values for max dictionary size and max total sample size for dictionary training (CASSANDRA-21194)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ import static org.apache.cassandra.cql3.statements.RequestValidations.invalidReq
|
|||
*/
|
||||
public final class Relation
|
||||
{
|
||||
public static final String FROZEN_MAP_ENTRY_PREDICATES_NOT_SUPPORTED = "Map-entry predicates on frozen map column %s are not supported";
|
||||
|
||||
/**
|
||||
* The raw columns'expression.
|
||||
*/
|
||||
|
|
@ -204,7 +206,10 @@ public final class Relation
|
|||
AbstractType<?> baseType = column.type.unwrap();
|
||||
checkFalse(baseType instanceof ListType, "Indexes on list entries (%s[index] = value) are not supported.", column.name);
|
||||
checkTrue(baseType instanceof MapType, "Column %s cannot be used as a map", column.name);
|
||||
checkTrue(baseType.isMultiCell(), "Map-entry predicates on frozen map column %s are not supported", column.name);
|
||||
|
||||
if (column.isClusteringColumn() && baseType.isCollection() && !column.type.isMultiCell())
|
||||
throw invalidRequest(FROZEN_MAP_ENTRY_PREDICATES_NOT_SUPPORTED, column.name);
|
||||
|
||||
columnsExpression.collectMarkerSpecification(boundNames);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import com.google.common.collect.RangeSet;
|
|||
|
||||
import org.apache.cassandra.cql3.Operator;
|
||||
import org.apache.cassandra.cql3.QueryOptions;
|
||||
import org.apache.cassandra.cql3.Relation;
|
||||
import org.apache.cassandra.cql3.functions.Function;
|
||||
import org.apache.cassandra.db.filter.IndexHints;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
|
|
@ -126,9 +127,21 @@ public final class MergedRestriction implements SingleRestriction
|
|||
checkOperator(other);
|
||||
|
||||
if (restriction.isContains() != other.isContains())
|
||||
{
|
||||
SimpleRestriction mapEntryRestriction = restriction.isContains() ? restriction : other;
|
||||
if (mapEntryRestriction.isMapElementExpression())
|
||||
{
|
||||
ColumnMetadata column = mapEntryRestriction.firstColumn();
|
||||
if (column.type.isFrozenCollection())
|
||||
{
|
||||
throw invalidRequest(Relation.FROZEN_MAP_ENTRY_PREDICATES_NOT_SUPPORTED, column.name);
|
||||
}
|
||||
}
|
||||
|
||||
throw invalidRequest("Collection column %s can only be restricted by CONTAINS, CONTAINS KEY, NOT_CONTAINS, NOT_CONTAINS_KEY" +
|
||||
" or map-entry equality if it already restricted by one of those",
|
||||
restriction.firstColumn().name);
|
||||
}
|
||||
|
||||
if (restriction.isSlice() && other.isSlice())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import com.google.common.collect.RangeSet;
|
|||
import org.apache.cassandra.cql3.ColumnsExpression;
|
||||
import org.apache.cassandra.cql3.Operator;
|
||||
import org.apache.cassandra.cql3.QueryOptions;
|
||||
import org.apache.cassandra.cql3.Relation;
|
||||
import org.apache.cassandra.cql3.functions.Function;
|
||||
import org.apache.cassandra.cql3.terms.Term;
|
||||
import org.apache.cassandra.cql3.terms.Terms;
|
||||
|
|
@ -157,6 +158,14 @@ public final class SimpleRestriction implements SingleRestriction
|
|||
|| columnsExpression.isMapElementExpression();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this restriction is a map element expression (e.g., map['key'] = value).
|
||||
*/
|
||||
public boolean isMapElementExpression()
|
||||
{
|
||||
return columnsExpression.isMapElementExpression();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsFilteringOrIndexing()
|
||||
{
|
||||
|
|
@ -218,6 +227,18 @@ public final class SimpleRestriction implements SingleRestriction
|
|||
if (isOnToken())
|
||||
return false;
|
||||
|
||||
// For map element expressions, check if the index explicitly supports them.
|
||||
if (columnsExpression.isMapElementExpression())
|
||||
{
|
||||
// If the index directly supports map element expressions, return true
|
||||
if (index.supportsMapElementExpression())
|
||||
return true;
|
||||
|
||||
// Supports post-filtering only and require ALLOW FILTERING
|
||||
if (index.supportsFilteringOnMapElementExpression())
|
||||
return false;
|
||||
}
|
||||
|
||||
for (ColumnMetadata column : columns())
|
||||
{
|
||||
if (index.supportsExpression(column, operator))
|
||||
|
|
@ -415,13 +436,28 @@ public final class SimpleRestriction implements SingleRestriction
|
|||
// TODO only map elements supported for now
|
||||
if (columnsExpression.isMapElementExpression())
|
||||
{
|
||||
// For frozen maps, check if any index on the column can support map entry predicates
|
||||
// either directly or via filtering. If not, throw an error.
|
||||
if (column.type.isFrozenCollection())
|
||||
{
|
||||
for (Index index : indexRegistry.listIndexes())
|
||||
{
|
||||
if (index.dependsOn(column)
|
||||
&& !index.supportsMapElementExpression()
|
||||
&& !index.supportsFilteringOnMapElementExpression())
|
||||
{
|
||||
throw invalidRequest(Relation.FROZEN_MAP_ENTRY_PREDICATES_NOT_SUPPORTED, column.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ByteBuffer key = columnsExpression.element(options);
|
||||
if (key == null)
|
||||
throw invalidRequest("Invalid null map key for column %s", firstColumn().name.toCQLString());
|
||||
throw invalidRequest("Invalid null map key for column %s", column.name.toCQLString());
|
||||
if (key == ByteBufferUtil.UNSET_BYTE_BUFFER)
|
||||
throw invalidRequest("Invalid unset map key for column %s", firstColumn().name.toCQLString());
|
||||
throw invalidRequest("Invalid unset map key for column %s", column.name.toCQLString());
|
||||
List<ByteBuffer> values = bindAndGet(options);
|
||||
filter.addMapEquality(firstColumn(), key, operator, values.get(0));
|
||||
filter.addMapEquality(column, key, operator, values.get(0));
|
||||
}
|
||||
break;
|
||||
default: throw new UnsupportedOperationException();
|
||||
|
|
|
|||
|
|
@ -81,10 +81,13 @@ public final class CreateIndexStatement extends AlterSchemaStatement
|
|||
public static final String ONLY_PARTITION_KEY = "Cannot create secondary index on the only partition key column %s";
|
||||
public static final String CREATE_ON_FROZEN_COLUMN = "Cannot create %s() index on frozen column %s. Frozen collections are immutable and must be fully " +
|
||||
"indexed by using the 'full(%s)' modifier";
|
||||
public static final String FULL_ON_FROZEN_COLLECTIONS = "full() indexes can only be created on frozen collections";
|
||||
public static final String FULL_ON_FROZEN_COLLECTIONS = "full() non-SAI indexes can only be created on frozen collections";
|
||||
public static final String NON_COLLECTION_SIMPLE_INDEX = "Cannot create %s() index on %s. Non-collection columns only support simple indexes";
|
||||
public static final String CREATE_WITH_NON_MAP_TYPE = "Cannot create index on %s of column %s with non-map type";
|
||||
public static final String CREATE_ON_NON_FROZEN_UDT = "Cannot create index on non-frozen UDT column %s";
|
||||
public static final String ENTRIES_INDEX_ON_FROZEN_MAP_CLUSTERING_KEY_NOT_SUPPORTED = "Cannot create ENTRIES index on frozen map clustering column '%s'. " +
|
||||
"Map entry predicates (column[key] = value) are not supported on clustering columns. " +
|
||||
"Use FULL, KEYS, or VALUES index instead.";
|
||||
public static final String INDEX_ALREADY_EXISTS = "Index '%s' already exists";
|
||||
public static final String INDEX_DUPLICATE_OF_EXISTING = "Index %s is a duplicate of existing index %s";
|
||||
public static final String KEYSPACE_DOES_NOT_MATCH_TABLE = "Keyspace name '%s' doesn't match table name '%s'";
|
||||
|
|
@ -200,7 +203,7 @@ public final class CreateIndexStatement extends AlterSchemaStatement
|
|||
|
||||
IndexMetadata.Kind kind = attrs.isCustom ? IndexMetadata.Kind.CUSTOM : IndexMetadata.Kind.COMPOSITES;
|
||||
|
||||
indexTargets.forEach(t -> validateIndexTarget(table, kind, t));
|
||||
indexTargets.forEach(t -> validateIndexTarget(table, kind, t, attrs));
|
||||
|
||||
String name = null == indexName ? generateIndexName(keyspace, indexTargets) : indexName;
|
||||
|
||||
|
|
@ -244,7 +247,7 @@ public final class CreateIndexStatement extends AlterSchemaStatement
|
|||
throw ire(TOO_LONG_CUSTOM_INDEX_TARGET, name, SchemaConstants.NAME_LENGTH);
|
||||
}
|
||||
|
||||
private void validateIndexTarget(TableMetadata table, IndexMetadata.Kind kind, IndexTarget target)
|
||||
private void validateIndexTarget(TableMetadata table, IndexMetadata.Kind kind, IndexTarget target, IndexAttributes attrs)
|
||||
{
|
||||
ColumnMetadata column = table.getColumn(target.column);
|
||||
|
||||
|
|
@ -253,6 +256,8 @@ public final class CreateIndexStatement extends AlterSchemaStatement
|
|||
|
||||
AbstractType<?> baseType = column.type.unwrap();
|
||||
|
||||
boolean isNonSAIIndex = !isSAIIndex(attrs);
|
||||
|
||||
// TODO: this check needs to be removed with CASSANDRA-20235
|
||||
if ((kind == IndexMetadata.Kind.CUSTOM))
|
||||
validateCustomIndexColumnName(target.column.toString());
|
||||
|
|
@ -283,22 +288,41 @@ public final class CreateIndexStatement extends AlterSchemaStatement
|
|||
if (column.isPartitionKey() && table.partitionKeyColumns().size() == 1)
|
||||
throw ire(ONLY_PARTITION_KEY, column);
|
||||
|
||||
if (baseType.isFrozenCollection() && target.type != Type.FULL)
|
||||
throw ire(CREATE_ON_FROZEN_COLUMN, target.type, column, column.name.toCQLString());
|
||||
|
||||
if (!baseType.isFrozenCollection() && target.type == Type.FULL)
|
||||
if (target.type == Type.FULL && isNonSAIIndex && (!baseType.isCollection() || column.type.isMultiCell()))
|
||||
throw ire(FULL_ON_FROZEN_COLLECTIONS);
|
||||
|
||||
if (!baseType.isCollection() && target.type != Type.SIMPLE)
|
||||
throw ire(NON_COLLECTION_SIMPLE_INDEX, target.type, column);
|
||||
|
||||
if (!(baseType instanceof MapType && baseType.isMultiCell()) && (target.type == Type.KEYS || target.type == Type.KEYS_AND_VALUES))
|
||||
// Frozen collections are only supported with SAI indexes.
|
||||
if (isNonSAIIndex && baseType.isCollection() && !column.type.isMultiCell())
|
||||
{
|
||||
if (target.type == Type.VALUES || target.type == Type.KEYS || target.type == Type.KEYS_AND_VALUES)
|
||||
{
|
||||
throw ire(CREATE_ON_FROZEN_COLUMN, target.type.toString(), column.name, column.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(baseType instanceof MapType) && (target.type == Type.KEYS || target.type == Type.KEYS_AND_VALUES ))
|
||||
throw ire(CREATE_WITH_NON_MAP_TYPE, target.type, column);
|
||||
|
||||
// Can't query map[key]=value on clustering key columns, so ENTRIES index would be not queryable.
|
||||
if (column.isClusteringColumn() && baseType instanceof MapType && !column.type.isMultiCell()
|
||||
&& target.type == Type.KEYS_AND_VALUES)
|
||||
throw ire(ENTRIES_INDEX_ON_FROZEN_MAP_CLUSTERING_KEY_NOT_SUPPORTED, column.name);
|
||||
|
||||
if (column.type.isUDT() && column.type.isMultiCell())
|
||||
throw ire(CREATE_ON_NON_FROZEN_UDT, column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given index attributes represent a Storage Attached Index.
|
||||
*/
|
||||
private boolean isSAIIndex(IndexAttributes attrs)
|
||||
{
|
||||
return attrs.isCustom && IndexMetadata.isSAIIndex(attrs.customClass);
|
||||
}
|
||||
|
||||
private String generateIndexName(KeyspaceMetadata keyspace, List<IndexTarget> targets)
|
||||
{
|
||||
String baseName = targets.size() == 1
|
||||
|
|
|
|||
|
|
@ -527,6 +527,11 @@ public class RowFilter implements Iterable<RowFilter.Expression>
|
|||
return operator;
|
||||
}
|
||||
|
||||
public boolean isMapElementExpression()
|
||||
{
|
||||
return kind() == Kind.MAP_ELEMENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this expression is used to query an index, the value to use as
|
||||
* partition key for that index query.
|
||||
|
|
|
|||
|
|
@ -437,6 +437,27 @@ public interface Index
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this index supports map element expressions on frozen map columns.
|
||||
*
|
||||
* @return {@code true} if this index supports map element else {@code false}.
|
||||
*/
|
||||
default boolean supportsMapElementExpression()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether index allows filtering for map element expressions on frozen collections.
|
||||
* SAI can handle map element predicates via post-filtering.
|
||||
*
|
||||
* @return {@code true} if map element expressions can be evaluated via filtering, {@code false} otherwise.
|
||||
*/
|
||||
default boolean supportsFilteringOnMapElementExpression()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the index supports custom search expressions using the
|
||||
* {@code}SELECT * FROM table WHERE expr(index_name, expression){@code} syntax, this
|
||||
|
|
|
|||
|
|
@ -453,12 +453,37 @@ public class StorageAttachedIndex implements Index
|
|||
return dependsOn(column) && indexTermType.supports(operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsExpression(RowFilter.Expression expression)
|
||||
{
|
||||
if (expression.isMapElementExpression() &&
|
||||
indexTermType.isFrozenCollection() &&
|
||||
indexTermType.indexTargetType() == IndexTarget.Type.FULL)
|
||||
|
||||
return false;
|
||||
|
||||
return supportsExpression(expression.column(), expression.operator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean filtersMultipleContains()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsMapElementExpression()
|
||||
{
|
||||
return termType().indexTargetType() == IndexTarget.Type.KEYS_AND_VALUES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFilteringOnMapElementExpression()
|
||||
{
|
||||
// SAI supports map element expressions via post-filtering on frozen collections
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractType<?> customExpressionValueType()
|
||||
{
|
||||
|
|
@ -784,6 +809,12 @@ public class StorageAttachedIndex implements Index
|
|||
while (bufferIterator != null && bufferIterator.hasNext())
|
||||
validateTermSizeForCell(analyzer, key, bufferIterator.next(), isClientMutation, state);
|
||||
}
|
||||
else if (indexTermType.isFrozenCollection() && indexTermType.indexTargetType() != IndexTarget.Type.FULL)
|
||||
{
|
||||
Iterator<ByteBuffer> bufferIterator = indexTermType.valuesOfFrozenCollection(row, FBUtilities.nowInSeconds());
|
||||
while (bufferIterator != null && bufferIterator.hasNext())
|
||||
validateTermSizeForCell(analyzer, key, bufferIterator.next(), isClientMutation, state);
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteBuffer value = indexTermType.valueOf(key, row, FBUtilities.nowInSeconds());
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import com.google.common.base.Stopwatch;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.cql3.statements.schema.IndexTarget;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.index.sai.StorageAttachedIndex;
|
||||
import org.apache.cassandra.index.sai.analyzer.AbstractAnalyzer;
|
||||
|
|
@ -94,6 +95,18 @@ public class SSTableIndexWriter implements PerColumnIndexWriter
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (index.termType().isFrozenCollection() && index.termType().indexTargetType() != IndexTarget.Type.FULL)
|
||||
{
|
||||
Iterator<ByteBuffer> valueIterator = index.termType().valuesOfFrozenCollection(row, nowInSec);
|
||||
if (valueIterator != null)
|
||||
{
|
||||
while (valueIterator.hasNext())
|
||||
{
|
||||
ByteBuffer value = valueIterator.next();
|
||||
addTerm(index.termType().asIndexBytes(value.duplicate()), key, sstableRowId);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteBuffer value = index.termType().valueOf(key.partitionKey(), row, nowInSec);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import javax.annotation.Nullable;
|
|||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import org.apache.cassandra.cql3.statements.schema.IndexTarget;
|
||||
import org.apache.cassandra.db.DecoratedKey;
|
||||
import org.apache.cassandra.db.lifecycle.ILifecycleTransaction;
|
||||
import org.apache.cassandra.db.memtable.Memtable;
|
||||
|
|
@ -76,6 +77,18 @@ public class MemtableIndexManager
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (index.termType().isFrozenCollection() && index.termType().indexTargetType() != IndexTarget.Type.FULL)
|
||||
{
|
||||
Iterator<ByteBuffer> bufferIterator = index.termType().valuesOfFrozenCollection(row, FBUtilities.nowInSeconds());
|
||||
if (bufferIterator != null)
|
||||
{
|
||||
while (bufferIterator.hasNext())
|
||||
{
|
||||
ByteBuffer value = bufferIterator.next();
|
||||
bytes += target.index(key, row.clustering(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteBuffer value = index.termType().valueOf(key, row, FBUtilities.nowInSeconds());
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import java.util.Iterator;
|
|||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import org.apache.cassandra.cql3.statements.schema.IndexTarget;
|
||||
import org.apache.cassandra.db.DecoratedKey;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.index.sai.QueryContext;
|
||||
|
|
@ -161,6 +162,12 @@ public class FilterTree
|
|||
Iterator<ByteBuffer> valueIterator = expression.getIndexTermType().valuesOf(row, now);
|
||||
return operator.apply(result, collectionMatch(valueIterator, expression));
|
||||
}
|
||||
else if (expression.getIndexTermType().isFrozenCollection() && expression.getIndexTermType().indexTargetType() != IndexTarget.Type.FULL)
|
||||
{
|
||||
Iterator<ByteBuffer> valueIterator = expression.getIndexTermType().valuesOfFrozenCollection(row, now);
|
||||
boolean matchResult = collectionMatch(valueIterator, expression);
|
||||
return operator.apply(result, matchResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteBuffer value = expression.getIndexTermType().valueOf(key, row, now);
|
||||
|
|
@ -181,6 +188,7 @@ public class FilterTree
|
|||
while (valueIterator.hasNext())
|
||||
{
|
||||
ByteBuffer value = valueIterator.next();
|
||||
|
||||
if (value == null)
|
||||
continue;
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ import java.util.Set;
|
|||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.concurrenttrees.radix.ConcurrentRadixTree;
|
||||
|
|
@ -309,6 +311,11 @@ public class IndexTermType
|
|||
return columnMetadata.name.toString();
|
||||
}
|
||||
|
||||
public IndexTarget.Type indexTargetType()
|
||||
{
|
||||
return indexTargetType;
|
||||
}
|
||||
|
||||
public AbstractType<?> vectorElementType()
|
||||
{
|
||||
assert isVector();
|
||||
|
|
@ -453,10 +460,80 @@ public class IndexTermType
|
|||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Iterator<ByteBuffer> valuesOfFrozenCollection(Row row, long nowInSecs)
|
||||
{
|
||||
if (row == null)
|
||||
return null;
|
||||
|
||||
ByteBuffer buffer;
|
||||
|
||||
if (columnMetadata.kind == ColumnMetadata.Kind.CLUSTERING)
|
||||
buffer = row.clustering().bufferAt(columnMetadata.position());
|
||||
else
|
||||
{
|
||||
Cell<?> cell = row.getCell(columnMetadata);
|
||||
if (cell == null || !cell.isLive(nowInSecs))
|
||||
return null;
|
||||
buffer = cell.buffer();
|
||||
}
|
||||
|
||||
if (buffer == null || buffer.remaining() == 0)
|
||||
return null;
|
||||
|
||||
CollectionType<?> collectionType = (CollectionType<?>) columnMetadata.type.unwrap();
|
||||
List<ByteBuffer> elements = collectionType.unpack(buffer);
|
||||
|
||||
switch (collectionType.kind)
|
||||
{
|
||||
case LIST:
|
||||
case SET:
|
||||
break;
|
||||
case MAP:
|
||||
elements = extractMapElements(elements);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported type of collection - " + collectionType.kind);
|
||||
}
|
||||
|
||||
if (isInetAddress())
|
||||
elements.sort((c1, c2) -> compareInet(encodeInetAddress(c1), encodeInetAddress(c2)));
|
||||
|
||||
return elements.iterator();
|
||||
}
|
||||
|
||||
private List<ByteBuffer> extractMapElements(List<ByteBuffer> elements)
|
||||
{
|
||||
List<ByteBuffer> result = new ArrayList<>(elements.size());
|
||||
|
||||
for (int i = 0; i < elements.size(); i += 2)
|
||||
{
|
||||
ByteBuffer key = elements.get(i);
|
||||
ByteBuffer value = i + 1 < elements.size() ? elements.get(i + 1) : null;
|
||||
|
||||
switch (indexTargetType)
|
||||
{
|
||||
case KEYS:
|
||||
result.add(key);
|
||||
break;
|
||||
case VALUES:
|
||||
if (value != null)
|
||||
result.add(value);
|
||||
break;
|
||||
case KEYS_AND_VALUES:
|
||||
if (value != null)
|
||||
result.add(CompositeType.build(ByteBufferAccessor.instance, key, value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Comparator<ByteBuffer> comparator()
|
||||
{
|
||||
// Override the comparator for BigInteger, frozen collections and composite types
|
||||
if (isBigInteger() || isBigDecimal() || isComposite() || isFrozen())
|
||||
// Override the comparator for BigInteger, BigDecimal and composite types
|
||||
if (isBigInteger() || isBigDecimal() || isComposite())
|
||||
return FastByteOperations::compareUnsigned;
|
||||
|
||||
return indexType;
|
||||
|
|
@ -474,9 +551,9 @@ public class IndexTermType
|
|||
return compareInet(b1, b2);
|
||||
else if (isLong())
|
||||
return indexType.unwrap().compare(b1, b2);
|
||||
// BigInteger values, frozen types and composite types (map entries) use compareUnsigned to maintain
|
||||
// BigInteger, BigDecimal and composite types (map entries) use compareUnsigned to maintain
|
||||
// a consistent order between the in-memory index and the on-disk index.
|
||||
else if (isBigInteger() || isBigDecimal() || isComposite() || isFrozen())
|
||||
else if (isBigInteger() || isBigDecimal() || isComposite())
|
||||
return FastByteOperations.compareUnsigned(b1, b2);
|
||||
|
||||
return indexType.compare(b1, b2);
|
||||
|
|
@ -612,6 +689,23 @@ public class IndexTermType
|
|||
return indexTargetType == IndexTarget.Type.KEYS_AND_VALUES && indexOperator == Expression.IndexOperator.EQ;
|
||||
}
|
||||
|
||||
if (isFrozenCollection())
|
||||
{
|
||||
if (indexTargetType == IndexTarget.Type.VALUES)
|
||||
return indexOperator == Expression.IndexOperator.CONTAINS_VALUE;
|
||||
|
||||
if (indexTargetType == IndexTarget.Type.KEYS)
|
||||
return indexOperator == Expression.IndexOperator.CONTAINS_KEY;
|
||||
|
||||
if (indexTargetType == IndexTarget.Type.KEYS_AND_VALUES)
|
||||
return indexOperator == Expression.IndexOperator.EQ;
|
||||
|
||||
if (indexTargetType == IndexTarget.Type.FULL)
|
||||
return indexOperator == Expression.IndexOperator.EQ;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (indexTargetType == IndexTarget.Type.FULL)
|
||||
return indexOperator == Expression.IndexOperator.EQ;
|
||||
|
||||
|
|
@ -725,7 +819,9 @@ public class IndexTermType
|
|||
|
||||
private AbstractType<?> calculateIndexType(AbstractType<?> baseType, EnumSet<Capability> capabilities, IndexTarget.Type indexTargetType)
|
||||
{
|
||||
return capabilities.contains(Capability.NON_FROZEN_COLLECTION) ? collectionCellValueType(baseType, indexTargetType) : baseType;
|
||||
if (IndexTarget.Type.FULL == indexTargetType)
|
||||
return baseType;
|
||||
return capabilities.contains(Capability.COLLECTION) ? collectionCellValueType(baseType, indexTargetType) : baseType;
|
||||
}
|
||||
|
||||
private Iterator<ByteBuffer> collectionIterator(ComplexColumnData cellData, long nowInSecs)
|
||||
|
|
@ -747,7 +843,7 @@ public class IndexTermType
|
|||
{
|
||||
if (isNonFrozenCollection())
|
||||
{
|
||||
switch (((CollectionType<?>) columnMetadata.type).kind)
|
||||
switch (((CollectionType<?>) columnMetadata.type.unwrap()).kind)
|
||||
{
|
||||
case LIST:
|
||||
return cell.buffer();
|
||||
|
|
@ -770,7 +866,7 @@ public class IndexTermType
|
|||
|
||||
private AbstractType<?> collectionCellValueType(AbstractType<?> type, IndexTarget.Type indexType)
|
||||
{
|
||||
CollectionType<?> collection = ((CollectionType<?>) type);
|
||||
CollectionType<?> collection = ((CollectionType<?>) type.unwrap());
|
||||
switch (collection.kind)
|
||||
{
|
||||
case LIST:
|
||||
|
|
|
|||
|
|
@ -225,6 +225,17 @@ public final class IndexMetadata
|
|||
return kind == Kind.COMPOSITES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given custom index class name represents a SAI.
|
||||
*/
|
||||
public static boolean isSAIIndex(String customClass)
|
||||
{
|
||||
if (customClass == null)
|
||||
return false;
|
||||
String resolved = indexNameAliases.getOrDefault(toLowerCaseLocalized(customClass), customClass);
|
||||
return StorageAttachedIndex.class.getName().equals(resolved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import org.junit.Test;
|
|||
import org.apache.cassandra.Util;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.CQLTester;
|
||||
import org.apache.cassandra.cql3.Relation;
|
||||
import org.apache.cassandra.cql3.UntypedResultSet;
|
||||
import org.apache.cassandra.dht.ByteOrderedPartitioner;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
|
|
@ -242,7 +243,7 @@ public class SecondaryIndexOnMapEntriesTest extends CQLTester
|
|||
}
|
||||
catch (InvalidRequestException e)
|
||||
{
|
||||
String expectedMessage = "Map-entry predicates on frozen map column v are not supported";
|
||||
String expectedMessage = String.format(Relation.FROZEN_MAP_ENTRY_PREDICATES_NOT_SUPPORTED, "v");
|
||||
assertTrue("Expected error message to contain '" + expectedMessage + "' but got '" +
|
||||
e.getMessage() + "'", e.getMessage().contains(expectedMessage));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1560,7 +1560,7 @@ public class SecondaryIndexTest extends CQLTester
|
|||
execute("INSERT INTO %s (k, v) VALUES (?, ?)", 1, set(udt1));
|
||||
assertInvalidMessage("Cannot create index on keys of column v with non-map type",
|
||||
"CREATE INDEX ON %s (keys(v))");
|
||||
assertInvalidMessage("full() indexes can only be created on frozen collections",
|
||||
assertInvalidMessage("full() non-SAI indexes can only be created on frozen collections",
|
||||
"CREATE INDEX ON %s (full(v))");
|
||||
String indexName = createIndex("CREATE INDEX ON %s (values(v))");
|
||||
|
||||
|
|
@ -1589,7 +1589,7 @@ public class SecondaryIndexTest extends CQLTester
|
|||
assertInvalidMessage("Cannot create index on non-frozen UDT column v", "CREATE INDEX ON %s (v)");
|
||||
assertInvalidMessage("Cannot create keys() index on v. Non-collection columns only support simple indexes", "CREATE INDEX ON %s (keys(v))");
|
||||
assertInvalidMessage("Cannot create values() index on v. Non-collection columns only support simple indexes", "CREATE INDEX ON %s (values(v))");
|
||||
assertInvalidMessage("full() indexes can only be created on frozen collections", "CREATE INDEX ON %s (full(v))");
|
||||
assertInvalidMessage("full() non-SAI indexes can only be created on frozen collections", "CREATE INDEX ON %s (full(v))");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import org.junit.Test;
|
|||
|
||||
import org.apache.cassandra.Util;
|
||||
import org.apache.cassandra.cql3.CQLTester;
|
||||
import org.apache.cassandra.cql3.Relation;
|
||||
import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
|
||||
|
||||
public class SelectSingleColumnRelationTest extends CQLTester
|
||||
|
|
@ -33,7 +34,7 @@ public class SelectSingleColumnRelationTest extends CQLTester
|
|||
public void textInvalidMapEntryPredicate() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk int, ck frozen<map<int, int>>, v int, PRIMARY KEY(pk, ck)) WITH CLUSTERING ORDER BY (ck DESC)");
|
||||
assertInvalidMessage("Map-entry predicates on frozen map column ck are not supported",
|
||||
assertInvalidMessage(String.format(Relation.FROZEN_MAP_ENTRY_PREDICATES_NOT_SUPPORTED, "ck"),
|
||||
"SELECT * FROM %s WHERE pk=? AND ck[0] = ?", 0, 0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import org.apache.cassandra.Util;
|
|||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.CQLTester;
|
||||
import org.apache.cassandra.cql3.Duration;
|
||||
import org.apache.cassandra.cql3.Relation;
|
||||
import org.apache.cassandra.cql3.UntypedResultSet;
|
||||
import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
|
|
@ -1646,11 +1647,12 @@ public class SelectTest extends CQLTester
|
|||
row(1, 2, list(1, 6), set(2, 12), map(1, 6)),
|
||||
row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
|
||||
|
||||
assertInvalidMessage("Map-entry predicates on frozen map column e are not supported",
|
||||
"SELECT * FROM %s WHERE e[1] = 6 ALLOW FILTERING");
|
||||
// CASSANDRA-18492: Allow filtering works with frozen map[key]
|
||||
assertRows(execute("SELECT * FROM %s WHERE e[1] = 6 ALLOW FILTERING"),
|
||||
row(1, 2, list(1, 6), set(2, 12), map(1, 6)));
|
||||
|
||||
assertInvalidMessage("Map-entry predicates on frozen map column e are not supported",
|
||||
"SELECT * FROM %s WHERE e[1] != 6 ALLOW FILTERING");
|
||||
assertRows(execute("SELECT * FROM %s WHERE e[1] != 6 ALLOW FILTERING"),
|
||||
row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
|
||||
|
||||
assertRows(execute("SELECT * FROM %s WHERE e CONTAINS KEY 1 AND e CONTAINS 2 ALLOW FILTERING"),
|
||||
row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
|
||||
|
|
@ -1674,9 +1676,9 @@ public class SelectTest extends CQLTester
|
|||
"SELECT * FROM %s WHERE e CONTAINS null ALLOW FILTERING");
|
||||
assertInvalidMessage("Invalid null value for column e",
|
||||
"SELECT * FROM %s WHERE e CONTAINS KEY null ALLOW FILTERING");
|
||||
assertInvalidMessage("Map-entry predicates on frozen map column e are not supported",
|
||||
assertInvalidMessage("Invalid null map key for column e",
|
||||
"SELECT * FROM %s WHERE e[null] = 2 ALLOW FILTERING");
|
||||
assertInvalidMessage("Map-entry predicates on frozen map column e are not supported",
|
||||
assertInvalidMessage("Invalid null value for e[1]",
|
||||
"SELECT * FROM %s WHERE e[1] = null ALLOW FILTERING");
|
||||
|
||||
// Checks filtering with unset
|
||||
|
|
@ -1701,10 +1703,10 @@ public class SelectTest extends CQLTester
|
|||
assertInvalidMessage("Invalid unset value for column e",
|
||||
"SELECT * FROM %s WHERE e CONTAINS KEY ? ALLOW FILTERING",
|
||||
unset());
|
||||
assertInvalidMessage("Map-entry predicates on frozen map column e are not supported",
|
||||
assertInvalidMessage("Invalid unset map key for column e",
|
||||
"SELECT * FROM %s WHERE e[?] = 2 ALLOW FILTERING",
|
||||
unset());
|
||||
assertInvalidMessage("Map-entry predicates on frozen map column e are not supported",
|
||||
assertInvalidMessage("Invalid unset value for e[1]",
|
||||
"SELECT * FROM %s WHERE e[1] = ? ALLOW FILTERING",
|
||||
unset());
|
||||
}
|
||||
|
|
@ -3556,7 +3558,7 @@ public class SelectTest extends CQLTester
|
|||
"NOT_CONTAINS_KEY or map-entry equality if it already restricted by one of those",
|
||||
"SELECT * FROM %s WHERE fm > {'lmn' : 'f'} AND fm CONTAINS KEY 'lmn'");
|
||||
|
||||
assertInvalidMessage("Map-entry predicates on frozen map column fm are not supported",
|
||||
assertInvalidMessage(String.format(Relation.FROZEN_MAP_ENTRY_PREDICATES_NOT_SUPPORTED, "fm"),
|
||||
"SELECT * FROM %s WHERE fm > {'lmn' : 'f'} AND fm['lmn'] = 'foo2'");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -657,7 +657,8 @@ public class AbstractReadQueryToCQLStringTest extends CQLTester
|
|||
test("SELECT * FROM %s WHERE u = {a: 'a', b: 1} ALLOW FILTERING");
|
||||
testInvalid("SELECT * FROM %s WHERE l['a'] = 'a' ALLOW FILTERING");
|
||||
testInvalid("SELECT * FROM %s WHERE s['a'] = 'a' ALLOW FILTERING");
|
||||
testInvalid("SELECT * FROM %s WHERE m['a'] = 'a' ALLOW FILTERING");
|
||||
// CASSANDRA-18492 Allow filtering works with map[key]
|
||||
test("SELECT * FROM %s WHERE m['a'] = 'a' ALLOW FILTERING");
|
||||
testInvalid("SELECT * FROM %s WHERE u.a = 'a' ALLOW FILTERING");
|
||||
testInvalid("SELECT * FROM %s WHERE u.b = 0 ALLOW FILTERING");
|
||||
testInvalid("SELECT * FROM %s WHERE u.a = 'a' ANd u.b = 0 ALLOW FILTERING");
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue