use of org.apache.cassandra.schema.ColumnMetadata in project cassandra by apache.
the class SASIIndex method validateOptions.
public static Map<String, String> validateOptions(Map<String, String> options, TableMetadata metadata) {
if (!(metadata.partitioner instanceof Murmur3Partitioner))
throw new ConfigurationException("SASI only supports Murmur3Partitioner.");
String targetColumn = options.get("target");
if (targetColumn == null)
throw new ConfigurationException("unknown target column");
Pair<ColumnMetadata, IndexTarget.Type> target = TargetParser.parse(metadata, targetColumn);
if (target == null)
throw new ConfigurationException("failed to retrieve target column for: " + targetColumn);
if (target.left.isComplex())
throw new ConfigurationException("complex columns are not yet supported by SASI");
IndexMode.validateAnalyzer(options);
IndexMode mode = IndexMode.getMode(target.left, options);
if (mode.mode == Mode.SPARSE) {
if (mode.isLiteral)
throw new ConfigurationException("SPARSE mode is only supported on non-literal columns.");
if (mode.isAnalyzed)
throw new ConfigurationException("SPARSE mode doesn't support analyzers.");
}
return Collections.emptyMap();
}
use of org.apache.cassandra.schema.ColumnMetadata in project cassandra by apache.
the class SASIIndexBuilder method build.
public void build() {
AbstractType<?> keyValidator = cfs.metadata().partitionKeyType;
for (Map.Entry<SSTableReader, Map<ColumnMetadata, ColumnIndex>> e : sstables.entrySet()) {
SSTableReader sstable = e.getKey();
Map<ColumnMetadata, ColumnIndex> indexes = e.getValue();
try (RandomAccessReader dataFile = sstable.openDataReader()) {
PerSSTableIndexWriter indexWriter = SASIIndex.newWriter(keyValidator, sstable.descriptor, indexes, OperationType.COMPACTION);
long previousKeyPosition = 0;
try (KeyIterator keys = new KeyIterator(sstable.descriptor, cfs.metadata())) {
while (keys.hasNext()) {
if (isStopRequested())
throw new CompactionInterruptedException(getCompactionInfo());
final DecoratedKey key = keys.next();
final long keyPosition = keys.getKeyPosition();
indexWriter.startPartition(key, keyPosition);
try {
RowIndexEntry indexEntry = sstable.getPosition(key, SSTableReader.Operator.EQ);
dataFile.seek(indexEntry.position);
// key
ByteBufferUtil.readWithShortLength(dataFile);
try (SSTableIdentityIterator partition = SSTableIdentityIterator.create(sstable, dataFile, key)) {
// if the row has statics attached, it has to be indexed separately
if (cfs.metadata().hasStaticColumns())
indexWriter.nextUnfilteredCluster(partition.staticRow());
while (partition.hasNext()) indexWriter.nextUnfilteredCluster(partition.next());
}
} catch (IOException ex) {
throw new FSReadError(ex, sstable.getFilename());
}
bytesProcessed += keyPosition - previousKeyPosition;
previousKeyPosition = keyPosition;
}
completeSSTable(indexWriter, sstable, indexes.values());
}
}
}
}
use of org.apache.cassandra.schema.ColumnMetadata in project cassandra by apache.
the class Operation method analyzeGroup.
@VisibleForTesting
protected static ListMultimap<ColumnMetadata, Expression> analyzeGroup(QueryController controller, OperationType op, List<RowFilter.Expression> expressions) {
ListMultimap<ColumnMetadata, Expression> analyzed = ArrayListMultimap.create();
// sort all of the expressions in the operation by name and priority of the logical operator
// this gives us an efficient way to handle inequality and combining into ranges without extra processing
// and converting expressions from one type to another.
Collections.sort(expressions, (a, b) -> {
int cmp = a.column().compareTo(b.column());
return cmp == 0 ? -Integer.compare(getPriority(a.operator()), getPriority(b.operator())) : cmp;
});
for (final RowFilter.Expression e : expressions) {
ColumnIndex columnIndex = controller.getIndex(e);
List<Expression> perColumn = analyzed.get(e.column());
if (columnIndex == null)
columnIndex = new ColumnIndex(controller.getKeyValidator(), e.column(), null);
AbstractAnalyzer analyzer = columnIndex.getAnalyzer();
analyzer.reset(e.getIndexValue());
// EQ/LIKE_*/NOT_EQ can have multiple expressions e.g. text = "Hello World",
// becomes text = "Hello" OR text = "World" because "space" is always interpreted as a split point (by analyzer),
// NOT_EQ is made an independent expression only in case of pre-existing multiple EQ expressions, or
// if there is no EQ operations and NOT_EQ is met or a single NOT_EQ expression present,
// in such case we know exactly that there would be no more EQ/RANGE expressions for given column
// since NOT_EQ has the lowest priority.
boolean isMultiExpression = false;
switch(e.operator()) {
case EQ:
isMultiExpression = false;
break;
case LIKE_PREFIX:
case LIKE_SUFFIX:
case LIKE_CONTAINS:
case LIKE_MATCHES:
isMultiExpression = true;
break;
case NEQ:
isMultiExpression = (perColumn.size() == 0 || perColumn.size() > 1 || (perColumn.size() == 1 && perColumn.get(0).getOp() == Op.NOT_EQ));
break;
}
if (isMultiExpression) {
while (analyzer.hasNext()) {
final ByteBuffer token = analyzer.next();
perColumn.add(new Expression(controller, columnIndex).add(e.operator(), token));
}
} else // "range" or not-equals operator, combines both bounds together into the single expression,
// iff operation of the group is AND, otherwise we are forced to create separate expressions,
// not-equals is combined with the range iff operator is AND.
{
Expression range;
if (perColumn.size() == 0 || op != OperationType.AND)
perColumn.add((range = new Expression(controller, columnIndex)));
else
range = Iterables.getLast(perColumn);
while (analyzer.hasNext()) range.add(e.operator(), analyzer.next());
}
}
return analyzed;
}
use of org.apache.cassandra.schema.ColumnMetadata in project cassandra by apache.
the class Operation method localSatisfiedBy.
/**
* Check every expression in the analyzed list to figure out if the
* columns in the give row match all of the based on the operation
* set to the current operation node.
*
* The algorithm is as follows: for every given expression from analyzed
* list get corresponding column from the Row:
* - apply {@link Expression#isSatisfiedBy(ByteBuffer)}
* method to figure out if it's satisfied;
* - apply logical operation between boolean accumulator and current boolean result;
* - if result == false and node's operation is AND return right away;
*
* After all of the expressions have been evaluated return resulting accumulator variable.
*
* Example:
*
* Operation = (op: AND, columns: [first_name = p, 5 < age < 7, last_name: y])
* Row = (first_name: pavel, last_name: y, age: 6, timestamp: 15)
*
* #1 get "first_name" = p (expressions)
* - row-get "first_name" => "pavel"
* - compare "pavel" against "p" => true (current)
* - set accumulator current => true (because this is expression #1)
*
* #2 get "last_name" = y (expressions)
* - row-get "last_name" => "y"
* - compare "y" against "y" => true (current)
* - set accumulator to accumulator & current => true
*
* #3 get 5 < "age" < 7 (expressions)
* - row-get "age" => "6"
* - compare 5 < 6 < 7 => true (current)
* - set accumulator to accumulator & current => true
*
* #4 return accumulator => true (row satisfied all of the conditions)
*
* @param currentCluster The row cluster to check.
* @param staticRow The static row associated with current cluster.
* @param allowMissingColumns allow columns value to be null.
* @return true if give Row satisfied all of the analyzed expressions,
* false otherwise.
*/
private boolean localSatisfiedBy(Unfiltered currentCluster, Row staticRow, boolean allowMissingColumns) {
if (currentCluster == null || !currentCluster.isRow())
return false;
final int now = FBUtilities.nowInSeconds();
boolean result = false;
int idx = 0;
for (ColumnMetadata column : expressions.keySet()) {
if (column.kind == Kind.PARTITION_KEY)
continue;
ByteBuffer value = ColumnIndex.getValueOf(column, column.kind == Kind.STATIC ? staticRow : (Row) currentCluster, now);
boolean isMissingColumn = value == null;
if (!allowMissingColumns && isMissingColumn)
throw new IllegalStateException("All indexed columns should be included into the column slice, missing: " + column);
boolean isMatch = false;
// If there is a column with multiple expressions that effectively means an OR
// e.g. comment = 'x y z' could be split into 'comment' EQ 'x', 'comment' EQ 'y', 'comment' EQ 'z'
// by analyzer, in situation like that we only need to check if at least one of expressions matches,
// and there is no hit on the NOT_EQ (if any) which are always at the end of the filter list.
// Loop always starts from the end of the list, which makes it possible to break after the last
// NOT_EQ condition on first EQ/RANGE condition satisfied, instead of checking every
// single expression in the column filter list.
List<Expression> filters = expressions.get(column);
for (int i = filters.size() - 1; i >= 0; i--) {
Expression expression = filters.get(i);
isMatch = !isMissingColumn && expression.isSatisfiedBy(value);
if (expression.getOp() == Op.NOT_EQ) {
// since this is NOT_EQ operation we have to
// inverse match flag (to check against other expressions),
// and break in case of negative inverse because that means
// that it's a positive hit on the not-eq clause.
isMatch = !isMatch;
if (!isMatch)
break;
} else // if it was a match on EQ/RANGE or column is missing
if (isMatch || isMissingColumn)
break;
}
if (idx++ == 0) {
result = isMatch;
continue;
}
result = op.apply(result, isMatch);
// exit early because we already got a single false
if (op == OperationType.AND && !result)
return false;
}
return idx == 0 || result;
}
use of org.apache.cassandra.schema.ColumnMetadata in project cassandra by apache.
the class CreateIndexStatement method validate.
public void validate(ClientState state) throws RequestValidationException {
TableMetadata table = Schema.instance.validateTable(keyspace(), columnFamily());
if (table.isCounter())
throw new InvalidRequestException("Secondary indexes are not supported on counter tables");
if (table.isView())
throw new InvalidRequestException("Secondary indexes are not supported on materialized views");
if (table.isCompactTable() && !table.isStaticCompactTable())
throw new InvalidRequestException("Secondary indexes are not supported on COMPACT STORAGE tables that have clustering columns");
List<IndexTarget> targets = new ArrayList<>(rawTargets.size());
for (IndexTarget.Raw rawTarget : rawTargets) targets.add(rawTarget.prepare(table));
if (targets.isEmpty() && !properties.isCustom)
throw new InvalidRequestException("Only CUSTOM indexes can be created without specifying a target column");
if (targets.size() > 1)
validateTargetsForMultiColumnIndex(targets);
for (IndexTarget target : targets) {
ColumnMetadata cd = table.getColumn(target.column);
if (cd == null)
throw new InvalidRequestException("No column definition found for column " + target.column);
if (cd.type.referencesDuration()) {
checkFalse(cd.type.isCollection(), "Secondary indexes are not supported on collections containing durations");
checkFalse(cd.type.isTuple(), "Secondary indexes are not supported on tuples containing durations");
checkFalse(cd.type.isUDT(), "Secondary indexes are not supported on UDTs containing durations");
throw invalidRequest("Secondary indexes are not supported on duration columns");
}
// TODO: we could lift that limitation
if (table.isCompactTable() && cd.isPrimaryKeyColumn())
throw new InvalidRequestException("Secondary indexes are not supported on PRIMARY KEY columns in COMPACT STORAGE tables");
if (cd.kind == ColumnMetadata.Kind.PARTITION_KEY && table.partitionKeyColumns().size() == 1)
throw new InvalidRequestException(String.format("Cannot create secondary index on partition key column %s", target.column));
boolean isMap = cd.type instanceof MapType;
boolean isFrozenCollection = cd.type.isCollection() && !cd.type.isMultiCell();
if (isFrozenCollection) {
validateForFrozenCollection(target);
} else {
validateNotFullIndex(target);
validateIsSimpleIndexIfTargetColumnNotCollection(cd, target);
validateTargetColumnIsMapIfIndexInvolvesKeys(isMap, target);
}
}
if (!Strings.isNullOrEmpty(indexName)) {
if (Schema.instance.getKeyspaceMetadata(keyspace()).existingIndexNames(null).contains(indexName)) {
if (ifNotExists)
return;
else
throw new InvalidRequestException(String.format("Index %s already exists", indexName));
}
}
properties.validate();
}
Aggregations