mirror of https://github.com/apache/cassandra
New 2i API and implementations for built in indexes
Patch by Sam Tunnicliffe; reviewed by Sergio Bossa, Sylvain Lebresne and Aleksey Yeschenko for CASSANDRA-9459 Conflicts: src/java/org/apache/cassandra/db/ColumnFamilyStore.java src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java src/java/org/apache/cassandra/db/compaction/CompactionIterator.java src/java/org/apache/cassandra/db/index/AbstractSimplePerColumnSecondaryIndex.java src/java/org/apache/cassandra/db/index/PerColumnSecondaryIndex.java src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java src/java/org/apache/cassandra/db/index/composites/CompositesIndex.java src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnClusteringKey.java src/java/org/apache/cassandra/db/partitions/AtomicBTreePartition.java src/java/org/apache/cassandra/db/partitions/PartitionUpdate.java src/java/org/apache/cassandra/index/internal/ColumnIndexSearcher.java src/java/org/apache/cassandra/index/internal/composites/CompositesSearcher.java src/java/org/apache/cassandra/index/internal/keys/KeysSearcher.java src/java/org/apache/cassandra/io/sstable/format/SSTableReader.java test/unit/org/apache/cassandra/schema/DefsTest.java test/unit/org/apache/cassandra/schema/LegacySchemaMigratorTest.java
This commit is contained in:
parent
f8089e8fa3
commit
0626be8667
4
NEWS.txt
4
NEWS.txt
|
|
@ -94,6 +94,10 @@ Upgrading
|
|||
- JMX methods set/getCompactionStrategyClass have been removed, use
|
||||
set/getCompactionParameters or set/getCompactionParametersJson instead.
|
||||
- SizeTieredCompactionStrategy parameter cold_reads_to_omit has been removed.
|
||||
- The secondary index API has been comprehensively reworked. This will be a breaking
|
||||
change for any custom index implementations, which should now look to implement
|
||||
the new org.apache.cassandra.index.Index interface.
|
||||
|
||||
|
||||
|
||||
2.2
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ import org.apache.cassandra.cql3.statements.CFStatement;
|
|||
import org.apache.cassandra.cql3.statements.CreateTableStatement;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.compaction.AbstractCompactionStrategy;
|
||||
import org.apache.cassandra.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.db.marshal.*;
|
||||
import org.apache.cassandra.dht.IPartitioner;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
|
|
@ -787,6 +786,7 @@ public final class CFMetaData
|
|||
throw new ConfigurationException(String.format("Column family comparators do not match or are not compatible (found %s; expected %s).", cfm.comparator.getClass().getSimpleName(), comparator.getClass().getSimpleName()));
|
||||
}
|
||||
|
||||
|
||||
public static Class<? extends AbstractCompactionStrategy> createCompactionStrategy(String className) throws ConfigurationException
|
||||
{
|
||||
className = className.contains(".") ? className : "org.apache.cassandra.db.compaction." + className;
|
||||
|
|
@ -875,21 +875,19 @@ public final class CFMetaData
|
|||
Set<String> indexNames = ksm == null ? new HashSet<>() : ksm.existingIndexNames(cfName);
|
||||
for (IndexMetadata index : indexes)
|
||||
{
|
||||
index.validate();
|
||||
|
||||
// check index names against this CF _and_ globally
|
||||
if (indexNames.contains(index.name))
|
||||
throw new ConfigurationException("Duplicate index name " + index.name);
|
||||
indexNames.add(index.name);
|
||||
|
||||
// This method validates any custom options in the index metadata but does not intialize the index
|
||||
SecondaryIndex.validate(this, index);
|
||||
index.validate();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// The comparator to validate the definition name with thrift.
|
||||
public AbstractType<?> thriftColumnNameType()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,12 +27,10 @@ import com.google.common.collect.Sets;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.cql3.functions.Function;
|
||||
import org.apache.cassandra.cql3.functions.FunctionName;
|
||||
import org.apache.cassandra.cql3.functions.UDAggregate;
|
||||
import org.apache.cassandra.cql3.functions.UDFunction;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.cql3.functions.*;
|
||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||
import org.apache.cassandra.db.Keyspace;
|
||||
import org.apache.cassandra.db.SystemKeyspace;
|
||||
import org.apache.cassandra.db.commitlog.CommitLog;
|
||||
import org.apache.cassandra.db.compaction.CompactionManager;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
|
|
@ -539,7 +537,7 @@ public class Schema
|
|||
assert cfs != null;
|
||||
|
||||
// make sure all the indexes are dropped, or else.
|
||||
cfs.indexManager.setIndexRemoved(new HashSet<>(cfs.indexManager.getBuiltIndexes()));
|
||||
cfs.indexManager.markAllIndexesRemoved();
|
||||
|
||||
// reinitialize the keyspace.
|
||||
CFMetaData cfm = oldKsm.tables.get(tableName).get();
|
||||
|
|
|
|||
|
|
@ -23,12 +23,13 @@ import java.util.Map;
|
|||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.filter.ColumnFilter;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.context.CounterContext;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.filter.ColumnFilter;
|
||||
import org.apache.cassandra.db.partitions.Partition;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
/**
|
||||
|
|
@ -78,7 +79,8 @@ public class UpdateParameters
|
|||
|
||||
this.prefetchedRows = prefetchedRows;
|
||||
|
||||
// Index column validation triggers a call to Keyspace.open() which we want to be able to avoid in some case (e.g. when using CQLSSTableWriter)
|
||||
// Index column validation triggers a call to Keyspace.open() which we want
|
||||
// to be able to avoid in some case (e.g. when using CQLSSTableWriter)
|
||||
if (validateIndexedColumns)
|
||||
{
|
||||
SecondaryIndexManager manager = Keyspace.openAndGetStore(metadata).indexManager;
|
||||
|
|
@ -95,17 +97,16 @@ public class UpdateParameters
|
|||
throw new InvalidRequestException(String.format("Out of bound timestamp, must be in [%d, %d]", Long.MIN_VALUE + 1, Long.MAX_VALUE));
|
||||
}
|
||||
|
||||
public void newPartition(DecoratedKey partitionKey) throws InvalidRequestException
|
||||
public void validateIndexedColumns(PartitionUpdate update)
|
||||
{
|
||||
if (indexManager != null)
|
||||
indexManager.validate(partitionKey);
|
||||
if (indexManager == null)
|
||||
return;
|
||||
|
||||
indexManager.validate(update);
|
||||
}
|
||||
|
||||
public void newRow(Clustering clustering) throws InvalidRequestException
|
||||
{
|
||||
if (indexManager != null)
|
||||
indexManager.validate(clustering);
|
||||
|
||||
if (metadata.isDense() && !metadata.isCompound())
|
||||
{
|
||||
// If it's a COMPACT STORAGE table with a single clustering column, the clustering value is
|
||||
|
|
@ -165,9 +166,6 @@ public class UpdateParameters
|
|||
|
||||
public void addCell(ColumnDefinition column, CellPath path, ByteBuffer value) throws InvalidRequestException
|
||||
{
|
||||
if (indexManager != null)
|
||||
indexManager.validate(column, value, path);
|
||||
|
||||
Cell cell = ttl == LivenessInfo.NO_TTL
|
||||
? BufferCell.live(metadata, column, timestamp, value, path)
|
||||
: BufferCell.expiring(column, timestamp, ttl, nowInSec, value, path);
|
||||
|
|
|
|||
|
|
@ -18,17 +18,20 @@
|
|||
package org.apache.cassandra.cql3.restrictions;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.NavigableSet;
|
||||
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.cql3.QueryOptions;
|
||||
import org.apache.cassandra.cql3.functions.Function;
|
||||
import org.apache.cassandra.cql3.statements.Bound;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.Clustering;
|
||||
import org.apache.cassandra.db.MultiCBuilder;
|
||||
import org.apache.cassandra.db.Slice;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.index.SecondaryIndexManager;
|
||||
|
||||
/**
|
||||
* A <code>PrimaryKeyRestrictions</code> which forwards all its method calls to another
|
||||
|
|
|
|||
|
|
@ -20,18 +20,17 @@ package org.apache.cassandra.cql3.restrictions;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.cassandra.cql3.Term.Terminal;
|
||||
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.cql3.Term.Terminal;
|
||||
import org.apache.cassandra.cql3.functions.Function;
|
||||
import org.apache.cassandra.cql3.statements.Bound;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.MultiCBuilder;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
import org.apache.cassandra.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.index.Index;
|
||||
import org.apache.cassandra.index.SecondaryIndexManager;
|
||||
|
||||
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
|
||||
import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
|
||||
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
|
||||
|
|
@ -113,23 +112,20 @@ public abstract class MultiColumnRestriction extends AbstractRestriction
|
|||
@Override
|
||||
public final boolean hasSupportingIndex(SecondaryIndexManager indexManager)
|
||||
{
|
||||
for (ColumnDefinition columnDef : columnDefs)
|
||||
{
|
||||
SecondaryIndex index = indexManager.getIndexForColumn(columnDef);
|
||||
if (index != null && isSupportedBy(index))
|
||||
return true;
|
||||
}
|
||||
for (Index index : indexManager.listIndexes())
|
||||
if (isSupportedBy(index))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this type of restriction is supported for by the specified index.
|
||||
* @param index the Secondary index
|
||||
* @param index the secondary index
|
||||
*
|
||||
* @return <code>true</code> this type of restriction is supported by the specified index,
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
protected abstract boolean isSupportedBy(SecondaryIndex index);
|
||||
protected abstract boolean isSupportedBy(Index index);
|
||||
|
||||
public static class EQRestriction extends MultiColumnRestriction
|
||||
{
|
||||
|
|
@ -161,9 +157,12 @@ public abstract class MultiColumnRestriction extends AbstractRestriction
|
|||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSupportedBy(SecondaryIndex index)
|
||||
protected boolean isSupportedBy(Index index)
|
||||
{
|
||||
return index.supportsOperator(Operator.EQ);
|
||||
for(ColumnDefinition column : columnDefs)
|
||||
if (index.supportsExpression(column, Operator.EQ))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -228,9 +227,12 @@ public abstract class MultiColumnRestriction extends AbstractRestriction
|
|||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSupportedBy(SecondaryIndex index)
|
||||
protected boolean isSupportedBy(Index index)
|
||||
{
|
||||
return index.supportsOperator(Operator.IN);
|
||||
for (ColumnDefinition column: columnDefs)
|
||||
if (index.supportsExpression(column, Operator.IN))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -368,9 +370,12 @@ public abstract class MultiColumnRestriction extends AbstractRestriction
|
|||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSupportedBy(SecondaryIndex index)
|
||||
protected boolean isSupportedBy(Index index)
|
||||
{
|
||||
return slice.isSupportedBy(index);
|
||||
for(ColumnDefinition def : columnDefs)
|
||||
if (slice.isSupportedBy(def, index))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ import org.apache.cassandra.cql3.functions.Function;
|
|||
import org.apache.cassandra.cql3.statements.Bound;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.utils.btree.BTreeSet;
|
||||
|
||||
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ import org.apache.cassandra.cql3.functions.Function;
|
|||
import org.apache.cassandra.cql3.statements.Bound;
|
||||
import org.apache.cassandra.db.MultiCBuilder;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.index.SecondaryIndexManager;
|
||||
|
||||
/**
|
||||
* A restriction/clause on a column.
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ import org.apache.cassandra.cql3.QueryOptions;
|
|||
import org.apache.cassandra.cql3.functions.Function;
|
||||
import org.apache.cassandra.cql3.restrictions.SingleColumnRestriction.ContainsRestriction;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.index.SecondaryIndexManager;
|
||||
|
||||
/**
|
||||
* Sets of column restrictions.
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ import org.apache.cassandra.config.ColumnDefinition;
|
|||
import org.apache.cassandra.cql3.QueryOptions;
|
||||
import org.apache.cassandra.cql3.functions.Function;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.index.SecondaryIndexManager;
|
||||
|
||||
/**
|
||||
* Sets of restrictions
|
||||
|
|
|
|||
|
|
@ -29,9 +29,10 @@ import org.apache.cassandra.cql3.functions.Function;
|
|||
import org.apache.cassandra.cql3.statements.Bound;
|
||||
import org.apache.cassandra.db.MultiCBuilder;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
import org.apache.cassandra.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.index.Index;
|
||||
import org.apache.cassandra.index.SecondaryIndexManager;
|
||||
|
||||
import static org.apache.cassandra.cql3.statements.RequestValidations.checkBindValueSet;
|
||||
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
|
||||
import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
|
||||
|
|
@ -71,8 +72,11 @@ public abstract class SingleColumnRestriction extends AbstractRestriction
|
|||
@Override
|
||||
public boolean hasSupportingIndex(SecondaryIndexManager indexManager)
|
||||
{
|
||||
SecondaryIndex index = indexManager.getIndexForColumn(columnDef);
|
||||
return index != null && isSupportedBy(index);
|
||||
for (Index index : indexManager.listIndexes())
|
||||
if (isSupportedBy(index))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -110,11 +114,11 @@ public abstract class SingleColumnRestriction extends AbstractRestriction
|
|||
/**
|
||||
* Check if this type of restriction is supported by the specified index.
|
||||
*
|
||||
* @param index the Secondary index
|
||||
* @param index the secondary index
|
||||
* @return <code>true</code> this type of restriction is supported by the specified index,
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
protected abstract boolean isSupportedBy(SecondaryIndex index);
|
||||
protected abstract boolean isSupportedBy(Index index);
|
||||
|
||||
public static final class EQRestriction extends SingleColumnRestriction
|
||||
{
|
||||
|
|
@ -174,9 +178,9 @@ public abstract class SingleColumnRestriction extends AbstractRestriction
|
|||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSupportedBy(SecondaryIndex index)
|
||||
protected boolean isSupportedBy(Index index)
|
||||
{
|
||||
return index.supportsOperator(Operator.EQ);
|
||||
return index.supportsExpression(columnDef, Operator.EQ);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -220,9 +224,9 @@ public abstract class SingleColumnRestriction extends AbstractRestriction
|
|||
}
|
||||
|
||||
@Override
|
||||
protected final boolean isSupportedBy(SecondaryIndex index)
|
||||
protected final boolean isSupportedBy(Index index)
|
||||
{
|
||||
return index.supportsOperator(Operator.IN);
|
||||
return index.supportsExpression(columnDef, Operator.IN);
|
||||
}
|
||||
|
||||
protected abstract List<ByteBuffer> getValues(QueryOptions options) throws InvalidRequestException;
|
||||
|
|
@ -387,9 +391,9 @@ public abstract class SingleColumnRestriction extends AbstractRestriction
|
|||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSupportedBy(SecondaryIndex index)
|
||||
protected boolean isSupportedBy(Index index)
|
||||
{
|
||||
return slice.isSupportedBy(index);
|
||||
return slice.isSupportedBy(columnDef, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -484,18 +488,18 @@ public abstract class SingleColumnRestriction extends AbstractRestriction
|
|||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSupportedBy(SecondaryIndex index)
|
||||
protected boolean isSupportedBy(Index index)
|
||||
{
|
||||
boolean supported = false;
|
||||
|
||||
if (numberOfValues() > 0)
|
||||
supported |= index.supportsOperator(Operator.CONTAINS);
|
||||
supported |= index.supportsExpression(columnDef, Operator.CONTAINS);
|
||||
|
||||
if (numberOfKeys() > 0)
|
||||
supported |= index.supportsOperator(Operator.CONTAINS_KEY);
|
||||
supported |= index.supportsExpression(columnDef, Operator.CONTAINS_KEY);
|
||||
|
||||
if (numberOfEntries() > 0)
|
||||
supported |= index.supportsOperator(Operator.EQ);
|
||||
supported |= index.supportsExpression(columnDef, Operator.EQ);
|
||||
|
||||
return supported;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,10 +30,9 @@ import org.apache.cassandra.cql3.functions.Function;
|
|||
import org.apache.cassandra.cql3.statements.Bound;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.dht.*;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.service.StorageService;
|
||||
import org.apache.cassandra.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.btree.BTreeSet;
|
||||
|
||||
|
|
@ -47,6 +46,8 @@ import static org.apache.cassandra.cql3.statements.RequestValidations.invalidReq
|
|||
*/
|
||||
public final class StatementRestrictions
|
||||
{
|
||||
public static final String NO_INDEX_FOUND_MESSAGE =
|
||||
"No supported secondary index found for the non primary key columns restrictions";
|
||||
/**
|
||||
* The Column Family meta data
|
||||
*/
|
||||
|
|
@ -159,7 +160,7 @@ public final class StatementRestrictions
|
|||
if (hasQueriableIndex)
|
||||
usesSecondaryIndexing = true;
|
||||
else if (!useFiltering)
|
||||
throw new InvalidRequestException("No supported secondary index found for the non primary key columns restrictions");
|
||||
throw new InvalidRequestException(NO_INDEX_FOUND_MESSAGE);
|
||||
|
||||
indexRestrictions.add(nonPrimaryKeyRestrictions);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,12 @@ import java.util.Collections;
|
|||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.cql3.Operator;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.cql3.functions.Function;
|
||||
import org.apache.cassandra.cql3.statements.Bound;
|
||||
import org.apache.cassandra.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.index.Index;
|
||||
|
||||
final class TermSlice
|
||||
{
|
||||
|
|
@ -152,20 +153,20 @@ final class TermSlice
|
|||
/**
|
||||
* Check if this <code>TermSlice</code> is supported by the specified index.
|
||||
*
|
||||
* @param index the Secondary index
|
||||
* @param index the secondary index
|
||||
* @return <code>true</code> this type of <code>TermSlice</code> is supported by the specified index,
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean isSupportedBy(SecondaryIndex index)
|
||||
public boolean isSupportedBy(ColumnDefinition column, Index index)
|
||||
{
|
||||
boolean supported = false;
|
||||
|
||||
if (hasBound(Bound.START))
|
||||
supported |= isInclusive(Bound.START) ? index.supportsOperator(Operator.GTE)
|
||||
: index.supportsOperator(Operator.GT);
|
||||
supported |= isInclusive(Bound.START) ? index.supportsExpression(column, Operator.GTE)
|
||||
: index.supportsExpression(column, Operator.GT);
|
||||
if (hasBound(Bound.END))
|
||||
supported |= isInclusive(Bound.END) ? index.supportsOperator(Operator.LTE)
|
||||
: index.supportsOperator(Operator.LT);
|
||||
supported |= isInclusive(Bound.END) ? index.supportsExpression(column, Operator.LTE)
|
||||
: index.supportsExpression(column, Operator.LT);
|
||||
|
||||
return supported;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,10 +28,12 @@ import org.apache.cassandra.cql3.QueryOptions;
|
|||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.cql3.functions.Function;
|
||||
import org.apache.cassandra.cql3.statements.Bound;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.Clustering;
|
||||
import org.apache.cassandra.db.MultiCBuilder;
|
||||
import org.apache.cassandra.db.Slice;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.index.SecondaryIndexManager;
|
||||
|
||||
import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
|
||||
|
||||
|
|
|
|||
|
|
@ -17,9 +17,7 @@
|
|||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.cassandra.auth.Permission;
|
||||
import org.apache.cassandra.config.*;
|
||||
|
|
@ -30,6 +28,8 @@ import org.apache.cassandra.db.marshal.AbstractType;
|
|||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.db.marshal.CounterColumnType;
|
||||
import org.apache.cassandra.exceptions.*;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
import org.apache.cassandra.schema.Indexes;
|
||||
import org.apache.cassandra.schema.TableParams;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.service.MigrationManager;
|
||||
|
|
@ -264,11 +264,17 @@ public class AlterTableStatement extends SchemaAlteringStatement
|
|||
break;
|
||||
}
|
||||
|
||||
// If a column is dropped which is the target of a secondary index
|
||||
// we need to also drop the index.
|
||||
cfm.getIndexes().get(def).ifPresent(indexMetadata -> {
|
||||
cfm.indexes(cfm.getIndexes().without(indexMetadata.name));
|
||||
});
|
||||
// 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.
|
||||
Indexes allIndexes = cfm.getIndexes();
|
||||
Collection<IndexMetadata> indexes = allIndexes.get(def);
|
||||
for (IndexMetadata index : indexes)
|
||||
{
|
||||
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);
|
||||
}
|
||||
cfm.indexes(allIndexes);
|
||||
|
||||
// If a column is dropped which is the target of a materialized view,
|
||||
// then we need to drop the view.
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@ package org.apache.cassandra.cql3.statements;
|
|||
import java.util.Collections;
|
||||
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;
|
||||
|
||||
|
|
@ -96,32 +98,15 @@ public class CreateIndexStatement extends SchemaAlteringStatement
|
|||
validateTargetColumnIsMapIfIndexInvolvesKeys(isMap, target);
|
||||
}
|
||||
|
||||
Indexes existingIndexes = cfm.getIndexes();
|
||||
for (IndexMetadata index : existingIndexes)
|
||||
{
|
||||
if (index.indexedColumn(cfm).equals(cd))
|
||||
{
|
||||
IndexTarget.TargetType prevType = IndexTarget.TargetType.fromIndexMetadata(index, cfm);
|
||||
if (isMap && target.type != prevType)
|
||||
{
|
||||
String msg = "Cannot create index on %s(%s): an index on %s(%s) already exists and indexing " +
|
||||
"a map on more than one dimension at the same time is not currently supported";
|
||||
throw new InvalidRequestException(String.format(msg,
|
||||
target.type, target.column,
|
||||
prevType, target.column));
|
||||
}
|
||||
|
||||
if (ifNotExists)
|
||||
return;
|
||||
else
|
||||
throw new InvalidRequestException("Index already existss");
|
||||
}
|
||||
}
|
||||
|
||||
if (!Strings.isNullOrEmpty(indexName))
|
||||
{
|
||||
if (Schema.instance.getKSMetaData(keyspace()).existingIndexNames(null).contains(indexName))
|
||||
throw new InvalidRequestException("Index already exists");
|
||||
{
|
||||
if (ifNotExists)
|
||||
return;
|
||||
else
|
||||
throw new InvalidRequestException(String.format("Index %s already exists", indexName));
|
||||
}
|
||||
}
|
||||
|
||||
properties.validate();
|
||||
|
|
@ -150,26 +135,26 @@ public class CreateIndexStatement extends SchemaAlteringStatement
|
|||
|
||||
private void validateForFrozenCollection(IndexTarget target) throws InvalidRequestException
|
||||
{
|
||||
if (target.type != IndexTarget.TargetType.FULL)
|
||||
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));
|
||||
}
|
||||
|
||||
private void validateNotFullIndex(IndexTarget target) throws InvalidRequestException
|
||||
{
|
||||
if (target.type == IndexTarget.TargetType.FULL)
|
||||
if (target.type == IndexTarget.Type.FULL)
|
||||
throw new InvalidRequestException("full() indexes can only be created on frozen collections");
|
||||
}
|
||||
|
||||
private void validateIsValuesIndexIfTargetColumnNotCollection(ColumnDefinition cd, IndexTarget target) throws InvalidRequestException
|
||||
{
|
||||
if (!cd.type.isCollection() && target.type != IndexTarget.TargetType.VALUES)
|
||||
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));
|
||||
}
|
||||
|
||||
private void validateTargetColumnIsMapIfIndexInvolvesKeys(boolean isMap, IndexTarget target) throws InvalidRequestException
|
||||
{
|
||||
if (target.type == IndexTarget.TargetType.KEYS || target.type == IndexTarget.TargetType.KEYS_AND_VALUES)
|
||||
if (target.type == IndexTarget.Type.KEYS || target.type == IndexTarget.Type.KEYS_AND_VALUES)
|
||||
{
|
||||
if (!isMap)
|
||||
throw new InvalidRequestException(String.format("Cannot create index on %s of column %s with non-map type",
|
||||
|
|
@ -187,12 +172,13 @@ public class CreateIndexStatement extends SchemaAlteringStatement
|
|||
if (Strings.isNullOrEmpty(acceptedName))
|
||||
acceptedName = Indexes.getAvailableIndexName(keyspace(), columnFamily(), cd.name);
|
||||
|
||||
for (IndexMetadata existing : cfm.getIndexes())
|
||||
if (existing.indexedColumn(cfm).equals(cd) || existing.name.equals(acceptedName))
|
||||
if (ifNotExists)
|
||||
return false;
|
||||
else
|
||||
throw new InvalidRequestException("Index already exists");
|
||||
if (Schema.instance.getKSMetaData(keyspace()).existingIndexNames(null).contains(acceptedName))
|
||||
{
|
||||
if (ifNotExists)
|
||||
return false;
|
||||
else
|
||||
throw new InvalidRequestException(String.format("Index %s already exists", acceptedName));
|
||||
}
|
||||
|
||||
IndexMetadata.IndexType indexType;
|
||||
Map<String, String> indexOptions;
|
||||
|
|
@ -218,12 +204,18 @@ public class CreateIndexStatement extends SchemaAlteringStatement
|
|||
indexOptions = Collections.emptyMap();
|
||||
}
|
||||
|
||||
IndexMetadata index = IndexMetadata.singleColumnIndex(cd, acceptedName, indexType, 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));
|
||||
if (existingIndex.isPresent())
|
||||
throw new InvalidRequestException(String.format("Index %s is a duplicate of existing index %s",
|
||||
index.name,
|
||||
existingIndex.get().name));
|
||||
|
||||
logger.debug("Updating index definition for {}", indexName);
|
||||
|
||||
IndexMetadata index = IndexMetadata.legacyIndex(cd, acceptedName, indexType, indexOptions);
|
||||
cfm.indexes(cfm.getIndexes().with(index));
|
||||
|
||||
MigrationManager.announceColumnFamilyUpdate(cfm, false, isLocalOnly);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,17 +17,19 @@
|
|||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Iterators;
|
||||
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.cql3.restrictions.Restriction;
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.exceptions.*;
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.cql3.restrictions.Restriction;
|
||||
import org.apache.cassandra.db.CBuilder;
|
||||
import org.apache.cassandra.db.Clustering;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
/**
|
||||
|
|
@ -93,6 +95,8 @@ public class DeleteStatement extends ModificationStatement
|
|||
update.add(params.buildRow());
|
||||
}
|
||||
}
|
||||
|
||||
params.validateIndexedColumns(update);
|
||||
}
|
||||
|
||||
protected void validateWhereClauseForConditions() throws InvalidRequestException
|
||||
|
|
|
|||
|
|
@ -19,8 +19,9 @@ package org.apache.cassandra.cql3.statements;
|
|||
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.cassandra.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.exceptions.*;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.exceptions.RequestValidationException;
|
||||
import org.apache.cassandra.exceptions.SyntaxException;
|
||||
|
||||
public class IndexPropDefs extends PropertyDefinitions
|
||||
{
|
||||
|
|
@ -50,9 +51,9 @@ public class IndexPropDefs extends PropertyDefinitions
|
|||
if (!isCustom && !properties.isEmpty())
|
||||
throw new InvalidRequestException("Cannot specify options for a non-CUSTOM index");
|
||||
|
||||
if (getRawOptions().containsKey(SecondaryIndex.CUSTOM_INDEX_OPTION_NAME))
|
||||
if (getRawOptions().containsKey(IndexTarget.CUSTOM_INDEX_OPTION_NAME))
|
||||
throw new InvalidRequestException(String.format("Cannot specify %s as a CUSTOM option",
|
||||
SecondaryIndex.CUSTOM_INDEX_OPTION_NAME));
|
||||
IndexTarget.CUSTOM_INDEX_OPTION_NAME));
|
||||
}
|
||||
|
||||
public Map<String, String> getRawOptions() throws SyntaxException
|
||||
|
|
@ -64,7 +65,7 @@ public class IndexPropDefs extends PropertyDefinitions
|
|||
public Map<String, String> getOptions() throws SyntaxException
|
||||
{
|
||||
Map<String, String> options = new HashMap<>(getRawOptions());
|
||||
options.put(SecondaryIndex.CUSTOM_INDEX_OPTION_NAME, customClass);
|
||||
options.put(IndexTarget.CUSTOM_INDEX_OPTION_NAME, customClass);
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,15 +22,31 @@ import java.util.Map;
|
|||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
|
||||
public class IndexTarget
|
||||
{
|
||||
public final ColumnIdentifier column;
|
||||
public final TargetType type;
|
||||
public static final String CUSTOM_INDEX_OPTION_NAME = "class_name";
|
||||
|
||||
private IndexTarget(ColumnIdentifier column, TargetType type)
|
||||
/**
|
||||
* The name of the option used to specify that the index is on the collection keys.
|
||||
*/
|
||||
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";
|
||||
|
||||
public final ColumnIdentifier column;
|
||||
public final Type type;
|
||||
|
||||
private IndexTarget(ColumnIdentifier column, Type type)
|
||||
{
|
||||
this.column = column;
|
||||
this.type = type;
|
||||
|
|
@ -39,9 +55,9 @@ public class IndexTarget
|
|||
public static class Raw
|
||||
{
|
||||
private final ColumnIdentifier.Raw column;
|
||||
private final TargetType type;
|
||||
private final Type type;
|
||||
|
||||
private Raw(ColumnIdentifier.Raw column, TargetType type)
|
||||
private Raw(ColumnIdentifier.Raw column, Type type)
|
||||
{
|
||||
this.column = column;
|
||||
this.type = type;
|
||||
|
|
@ -49,22 +65,22 @@ public class IndexTarget
|
|||
|
||||
public static Raw valuesOf(ColumnIdentifier.Raw c)
|
||||
{
|
||||
return new Raw(c, TargetType.VALUES);
|
||||
return new Raw(c, Type.VALUES);
|
||||
}
|
||||
|
||||
public static Raw keysOf(ColumnIdentifier.Raw c)
|
||||
{
|
||||
return new Raw(c, TargetType.KEYS);
|
||||
return new Raw(c, Type.KEYS);
|
||||
}
|
||||
|
||||
public static Raw keysAndValuesOf(ColumnIdentifier.Raw c)
|
||||
{
|
||||
return new Raw(c, TargetType.KEYS_AND_VALUES);
|
||||
return new Raw(c, Type.KEYS_AND_VALUES);
|
||||
}
|
||||
|
||||
public static Raw fullCollection(ColumnIdentifier.Raw c)
|
||||
{
|
||||
return new Raw(c, TargetType.FULL);
|
||||
return new Raw(c, Type.FULL);
|
||||
}
|
||||
|
||||
public IndexTarget prepare(CFMetaData cfm)
|
||||
|
|
@ -73,7 +89,7 @@ public class IndexTarget
|
|||
}
|
||||
}
|
||||
|
||||
public static enum TargetType
|
||||
public static enum Type
|
||||
{
|
||||
VALUES, KEYS, KEYS_AND_VALUES, FULL;
|
||||
|
||||
|
|
@ -92,21 +108,21 @@ public class IndexTarget
|
|||
{
|
||||
switch (this)
|
||||
{
|
||||
case KEYS: return SecondaryIndex.INDEX_KEYS_OPTION_NAME;
|
||||
case KEYS_AND_VALUES: return SecondaryIndex.INDEX_ENTRIES_OPTION_NAME;
|
||||
case VALUES: return SecondaryIndex.INDEX_VALUES_OPTION_NAME;
|
||||
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 TargetType fromIndexMetadata(IndexMetadata index, CFMetaData cfm)
|
||||
public static Type fromIndexMetadata(IndexMetadata index, CFMetaData cfm)
|
||||
{
|
||||
Map<String, String> options = index.options;
|
||||
if (options.containsKey(SecondaryIndex.INDEX_KEYS_OPTION_NAME))
|
||||
if (options.containsKey(INDEX_KEYS_OPTION_NAME))
|
||||
{
|
||||
return KEYS;
|
||||
}
|
||||
else if (options.containsKey(SecondaryIndex.INDEX_ENTRIES_OPTION_NAME))
|
||||
else if (options.containsKey(INDEX_ENTRIES_OPTION_NAME))
|
||||
{
|
||||
return KEYS_AND_VALUES;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,20 +35,24 @@ import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
|
|||
import org.apache.cassandra.cql3.selection.RawSelector;
|
||||
import org.apache.cassandra.cql3.selection.Selection;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.filter.*;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.db.marshal.CompositeType;
|
||||
import org.apache.cassandra.db.marshal.Int32Type;
|
||||
import org.apache.cassandra.db.partitions.PartitionIterator;
|
||||
import org.apache.cassandra.db.rows.ComplexColumnData;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.db.rows.RowIterator;
|
||||
import org.apache.cassandra.db.view.MaterializedView;
|
||||
import org.apache.cassandra.dht.AbstractBounds;
|
||||
import org.apache.cassandra.exceptions.*;
|
||||
import org.apache.cassandra.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.serializers.MarshalException;
|
||||
import org.apache.cassandra.service.*;
|
||||
import org.apache.cassandra.service.pager.QueryPager;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.service.ClientWarn;
|
||||
import org.apache.cassandra.service.QueryState;
|
||||
import org.apache.cassandra.service.pager.PagingState;
|
||||
import org.apache.cassandra.service.pager.QueryPager;
|
||||
import org.apache.cassandra.thrift.ThriftValidation;
|
||||
import org.apache.cassandra.transport.messages.ResultMessage;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
|
@ -74,6 +78,10 @@ public class SelectStatement implements CQLStatement
|
|||
private static final Logger logger = LoggerFactory.getLogger(SelectStatement.class);
|
||||
|
||||
private static final int DEFAULT_COUNT_PAGE_SIZE = 10000;
|
||||
public static final String REQUIRES_ALLOW_FILTERING_MESSAGE =
|
||||
"Cannot execute this query as it might involve data filtering and " +
|
||||
"thus may have unpredictable performance. If you want to execute " +
|
||||
"this query despite the performance unpredictability, use ALLOW FILTERING";
|
||||
|
||||
private final int boundTerms;
|
||||
public final CFMetaData cfm;
|
||||
|
|
@ -581,7 +589,6 @@ public class SelectStatement implements CQLStatement
|
|||
ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(columnFamily());
|
||||
SecondaryIndexManager secondaryIndexManager = cfs.indexManager;
|
||||
RowFilter filter = restrictions.getRowFilter(secondaryIndexManager, options);
|
||||
secondaryIndexManager.validateFilter(filter);
|
||||
return filter;
|
||||
}
|
||||
|
||||
|
|
@ -937,10 +944,7 @@ public class SelectStatement implements CQLStatement
|
|||
// We will potentially filter data if either:
|
||||
// - Have more than one IndexExpression
|
||||
// - Have no index expression and the row filter is not the identity
|
||||
checkFalse(restrictions.needFiltering(),
|
||||
"Cannot execute this query as it might involve data filtering and " +
|
||||
"thus may have unpredictable performance. If you want to execute " +
|
||||
"this query despite the performance unpredictability, use ALLOW FILTERING");
|
||||
checkFalse(restrictions.needFiltering(), REQUIRES_ALLOW_FILTERING_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,14 +17,18 @@
|
|||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.db.CBuilder;
|
||||
import org.apache.cassandra.db.Clustering;
|
||||
import org.apache.cassandra.db.CompactTables;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.exceptions.*;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
|
|
@ -49,8 +53,6 @@ public class UpdateStatement extends ModificationStatement
|
|||
public void addUpdateForKey(PartitionUpdate update, CBuilder cbuilder, UpdateParameters params)
|
||||
throws InvalidRequestException
|
||||
{
|
||||
params.newPartition(update.partitionKey());
|
||||
|
||||
if (updatesRegularRows())
|
||||
{
|
||||
params.newRow(cbuilder.build());
|
||||
|
|
@ -90,6 +92,8 @@ public class UpdateStatement extends ModificationStatement
|
|||
op.execute(update.partitionKey(), params);
|
||||
update.add(params.buildRow());
|
||||
}
|
||||
|
||||
params.validateIndexedColumns(update);
|
||||
}
|
||||
|
||||
public static class ParsedInsert extends ModificationStatement.Parsed
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@
|
|||
*/
|
||||
package org.apache.cassandra.db;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
|
|
@ -44,33 +46,38 @@ import org.apache.cassandra.config.*;
|
|||
import org.apache.cassandra.db.commitlog.CommitLog;
|
||||
import org.apache.cassandra.db.commitlog.ReplayPosition;
|
||||
import org.apache.cassandra.db.compaction.*;
|
||||
import org.apache.cassandra.db.filter.*;
|
||||
import org.apache.cassandra.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.db.view.MaterializedViewManager;
|
||||
import org.apache.cassandra.db.filter.ClusteringIndexFilter;
|
||||
import org.apache.cassandra.db.filter.DataLimits;
|
||||
import org.apache.cassandra.db.lifecycle.*;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.partitions.CachedPartition;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.db.rows.CellPath;
|
||||
import org.apache.cassandra.db.view.MaterializedViewManager;
|
||||
import org.apache.cassandra.dht.*;
|
||||
import org.apache.cassandra.dht.Range;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.index.internal.CassandraIndex;
|
||||
import org.apache.cassandra.index.transactions.UpdateTransaction;
|
||||
import org.apache.cassandra.io.FSWriteError;
|
||||
import org.apache.cassandra.io.sstable.*;
|
||||
import org.apache.cassandra.io.sstable.Component;
|
||||
import org.apache.cassandra.io.sstable.Descriptor;
|
||||
import org.apache.cassandra.io.sstable.SSTableMultiWriter;
|
||||
import org.apache.cassandra.io.sstable.format.*;
|
||||
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
|
||||
import org.apache.cassandra.io.util.FileUtils;
|
||||
import org.apache.cassandra.metrics.TableMetrics.Sampler;
|
||||
import org.apache.cassandra.metrics.TableMetrics;
|
||||
import org.apache.cassandra.metrics.TableMetrics.Sampler;
|
||||
import org.apache.cassandra.schema.*;
|
||||
import org.apache.cassandra.service.CacheService;
|
||||
import org.apache.cassandra.service.StorageService;
|
||||
import org.apache.cassandra.utils.*;
|
||||
import org.apache.cassandra.utils.TopKSampler.SamplerResult;
|
||||
import org.apache.cassandra.utils.concurrent.*;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.utils.concurrent.Refs;
|
||||
import org.apache.cassandra.utils.memory.MemtableAllocator;
|
||||
import org.json.simple.*;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import static org.apache.cassandra.utils.Throwables.maybeFail;
|
||||
|
||||
|
|
@ -403,7 +410,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
|
||||
// create the private ColumnFamilyStores for the secondary column indexes
|
||||
for (IndexMetadata info : metadata.getIndexes())
|
||||
indexManager.addIndexedColumn(info);
|
||||
indexManager.addIndex(info);
|
||||
|
||||
if (registerBookkeeping)
|
||||
{
|
||||
|
|
@ -504,8 +511,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
|
||||
data.dropSSTables();
|
||||
TransactionLog.waitForDeletions();
|
||||
|
||||
indexManager.invalidate();
|
||||
indexManager.invalidateAllIndexesBlocking();
|
||||
materializedViewManager.invalidate();
|
||||
|
||||
invalidateCaches();
|
||||
|
|
@ -615,9 +621,12 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
}
|
||||
|
||||
// also clean out any index leftovers.
|
||||
for (IndexMetadata def : metadata.getIndexes())
|
||||
if (!def.isCustom())
|
||||
scrubDataDirectories(SecondaryIndex.newIndexMetadata(metadata, def));
|
||||
for (IndexMetadata index : metadata.getIndexes())
|
||||
if (!index.isCustom())
|
||||
{
|
||||
CFMetaData indexMetadata = CassandraIndex.indexCfsMetadata(metadata, index);
|
||||
scrubDataDirectories(indexMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
// must be called after all sstables are loaded since row cache merges all row versions
|
||||
|
|
@ -746,7 +755,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
try (Refs<SSTableReader> refs = Refs.ref(newSSTables))
|
||||
{
|
||||
data.addSSTables(newSSTables);
|
||||
indexManager.maybeBuildSecondaryIndexes(newSSTables, indexManager.allIndexesNames());
|
||||
indexManager.buildAllIndexesBlocking(newSSTables);
|
||||
}
|
||||
|
||||
logger.info("Done loading load new SSTables for {}/{}", keyspace.getName(), name);
|
||||
|
|
@ -766,10 +775,8 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
Iterable<SSTableReader> sstables = cfs.getSSTables(SSTableSet.CANONICAL);
|
||||
try (Refs<SSTableReader> refs = Refs.ref(sstables))
|
||||
{
|
||||
cfs.indexManager.setIndexRemoved(indexes);
|
||||
logger.info(String.format("User Requested secondary index re-build for %s/%s indexes", ksName, cfName));
|
||||
cfs.indexManager.maybeBuildSecondaryIndexes(refs, indexes);
|
||||
cfs.indexManager.setIndexBuilt(indexes);
|
||||
cfs.indexManager.rebuildIndexesBlocking(refs, indexes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -852,16 +859,13 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
onHeapTotal += memtable.getAllocator().onHeap().owns();
|
||||
offHeapTotal += memtable.getAllocator().offHeap().owns();
|
||||
|
||||
for (SecondaryIndex index : indexManager.getIndexes())
|
||||
for (ColumnFamilyStore indexCfs : indexManager.getAllIndexColumnFamilyStores())
|
||||
{
|
||||
if (index.getIndexCfs() != null)
|
||||
{
|
||||
MemtableAllocator allocator = index.getIndexCfs().getTracker().getView().getCurrentMemtable().getAllocator();
|
||||
onHeapRatio += allocator.onHeap().ownershipRatio();
|
||||
offHeapRatio += allocator.offHeap().ownershipRatio();
|
||||
onHeapTotal += allocator.onHeap().owns();
|
||||
offHeapTotal += allocator.offHeap().owns();
|
||||
}
|
||||
MemtableAllocator allocator = indexCfs.getTracker().getView().getCurrentMemtable().getAllocator();
|
||||
onHeapRatio += allocator.onHeap().ownershipRatio();
|
||||
offHeapRatio += allocator.offHeap().ownershipRatio();
|
||||
onHeapTotal += allocator.onHeap().owns();
|
||||
offHeapTotal += allocator.offHeap().owns();
|
||||
}
|
||||
|
||||
logger.info("Enqueuing flush of {}: {}", name, String.format("%d (%.0f%%) on-heap, %d (%.0f%%) off-heap",
|
||||
|
|
@ -949,14 +953,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
*/
|
||||
|
||||
if (flushSecondaryIndexes)
|
||||
{
|
||||
for (SecondaryIndex index : indexManager.getIndexesNotBackedByCfs())
|
||||
{
|
||||
// flush any non-cfs backed indexes
|
||||
logger.info("Flushing SecondaryIndex {}", index);
|
||||
index.forceBlockingFlush();
|
||||
}
|
||||
}
|
||||
indexManager.flushAllCustomIndexesBlocking();
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -1127,14 +1124,11 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
onHeap += current.getAllocator().onHeap().ownershipRatio();
|
||||
offHeap += current.getAllocator().offHeap().ownershipRatio();
|
||||
|
||||
for (SecondaryIndex index : cfs.indexManager.getIndexes())
|
||||
for (ColumnFamilyStore indexCfs : cfs.indexManager.getAllIndexColumnFamilyStores())
|
||||
{
|
||||
if (index.getIndexCfs() != null)
|
||||
{
|
||||
MemtableAllocator allocator = index.getIndexCfs().getTracker().getView().getCurrentMemtable().getAllocator();
|
||||
onHeap += allocator.onHeap().ownershipRatio();
|
||||
offHeap += allocator.offHeap().ownershipRatio();
|
||||
}
|
||||
MemtableAllocator allocator = indexCfs.getTracker().getView().getCurrentMemtable().getAllocator();
|
||||
onHeap += allocator.onHeap().ownershipRatio();
|
||||
offHeap += allocator.offHeap().ownershipRatio();
|
||||
}
|
||||
|
||||
float ratio = Math.max(onHeap, offHeap);
|
||||
|
|
@ -1185,7 +1179,8 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
* param @ key - key for update/insert
|
||||
* param @ columnFamily - columnFamily changes
|
||||
*/
|
||||
public void apply(PartitionUpdate update, SecondaryIndexManager.Updater indexer, OpOrder.Group opGroup, ReplayPosition replayPosition)
|
||||
public void apply(PartitionUpdate update, UpdateTransaction indexer, OpOrder.Group opGroup, ReplayPosition replayPosition)
|
||||
|
||||
{
|
||||
long start = System.nanoTime();
|
||||
Memtable mt = data.getMemtableFor(opGroup, replayPosition);
|
||||
|
|
@ -1390,22 +1385,25 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
if (!isIndex())
|
||||
return false;
|
||||
|
||||
SecondaryIndex index = null;
|
||||
boolean validIndex = false;
|
||||
ColumnFamilyStore parentCfs = null;
|
||||
String indexName = null;
|
||||
if (metadata.cfName.contains(Directories.SECONDARY_INDEX_NAME_SEPARATOR))
|
||||
{
|
||||
String[] parts = metadata.cfName.split("\\" + Directories.SECONDARY_INDEX_NAME_SEPARATOR, 2);
|
||||
ColumnFamilyStore parentCfs = keyspace.getColumnFamilyStore(parts[0]);
|
||||
index = parentCfs.indexManager.getIndexByName(metadata.cfName);
|
||||
assert index != null;
|
||||
parentCfs = keyspace.getColumnFamilyStore(parts[0]);
|
||||
assert parentCfs.indexManager.getAllIndexColumnFamilyStores().contains(this);
|
||||
validIndex = true;
|
||||
indexName = this.name;
|
||||
}
|
||||
|
||||
if (index == null)
|
||||
if (! validIndex)
|
||||
return false;
|
||||
|
||||
truncateBlocking();
|
||||
|
||||
logger.warn("Rebuilding index for {} because of <{}>", name, failure.getMessage());
|
||||
index.getBaseCfs().rebuildSecondaryIndex(index.getIndexName());
|
||||
parentCfs.rebuildSecondaryIndex(indexName);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1946,8 +1944,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
|
||||
ReplayPosition replayAfter = discardSSTables(truncatedAt);
|
||||
|
||||
for (SecondaryIndex index : indexManager.getIndexes())
|
||||
index.truncateBlocking(truncatedAt);
|
||||
indexManager.truncateAllIndexesBlocking(truncatedAt);
|
||||
|
||||
materializedViewManager.truncateBlocking(truncatedAt);
|
||||
|
||||
|
|
@ -2192,12 +2189,12 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
{
|
||||
// we return the main CFS first, which we rely on for simplicity in switchMemtable(), for getting the
|
||||
// latest replay position
|
||||
return Iterables.concat(Collections.singleton(this), indexManager.getIndexesBackedByCfs());
|
||||
return Iterables.concat(Collections.singleton(this), indexManager.getAllIndexColumnFamilyStores());
|
||||
}
|
||||
|
||||
public List<String> getBuiltIndexes()
|
||||
{
|
||||
return indexManager.getBuiltIndexes();
|
||||
return indexManager.getBuiltIndexNames();
|
||||
}
|
||||
|
||||
public int getUnleveledSSTables()
|
||||
|
|
|
|||
|
|
@ -33,26 +33,26 @@ import org.slf4j.LoggerFactory;
|
|||
import org.apache.cassandra.concurrent.Stage;
|
||||
import org.apache.cassandra.concurrent.StageManager;
|
||||
import org.apache.cassandra.config.*;
|
||||
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
|
||||
import org.apache.cassandra.db.commitlog.CommitLog;
|
||||
import org.apache.cassandra.db.commitlog.ReplayPosition;
|
||||
import org.apache.cassandra.db.compaction.CompactionManager;
|
||||
import org.apache.cassandra.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.db.lifecycle.SSTableSet;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
|
||||
import org.apache.cassandra.db.view.MaterializedViewManager;
|
||||
import org.apache.cassandra.exceptions.WriteTimeoutException;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.db.lifecycle.SSTableSet;
|
||||
import org.apache.cassandra.index.Index;
|
||||
import org.apache.cassandra.index.transactions.UpdateTransaction;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||
import org.apache.cassandra.locator.AbstractReplicationStrategy;
|
||||
import org.apache.cassandra.metrics.KeyspaceMetrics;
|
||||
import org.apache.cassandra.schema.KeyspaceMetadata;
|
||||
import org.apache.cassandra.service.StorageService;
|
||||
import org.apache.cassandra.tracing.Tracing;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.JVMStabilityInspector;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.metrics.KeyspaceMetrics;
|
||||
|
||||
/**
|
||||
* It represents a Keyspace.
|
||||
|
|
@ -469,10 +469,10 @@ public class Keyspace
|
|||
}
|
||||
|
||||
Tracing.trace("Adding to {} memtable", upd.metadata().cfName);
|
||||
SecondaryIndexManager.Updater updater = updateIndexes
|
||||
? cfs.indexManager.updaterFor(upd, opGroup, nowInSec)
|
||||
: SecondaryIndexManager.nullUpdater;
|
||||
cfs.apply(upd, updater, opGroup, replayPosition);
|
||||
UpdateTransaction indexTransaction = updateIndexes
|
||||
? cfs.indexManager.newUpdateTransaction(upd, opGroup, nowInSec)
|
||||
: UpdateTransaction.NO_OP;
|
||||
cfs.apply(upd, indexTransaction, opGroup, replayPosition);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
|
@ -490,15 +490,16 @@ public class Keyspace
|
|||
/**
|
||||
* @param key row to index
|
||||
* @param cfs ColumnFamily to index partition in
|
||||
* @param idxNames columns to index, in comparator order
|
||||
* @param indexes the indexes to submit the row to
|
||||
*/
|
||||
public static void indexPartition(DecoratedKey key, ColumnFamilyStore cfs, Set<String> idxNames)
|
||||
public static void indexPartition(DecoratedKey key, ColumnFamilyStore cfs, Set<Index> indexes)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Indexing partition {} ", cfs.metadata.getKeyValidator().getString(key.getKey()));
|
||||
|
||||
Set<SecondaryIndex> indexes = cfs.indexManager.getIndexesByNames(idxNames);
|
||||
SinglePartitionReadCommand cmd = SinglePartitionReadCommand.fullPartitionRead(cfs.metadata, FBUtilities.nowInSeconds(), key);
|
||||
SinglePartitionReadCommand cmd = SinglePartitionReadCommand.fullPartitionRead(cfs.metadata,
|
||||
FBUtilities.nowInSeconds(),
|
||||
key);
|
||||
|
||||
try (OpOrder.Group opGroup = cfs.keyspace.writeOrder.start();
|
||||
UnfilteredRowIterator partition = cmd.queryMemtableAndDisk(cfs, opGroup))
|
||||
|
|
@ -515,7 +516,9 @@ public class Keyspace
|
|||
return futures;
|
||||
}
|
||||
|
||||
public Iterable<ColumnFamilyStore> getValidColumnFamilies(boolean allowIndexes, boolean autoAddIndexes, String... cfNames) throws IOException
|
||||
public Iterable<ColumnFamilyStore> getValidColumnFamilies(boolean allowIndexes,
|
||||
boolean autoAddIndexes,
|
||||
String... cfNames) throws IOException
|
||||
{
|
||||
Set<ColumnFamilyStore> valid = new HashSet<>();
|
||||
|
||||
|
|
@ -526,65 +529,58 @@ public class Keyspace
|
|||
{
|
||||
valid.add(cfStore);
|
||||
if (autoAddIndexes)
|
||||
{
|
||||
for (SecondaryIndex si : cfStore.indexManager.getIndexes())
|
||||
{
|
||||
if (si.getIndexCfs() != null) {
|
||||
logger.info("adding secondary index {} to operation", si.getIndexName());
|
||||
valid.add(si.getIndexCfs());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
valid.addAll(getIndexColumnFamilyStores(cfStore));
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
// filter out interesting stores
|
||||
|
||||
// include the specified stores and possibly the stores of any of their indexes
|
||||
for (String cfName : cfNames)
|
||||
{
|
||||
//if the CF name is an index, just flush the CF that owns the index
|
||||
String baseCfName = cfName;
|
||||
String idxName = null;
|
||||
if (cfName.contains(".")) // secondary index
|
||||
int separatorPos = cfName.indexOf(Directories.SECONDARY_INDEX_NAME_SEPARATOR);
|
||||
if (separatorPos > -1)
|
||||
{
|
||||
if(!allowIndexes)
|
||||
if (!allowIndexes)
|
||||
{
|
||||
logger.warn("Operation not allowed on secondary Index table ({})", cfName);
|
||||
continue;
|
||||
}
|
||||
String baseName = cfName.substring(0, separatorPos);
|
||||
String indexName = cfName.substring(separatorPos + Directories.SECONDARY_INDEX_NAME_SEPARATOR.length());
|
||||
|
||||
String[] parts = cfName.split("\\.", 2);
|
||||
baseCfName = parts[0];
|
||||
idxName = parts[1];
|
||||
}
|
||||
ColumnFamilyStore baseCfs = getColumnFamilyStore(baseName);
|
||||
Index index = baseCfs.indexManager.getIndexByName(indexName);
|
||||
if (index == null)
|
||||
throw new IllegalArgumentException(String.format("Invalid index specified: %s/%s.",
|
||||
baseCfs.metadata.cfName,
|
||||
indexName));
|
||||
|
||||
ColumnFamilyStore cfStore = getColumnFamilyStore(baseCfName);
|
||||
if (idxName != null)
|
||||
{
|
||||
Collection< SecondaryIndex > indexes = cfStore.indexManager.getIndexesByNames(new HashSet<>(Arrays.asList(cfName)));
|
||||
if (indexes.isEmpty())
|
||||
throw new IllegalArgumentException(String.format("Invalid index specified: %s/%s.", baseCfName, idxName));
|
||||
else
|
||||
valid.add(Iterables.get(indexes, 0).getIndexCfs());
|
||||
if (index.getBackingTable().isPresent())
|
||||
valid.add(index.getBackingTable().get());
|
||||
}
|
||||
else
|
||||
{
|
||||
ColumnFamilyStore cfStore = getColumnFamilyStore(cfName);
|
||||
valid.add(cfStore);
|
||||
if(autoAddIndexes)
|
||||
{
|
||||
for(SecondaryIndex si : cfStore.indexManager.getIndexes())
|
||||
{
|
||||
if (si.getIndexCfs() != null) {
|
||||
logger.info("adding secondary index {} to operation", si.getIndexName());
|
||||
valid.add(si.getIndexCfs());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (autoAddIndexes)
|
||||
valid.addAll(getIndexColumnFamilyStores(cfStore));
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
private Set<ColumnFamilyStore> getIndexColumnFamilyStores(ColumnFamilyStore baseCfs)
|
||||
{
|
||||
Set<ColumnFamilyStore> stores = new HashSet<>();
|
||||
for (ColumnFamilyStore indexCfs : baseCfs.indexManager.getAllIndexColumnFamilyStores())
|
||||
{
|
||||
logger.info("adding secondary index table {} to operation", indexCfs.metadata.cfName);
|
||||
stores.add(indexCfs);
|
||||
}
|
||||
return stores;
|
||||
}
|
||||
|
||||
public static Iterable<Keyspace> all()
|
||||
{
|
||||
return Iterables.transform(Schema.instance.getKeyspaces(), keyspaceTransformer);
|
||||
|
|
|
|||
|
|
@ -19,40 +19,42 @@ package org.apache.cassandra.db;
|
|||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentNavigableMap;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.cassandra.db.compaction.OperationType;
|
||||
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
|
||||
import org.apache.cassandra.io.sstable.SSTableTxnWriter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.commitlog.CommitLog;
|
||||
import org.apache.cassandra.db.commitlog.ReplayPosition;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.dht.Murmur3Partitioner.LongToken;
|
||||
import org.apache.cassandra.db.filter.*;
|
||||
import org.apache.cassandra.db.compaction.OperationType;
|
||||
import org.apache.cassandra.db.filter.ClusteringIndexFilter;
|
||||
import org.apache.cassandra.db.filter.ColumnFilter;
|
||||
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.rows.EncodingStats;
|
||||
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
|
||||
import org.apache.cassandra.dht.*;
|
||||
import org.apache.cassandra.dht.Murmur3Partitioner.LongToken;
|
||||
import org.apache.cassandra.index.transactions.UpdateTransaction;
|
||||
import org.apache.cassandra.io.sstable.Descriptor;
|
||||
import org.apache.cassandra.io.sstable.SSTableTxnWriter;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
|
||||
import org.apache.cassandra.io.util.DiskAwareRunnable;
|
||||
import org.apache.cassandra.service.ActiveRepairService;
|
||||
import org.apache.cassandra.utils.*;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.utils.memory.*;
|
||||
import org.apache.cassandra.utils.memory.MemtableAllocator;
|
||||
import org.apache.cassandra.utils.memory.MemtablePool;
|
||||
|
||||
public class Memtable implements Comparable<Memtable>
|
||||
{
|
||||
|
|
@ -208,7 +210,7 @@ public class Memtable implements Comparable<Memtable>
|
|||
*
|
||||
* replayPosition should only be null if this is a secondary index, in which case it is *expected* to be null
|
||||
*/
|
||||
long put(PartitionUpdate update, SecondaryIndexManager.Updater indexer, OpOrder.Group opGroup)
|
||||
long put(PartitionUpdate update, UpdateTransaction indexer, OpOrder.Group opGroup)
|
||||
{
|
||||
AtomicBTreePartition previous = partitions.get(update.partitionKey());
|
||||
|
||||
|
|
|
|||
|
|
@ -25,21 +25,22 @@ import com.google.common.collect.Iterables;
|
|||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.db.filter.*;
|
||||
import org.apache.cassandra.db.lifecycle.SSTableSet;
|
||||
import org.apache.cassandra.db.lifecycle.View;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.filter.*;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexSearcher;
|
||||
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
|
||||
import org.apache.cassandra.dht.AbstractBounds;
|
||||
import org.apache.cassandra.exceptions.RequestExecutionException;
|
||||
import org.apache.cassandra.index.Index;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||
import org.apache.cassandra.metrics.TableMetrics;
|
||||
import org.apache.cassandra.net.MessageOut;
|
||||
import org.apache.cassandra.net.MessagingService;
|
||||
import org.apache.cassandra.service.*;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.service.StorageProxy;
|
||||
import org.apache.cassandra.service.pager.*;
|
||||
import org.apache.cassandra.thrift.ThriftResultsMerger;
|
||||
import org.apache.cassandra.tracing.Tracing;
|
||||
|
|
@ -273,8 +274,8 @@ public class PartitionRangeReadCommand extends ReadCommand
|
|||
public PartitionIterator postReconciliationProcessing(PartitionIterator result)
|
||||
{
|
||||
ColumnFamilyStore cfs = Keyspace.open(metadata().ksName).getColumnFamilyStore(metadata().cfName);
|
||||
SecondaryIndexSearcher searcher = getIndexSearcher(cfs);
|
||||
return searcher == null ? result : searcher.postReconciliationProcessing(rowFilter(), result);
|
||||
Index index = getIndex(cfs, false);
|
||||
return index == null ? result : index.postProcessorFor(this).apply(result, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -22,20 +22,16 @@ import java.nio.ByteBuffer;
|
|||
import java.util.*;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.config.Schema;
|
||||
import org.apache.cassandra.config.*;
|
||||
import org.apache.cassandra.cql3.Operator;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexSearcher;
|
||||
import org.apache.cassandra.db.filter.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.dht.AbstractBounds;
|
||||
import org.apache.cassandra.index.Index;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
|
|
@ -43,7 +39,6 @@ import org.apache.cassandra.metrics.TableMetrics;
|
|||
import org.apache.cassandra.net.MessageOut;
|
||||
import org.apache.cassandra.net.MessagingService;
|
||||
import org.apache.cassandra.service.ClientWarn;
|
||||
import org.apache.cassandra.service.StorageService;
|
||||
import org.apache.cassandra.tracing.Tracing;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
|
@ -261,14 +256,16 @@ public abstract class ReadCommand implements ReadQuery
|
|||
: ReadResponse.createDataResponse(iterator, selection);
|
||||
}
|
||||
|
||||
protected SecondaryIndexSearcher getIndexSearcher(ColumnFamilyStore cfs)
|
||||
protected Index getIndex(ColumnFamilyStore cfs, boolean includeInTrace)
|
||||
{
|
||||
return cfs.indexManager.getBestIndexSearcherFor(this);
|
||||
return cfs.indexManager.getBestIndexFor(this, includeInTrace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes this command on the local host.
|
||||
*
|
||||
* @param orderGroup the operation group spanning this command
|
||||
*
|
||||
* @return an iterator over the result of executing this command locally.
|
||||
*/
|
||||
@SuppressWarnings("resource") // The result iterator is closed upon exceptions (we know it's fine to potentially not close the intermediary
|
||||
|
|
@ -278,10 +275,12 @@ public abstract class ReadCommand implements ReadQuery
|
|||
long startTimeNanos = System.nanoTime();
|
||||
|
||||
ColumnFamilyStore cfs = Keyspace.openAndGetStore(metadata());
|
||||
SecondaryIndexSearcher searcher = getIndexSearcher(cfs);
|
||||
Index index = getIndex(cfs, true);
|
||||
Index.Searcher searcher = index == null ? null : index.searcherFor(this);
|
||||
|
||||
UnfilteredPartitionIterator resultIterator = searcher == null
|
||||
? queryStorage(cfs, orderGroup)
|
||||
: searcher.search(this, orderGroup);
|
||||
: searcher.search(orderGroup);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -291,7 +290,7 @@ public abstract class ReadCommand implements ReadQuery
|
|||
// no point in checking it again.
|
||||
RowFilter updatedFilter = searcher == null
|
||||
? rowFilter()
|
||||
: rowFilter().without(searcher.primaryClause(this));
|
||||
: index.getPostIndexQueryFilter(rowFilter());
|
||||
|
||||
// TODO: We'll currently do filtering by the rowFilter here because it's convenient. However,
|
||||
// we'll probably want to optimize by pushing it down the layer (like for dropped columns) as it
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
package org.apache.cassandra.db;
|
||||
|
||||
import org.apache.cassandra.db.index.*;
|
||||
import org.apache.cassandra.index.Index;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
|
||||
public class ReadOrderGroup implements AutoCloseable
|
||||
|
|
@ -98,14 +98,8 @@ public class ReadOrderGroup implements AutoCloseable
|
|||
|
||||
private static ColumnFamilyStore maybeGetIndexCfs(ColumnFamilyStore baseCfs, ReadCommand command)
|
||||
{
|
||||
SecondaryIndexSearcher searcher = command.getIndexSearcher(baseCfs);
|
||||
if (searcher == null)
|
||||
return null;
|
||||
|
||||
SecondaryIndex index = searcher.highestSelectivityIndex(command.rowFilter());
|
||||
return index == null || !(index instanceof AbstractSimplePerColumnSecondaryIndex)
|
||||
? null
|
||||
: ((AbstractSimplePerColumnSecondaryIndex)index).getIndexCfs();
|
||||
Index index = baseCfs.indexManager.getBestIndexFor(command);
|
||||
return index == null ? null : index.getBackingTable().orElse(null);
|
||||
}
|
||||
|
||||
public void close()
|
||||
|
|
|
|||
|
|
@ -17,18 +17,22 @@
|
|||
*/
|
||||
package org.apache.cassandra.db;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.File;
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
import javax.management.openmbean.OpenDataException;
|
||||
import javax.management.openmbean.TabularData;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -37,25 +41,18 @@ import org.apache.cassandra.config.DatabaseDescriptor;
|
|||
import org.apache.cassandra.cql3.QueryProcessor;
|
||||
import org.apache.cassandra.cql3.UntypedResultSet;
|
||||
import org.apache.cassandra.cql3.functions.*;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.commitlog.ReplayPosition;
|
||||
import org.apache.cassandra.db.compaction.CompactionHistoryTabularData;
|
||||
import org.apache.cassandra.db.marshal.*;
|
||||
import org.apache.cassandra.dht.IPartitioner;
|
||||
import org.apache.cassandra.dht.LocalPartitioner;
|
||||
import org.apache.cassandra.dht.Range;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.dht.*;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.io.sstable.Descriptor;
|
||||
import org.apache.cassandra.io.util.DataInputBuffer;
|
||||
import org.apache.cassandra.io.util.DataOutputBuffer;
|
||||
import org.apache.cassandra.io.util.FileUtils;
|
||||
import org.apache.cassandra.io.util.NIODataInputStream;
|
||||
import org.apache.cassandra.io.util.*;
|
||||
import org.apache.cassandra.locator.IEndpointSnitch;
|
||||
import org.apache.cassandra.metrics.RestorableMeter;
|
||||
import org.apache.cassandra.net.MessagingService;
|
||||
import org.apache.cassandra.schema.*;
|
||||
import org.apache.cassandra.schema.Tables;
|
||||
import org.apache.cassandra.service.StorageService;
|
||||
import org.apache.cassandra.service.paxos.Commit;
|
||||
import org.apache.cassandra.service.paxos.PaxosState;
|
||||
|
|
@ -65,7 +62,6 @@ import org.apache.cassandra.utils.*;
|
|||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
||||
import static org.apache.cassandra.cql3.QueryProcessor.executeInternal;
|
||||
import static org.apache.cassandra.cql3.QueryProcessor.executeOnceInternal;
|
||||
|
||||
|
|
@ -516,7 +512,14 @@ public final class SystemKeyspace
|
|||
if (ksname.equals("system") && cfname.equals(COMPACTION_HISTORY))
|
||||
return;
|
||||
String req = "INSERT INTO system.%s (id, keyspace_name, columnfamily_name, compacted_at, bytes_in, bytes_out, rows_merged) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
executeInternal(String.format(req, COMPACTION_HISTORY), UUIDGen.getTimeUUID(), ksname, cfname, ByteBufferUtil.bytes(compactedAt), bytesIn, bytesOut, rowsMerged);
|
||||
executeInternal(String.format(req, COMPACTION_HISTORY),
|
||||
UUIDGen.getTimeUUID(),
|
||||
ksname,
|
||||
cfname,
|
||||
ByteBufferUtil.bytes(compactedAt),
|
||||
bytesIn,
|
||||
bytesOut,
|
||||
rowsMerged);
|
||||
}
|
||||
|
||||
public static TabularData getCompactionHistory() throws OpenDataException
|
||||
|
|
@ -1030,6 +1033,16 @@ public final class SystemKeyspace
|
|||
forceBlockingFlush(BUILT_INDEXES);
|
||||
}
|
||||
|
||||
public static List<String> getBuiltIndexes(String keyspaceName, Set<String> indexNames)
|
||||
{
|
||||
List<String> names = new ArrayList<>(indexNames);
|
||||
String req = "SELECT index_name from %s.\"%s\" WHERE table_name=? AND index_name IN ?";
|
||||
UntypedResultSet results = executeInternal(String.format(req, NAME, BUILT_INDEXES), keyspaceName, names);
|
||||
return StreamSupport.stream(results.spliterator(), false)
|
||||
.map(r -> r.getString("index_name"))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the host ID from the system keyspace, creating (and storing) one if
|
||||
* none exists.
|
||||
|
|
|
|||
|
|
@ -17,15 +17,19 @@
|
|||
*/
|
||||
package org.apache.cassandra.db.compaction;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.partitions.PurgingPartitionIterator;
|
||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.index.transactions.CompactionTransaction;
|
||||
import org.apache.cassandra.io.sstable.ISSTableScanner;
|
||||
import org.apache.cassandra.metrics.CompactionMetrics;
|
||||
|
||||
|
|
@ -47,6 +51,7 @@ import org.apache.cassandra.metrics.CompactionMetrics;
|
|||
*/
|
||||
public class CompactionIterator extends CompactionInfo.Holder implements UnfilteredPartitionIterator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(CompactionIterator.class);
|
||||
private static final long UNFILTERED_TO_UPDATE_PROGRESS = 100;
|
||||
|
||||
private final OperationType type;
|
||||
|
|
@ -148,29 +153,33 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
|
|||
if (type != OperationType.COMPACTION || !controller.cfs.indexManager.hasIndexes())
|
||||
return null;
|
||||
|
||||
// If we have a 2ndary index, we must update it with deleted/shadowed cells.
|
||||
// TODO: this should probably be done asynchronously and batched.
|
||||
final SecondaryIndexManager.Updater indexer = controller.cfs.indexManager.gcUpdaterFor(partitionKey, nowInSec);
|
||||
final RowDiffListener diffListener = new RowDiffListener()
|
||||
Columns statics = Columns.NONE;
|
||||
Columns regulars = Columns.NONE;
|
||||
for (UnfilteredRowIterator iter : versions)
|
||||
{
|
||||
public void onPrimaryKeyLivenessInfo(int i, Clustering clustering, LivenessInfo merged, LivenessInfo original)
|
||||
if (iter != null)
|
||||
{
|
||||
statics = statics.mergeTo(iter.columns().statics);
|
||||
regulars = regulars.mergeTo(iter.columns().regulars);
|
||||
}
|
||||
}
|
||||
final PartitionColumns partitionColumns = new PartitionColumns(statics, regulars);
|
||||
|
||||
public void onDeletion(int i, Clustering clustering, DeletionTime merged, DeletionTime original)
|
||||
{
|
||||
}
|
||||
|
||||
public void onComplexDeletion(int i, Clustering clustering, ColumnDefinition column, DeletionTime merged, DeletionTime original)
|
||||
{
|
||||
}
|
||||
|
||||
public void onCell(int i, Clustering clustering, Cell merged, Cell original)
|
||||
{
|
||||
if (original != null && (merged == null || !merged.isLive(nowInSec)))
|
||||
indexer.remove(clustering, original);
|
||||
}
|
||||
};
|
||||
// If we have a 2ndary index, we must update it with deleted/shadowed cells.
|
||||
// we can reuse a single CleanupTransaction for the duration of a partition.
|
||||
// Currently, it doesn't do any batching of row updates, so every merge event
|
||||
// for a single partition results in a fresh cycle of:
|
||||
// * Get new Indexer instances
|
||||
// * Indexer::start
|
||||
// * Indexer::onRowMerge (for every row being merged by the compaction)
|
||||
// * Indexer::commit
|
||||
// A new OpOrder.Group is opened in an ARM block wrapping the commits
|
||||
// TODO: this should probably be done asynchronously and batched.
|
||||
final CompactionTransaction indexTransaction =
|
||||
controller.cfs.indexManager.newCompactionTransaction(partitionKey,
|
||||
partitionColumns,
|
||||
versions.size(),
|
||||
nowInSec);
|
||||
|
||||
return new UnfilteredRowIterators.MergeListener()
|
||||
{
|
||||
|
|
@ -180,7 +189,9 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
|
|||
|
||||
public void onMergedRows(Row merged, Columns columns, Row[] versions)
|
||||
{
|
||||
Rows.diff(merged, columns, versions, diffListener);
|
||||
indexTransaction.start();
|
||||
indexTransaction.onRowMerge(columns, merged, versions);
|
||||
indexTransaction.commit();
|
||||
}
|
||||
|
||||
public void onMergedRangeTombstoneMarkers(RangeTombstoneMarker mergedMarker, RangeTombstoneMarker[] versions)
|
||||
|
|
|
|||
|
|
@ -20,22 +20,8 @@ package org.apache.cassandra.db.compaction;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.ObjectName;
|
||||
import javax.management.openmbean.OpenDataException;
|
||||
|
|
@ -56,33 +42,27 @@ import org.apache.cassandra.config.DatabaseDescriptor;
|
|||
import org.apache.cassandra.config.Schema;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.compaction.CompactionInfo.Holder;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexBuilder;
|
||||
import org.apache.cassandra.db.view.MaterializedViewBuilder;
|
||||
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
|
||||
import org.apache.cassandra.db.lifecycle.SSTableSet;
|
||||
import org.apache.cassandra.db.lifecycle.View;
|
||||
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
|
||||
import org.apache.cassandra.db.view.MaterializedViewBuilder;
|
||||
import org.apache.cassandra.dht.Bounds;
|
||||
import org.apache.cassandra.dht.Range;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.io.sstable.*;
|
||||
import org.apache.cassandra.index.SecondaryIndexBuilder;
|
||||
import org.apache.cassandra.io.sstable.Descriptor;
|
||||
import org.apache.cassandra.io.sstable.ISSTableScanner;
|
||||
import org.apache.cassandra.io.sstable.SSTableRewriter;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableWriter;
|
||||
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
|
||||
import org.apache.cassandra.io.sstable.ISSTableScanner;
|
||||
import org.apache.cassandra.io.sstable.SSTableRewriter;
|
||||
import org.apache.cassandra.io.util.FileUtils;
|
||||
import org.apache.cassandra.metrics.CompactionMetrics;
|
||||
import org.apache.cassandra.repair.Validator;
|
||||
import org.apache.cassandra.service.ActiveRepairService;
|
||||
import org.apache.cassandra.service.StorageService;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.JVMStabilityInspector;
|
||||
import org.apache.cassandra.utils.MerkleTrees;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
import org.apache.cassandra.utils.WrappedRunnable;
|
||||
import org.apache.cassandra.utils.UUIDGen;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.utils.*;
|
||||
import org.apache.cassandra.utils.concurrent.Refs;
|
||||
|
||||
import static java.util.Collections.singleton;
|
||||
|
|
@ -847,7 +827,7 @@ public class CompactionManager implements CompactionManagerMBean
|
|||
}
|
||||
|
||||
// flush to ensure we don't lose the tombstones on a restart, since they are not commitlog'd
|
||||
cfs.indexManager.flushIndexesBlocking();
|
||||
cfs.indexManager.flushAllIndexesBlocking();
|
||||
|
||||
finished = writer.finish();
|
||||
}
|
||||
|
|
@ -939,11 +919,7 @@ public class CompactionManager implements CompactionManagerMBean
|
|||
|
||||
cfs.invalidateCachedPartition(partition.partitionKey());
|
||||
|
||||
// acquire memtable lock here because secondary index deletion may cause a race. See CASSANDRA-3712
|
||||
try (OpOrder.Group opGroup = cfs.keyspace.writeOrder.start())
|
||||
{
|
||||
cfs.indexManager.deleteFromIndexes(partition, opGroup, nowInSec);
|
||||
}
|
||||
cfs.indexManager.deletePartition(partition, nowInSec);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,10 +27,9 @@ import org.apache.cassandra.config.CFMetaData;
|
|||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.cql3.Operator;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.marshal.*;
|
||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
|
|
@ -38,7 +37,9 @@ import org.apache.cassandra.net.MessagingService;
|
|||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
import static org.apache.cassandra.cql3.statements.RequestValidations.*;
|
||||
import static org.apache.cassandra.cql3.statements.RequestValidations.checkBindValueSet;
|
||||
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
|
||||
import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
|
||||
|
||||
/**
|
||||
* A filter on which rows a given query should include or exclude.
|
||||
|
|
@ -91,6 +92,11 @@ public abstract class RowFilter implements Iterable<RowFilter.Expression>
|
|||
expressions.add(new ThriftExpression(metadata, name, op, value));
|
||||
}
|
||||
|
||||
public List<Expression> getExpressions()
|
||||
{
|
||||
return expressions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the provided iterator so that only the row satisfying the expression of this filter
|
||||
* are included in the resulting iterator.
|
||||
|
|
|
|||
|
|
@ -1,251 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.compaction.CompactionManager;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
|
||||
/**
|
||||
* Implements a secondary index for a column family using a second column family
|
||||
* in which the row keys are indexed values, and column names are base row keys.
|
||||
*/
|
||||
public abstract class AbstractSimplePerColumnSecondaryIndex extends PerColumnSecondaryIndex
|
||||
{
|
||||
protected ColumnFamilyStore indexCfs;
|
||||
|
||||
// SecondaryIndex "forces" a set of ColumnDefinition. However this class (and thus it's subclass)
|
||||
// only support one def per index. So inline it in a field for 1) convenience and 2) avoid creating
|
||||
// an iterator each time we need to access it.
|
||||
// TODO: we should fix SecondaryIndex API
|
||||
protected ColumnDefinition columnDef;
|
||||
|
||||
public void init()
|
||||
{
|
||||
assert baseCfs != null && columnDefs != null && columnDefs.size() == 1;
|
||||
|
||||
columnDef = columnDefs.iterator().next();
|
||||
|
||||
CFMetaData indexedCfMetadata = SecondaryIndex.newIndexMetadata(baseCfs.metadata, indexMetadata, getIndexKeyComparator());
|
||||
indexCfs = ColumnFamilyStore.createColumnFamilyStore(baseCfs.keyspace,
|
||||
indexedCfMetadata.cfName,
|
||||
indexedCfMetadata,
|
||||
baseCfs.getTracker().loadsstables);
|
||||
}
|
||||
|
||||
protected AbstractType<?> getIndexKeyComparator()
|
||||
{
|
||||
return columnDef.type;
|
||||
}
|
||||
|
||||
public ColumnDefinition indexedColumn()
|
||||
{
|
||||
return columnDef;
|
||||
}
|
||||
|
||||
@Override
|
||||
String indexTypeForGrouping()
|
||||
{
|
||||
return "_internal_";
|
||||
}
|
||||
|
||||
protected Clustering makeIndexClustering(ByteBuffer rowKey, Clustering clustering, Cell cell)
|
||||
{
|
||||
return makeIndexClustering(rowKey, clustering, cell == null ? null : cell.path());
|
||||
}
|
||||
|
||||
protected Clustering makeIndexClustering(ByteBuffer rowKey, Clustering clustering, CellPath path)
|
||||
{
|
||||
return buildIndexClusteringPrefix(rowKey, clustering, path).build();
|
||||
}
|
||||
|
||||
protected Slice.Bound makeIndexBound(ByteBuffer rowKey, Slice.Bound bound)
|
||||
{
|
||||
return buildIndexClusteringPrefix(rowKey, bound, null).buildBound(bound.isStart(), bound.isInclusive());
|
||||
}
|
||||
|
||||
protected abstract CBuilder buildIndexClusteringPrefix(ByteBuffer rowKey, ClusteringPrefix prefix, CellPath path);
|
||||
|
||||
protected ByteBuffer getIndexedValue(ByteBuffer rowKey, Clustering clustering, Cell cell)
|
||||
{
|
||||
return cell == null
|
||||
? getIndexedValue(rowKey, clustering, null, null)
|
||||
: getIndexedValue(rowKey, clustering, cell.value(), cell.path());
|
||||
}
|
||||
|
||||
protected abstract ByteBuffer getIndexedValue(ByteBuffer rowKey, Clustering clustering, ByteBuffer cellValue, CellPath cellPath);
|
||||
|
||||
public void delete(ByteBuffer rowKey, Clustering clustering, Cell cell, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
deleteForCleanup(rowKey, clustering, cell, opGroup, nowInSec);
|
||||
}
|
||||
|
||||
public void deleteForCleanup(ByteBuffer rowKey, Clustering clustering, Cell cell, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
delete(rowKey, clustering, cell.value(), cell.path(), new DeletionTime(cell.timestamp(), nowInSec), opGroup);
|
||||
}
|
||||
|
||||
public void delete(ByteBuffer rowKey, Clustering clustering, ByteBuffer cellValue, CellPath path, DeletionTime deletion, OpOrder.Group opGroup)
|
||||
{
|
||||
DecoratedKey valueKey = getIndexKeyFor(getIndexedValue(rowKey, clustering, cellValue, path));
|
||||
|
||||
Row row = BTreeRow.emptyDeletedRow(makeIndexClustering(rowKey, clustering, path), deletion);
|
||||
PartitionUpdate upd = PartitionUpdate.singleRowUpdate(indexCfs.metadata, valueKey, row);
|
||||
|
||||
indexCfs.apply(upd, SecondaryIndexManager.nullUpdater, opGroup, null);
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("removed index entry for cleaned-up value {}:{}", valueKey, upd);
|
||||
}
|
||||
|
||||
public void insert(ByteBuffer rowKey, Clustering clustering, Cell cell, OpOrder.Group opGroup)
|
||||
{
|
||||
insert(rowKey, clustering, cell, LivenessInfo.create(cell.timestamp(), cell.ttl(), cell.localDeletionTime()), opGroup);
|
||||
}
|
||||
|
||||
public void insert(ByteBuffer rowKey, Clustering clustering, Cell cell, LivenessInfo info, OpOrder.Group opGroup)
|
||||
{
|
||||
DecoratedKey valueKey = getIndexKeyFor(getIndexedValue(rowKey, clustering, cell));
|
||||
|
||||
Row row = BTreeRow.noCellLiveRow(makeIndexClustering(rowKey, clustering, cell), info);
|
||||
PartitionUpdate upd = PartitionUpdate.singleRowUpdate(indexCfs.metadata, valueKey, row);
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("applying index row {} in {}", indexCfs.metadata.getKeyValidator().getString(valueKey.getKey()), upd);
|
||||
|
||||
indexCfs.apply(upd, SecondaryIndexManager.nullUpdater, opGroup, null);
|
||||
}
|
||||
|
||||
public void update(ByteBuffer rowKey, Clustering clustering, Cell oldCell, Cell cell, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
// insert the new value before removing the old one, so we never have a period
|
||||
// where the row is invisible to both queries (the opposite seems preferable); see CASSANDRA-5540
|
||||
insert(rowKey, clustering, cell, opGroup);
|
||||
if (SecondaryIndexManager.shouldCleanupOldValue(oldCell, cell))
|
||||
delete(rowKey, clustering, oldCell, opGroup, nowInSec);
|
||||
}
|
||||
|
||||
public boolean indexes(ColumnDefinition column)
|
||||
{
|
||||
return column.name.equals(columnDef.name);
|
||||
}
|
||||
|
||||
public void removeIndex(ByteBuffer columnName)
|
||||
{
|
||||
// interrupt in-progress compactions
|
||||
Collection<ColumnFamilyStore> cfss = Collections.singleton(indexCfs);
|
||||
CompactionManager.instance.interruptCompactionForCFs(cfss, true);
|
||||
CompactionManager.instance.waitForCessation(cfss);
|
||||
|
||||
indexCfs.keyspace.writeOrder.awaitNewBarrier();
|
||||
indexCfs.forceBlockingFlush();
|
||||
|
||||
indexCfs.readOrdering.awaitNewBarrier();
|
||||
indexCfs.invalidate();
|
||||
}
|
||||
|
||||
public void forceBlockingFlush()
|
||||
{
|
||||
Future<?> wait;
|
||||
// we synchronise on the baseCfs to make sure we are ordered correctly with other flushes to the base CFS
|
||||
synchronized (baseCfs.getTracker())
|
||||
{
|
||||
wait = indexCfs.forceFlush();
|
||||
}
|
||||
FBUtilities.waitOnFuture(wait);
|
||||
}
|
||||
|
||||
public void invalidate()
|
||||
{
|
||||
indexCfs.invalidate();
|
||||
}
|
||||
|
||||
public void truncateBlocking(long truncatedAt)
|
||||
{
|
||||
indexCfs.discardSSTables(truncatedAt);
|
||||
}
|
||||
|
||||
public ColumnFamilyStore getIndexCfs()
|
||||
{
|
||||
return indexCfs;
|
||||
}
|
||||
|
||||
protected ClusteringComparator getIndexComparator()
|
||||
{
|
||||
assert indexCfs != null;
|
||||
return indexCfs.metadata.comparator;
|
||||
}
|
||||
|
||||
public String getIndexName()
|
||||
{
|
||||
return indexCfs.name;
|
||||
}
|
||||
|
||||
public void reload()
|
||||
{
|
||||
indexCfs.metadata.reloadIndexMetadataProperties(baseCfs.metadata);
|
||||
indexCfs.reload();
|
||||
}
|
||||
|
||||
public long estimateResultRows()
|
||||
{
|
||||
return getIndexCfs().getMeanColumns();
|
||||
}
|
||||
|
||||
public void validate(DecoratedKey partitionKey) throws InvalidRequestException
|
||||
{
|
||||
if (columnDef.kind == ColumnDefinition.Kind.PARTITION_KEY)
|
||||
validateIndexedValue(getIndexedValue(partitionKey.getKey(), null, null, null));
|
||||
}
|
||||
|
||||
public void validate(Clustering clustering) throws InvalidRequestException
|
||||
{
|
||||
if (columnDef.kind == ColumnDefinition.Kind.CLUSTERING)
|
||||
validateIndexedValue(getIndexedValue(null, clustering, null, null));
|
||||
}
|
||||
|
||||
public void validate(ByteBuffer cellValue, CellPath path) throws InvalidRequestException
|
||||
{
|
||||
if (!columnDef.isPrimaryKeyColumn())
|
||||
validateIndexedValue(getIndexedValue(null, null, cellValue, path));
|
||||
}
|
||||
|
||||
private void validateIndexedValue(ByteBuffer value)
|
||||
{
|
||||
if (value != null && value.remaining() >= FBUtilities.MAX_UNSIGNED_SHORT)
|
||||
throw new InvalidRequestException(String.format("Cannot index value of size %d for index %s on %s.%s(%s) (maximum allowed size=%d)",
|
||||
value.remaining(), getIndexName(), baseKeyspace(), baseTable(), columnDef.name, FBUtilities.MAX_UNSIGNED_SHORT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s(%s)", baseTable(), columnDef.name);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
|
||||
/**
|
||||
* Base class for Secondary indexes that implement a unique index per column
|
||||
*
|
||||
*/
|
||||
public abstract class PerColumnSecondaryIndex extends SecondaryIndex
|
||||
{
|
||||
/**
|
||||
* Called when a column has been tombstoned or replaced.
|
||||
*
|
||||
* @param rowKey the underlying row key which is indexed
|
||||
* @param col all the column info
|
||||
*/
|
||||
public abstract void delete(ByteBuffer rowKey, Clustering clustering, Cell cell, OpOrder.Group opGroup, int nowInSec);
|
||||
|
||||
/**
|
||||
* Called when a column has been removed due to a cleanup operation.
|
||||
*/
|
||||
public abstract void deleteForCleanup(ByteBuffer rowKey, Clustering clustering, Cell cell, OpOrder.Group opGroup, int nowInSec);
|
||||
|
||||
/**
|
||||
* For indexes on the primary key, index the given PK.
|
||||
*/
|
||||
public void maybeIndex(ByteBuffer partitionKey, Clustering clustering, long timestamp, int ttl, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* For indexes on the primary key, delete the given PK.
|
||||
*/
|
||||
public void maybeDelete(ByteBuffer partitionKey, Clustering clustering, DeletionTime deletion, OpOrder.Group opGroup)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* insert a column to the index
|
||||
*
|
||||
* @param rowKey the underlying row key which is indexed
|
||||
* @param col all the column info
|
||||
*/
|
||||
public abstract void insert(ByteBuffer rowKey, Clustering clustering, Cell cell, OpOrder.Group opGroup);
|
||||
|
||||
/**
|
||||
* update a column from the index
|
||||
*
|
||||
* @param rowKey the underlying row key which is indexed
|
||||
* @param oldCol the previous column info
|
||||
* @param col all the column info
|
||||
*/
|
||||
public abstract void update(ByteBuffer rowKey, Clustering clustering, Cell oldCell, Cell cell, OpOrder.Group opGroup, int nowInSec);
|
||||
|
||||
protected boolean indexPrimaryKeyColumn()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void indexRow(DecoratedKey key, Row row, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
Clustering clustering = row.clustering();
|
||||
if (indexPrimaryKeyColumn())
|
||||
{
|
||||
// Same as in AtomicBTreePartition.maybeIndexPrimaryKeyColumn
|
||||
long timestamp = row.primaryKeyLivenessInfo().timestamp();
|
||||
int ttl = row.primaryKeyLivenessInfo().ttl();
|
||||
|
||||
for (Cell cell : row.cells())
|
||||
{
|
||||
if (cell.isLive(nowInSec) && cell.timestamp() > timestamp)
|
||||
{
|
||||
timestamp = cell.timestamp();
|
||||
ttl = cell.ttl();
|
||||
}
|
||||
}
|
||||
maybeIndex(key.getKey(), clustering, timestamp, ttl, opGroup, nowInSec);
|
||||
}
|
||||
|
||||
for (Cell cell : row.cells())
|
||||
{
|
||||
if (!indexes(cell.column()))
|
||||
continue;
|
||||
|
||||
if (cell.isLive(nowInSec))
|
||||
insert(key.getKey(), clustering, cell, opGroup);
|
||||
}
|
||||
}
|
||||
|
||||
public String getNameForSystemKeyspace(ByteBuffer column)
|
||||
{
|
||||
return getIndexName();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
|
||||
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
/**
|
||||
* Base class for Secondary indexes that implement a unique index per row
|
||||
*/
|
||||
public abstract class PerRowSecondaryIndex extends SecondaryIndex
|
||||
{
|
||||
/**
|
||||
* Index the given partition.
|
||||
*/
|
||||
public abstract void index(ByteBuffer key, UnfilteredRowIterator atoms);
|
||||
|
||||
/**
|
||||
* cleans up deleted columns from cassandra cleanup compaction
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
public abstract void delete(ByteBuffer key, OpOrder.Group opGroup);
|
||||
|
||||
public String getNameForSystemKeyspace(ByteBuffer columnName)
|
||||
{
|
||||
try
|
||||
{
|
||||
return getIndexName()+ByteBufferUtil.string(columnName);
|
||||
}
|
||||
catch (CharacterCodingException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,429 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
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.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.compaction.CompactionManager;
|
||||
import org.apache.cassandra.db.index.composites.CompositesIndex;
|
||||
import org.apache.cassandra.db.index.keys.KeysIndex;
|
||||
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.dht.LocalPartitioner;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.io.sstable.ReducingKeyIterator;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.concurrent.Refs;
|
||||
|
||||
/**
|
||||
* Abstract base class for different types of secondary indexes.
|
||||
*
|
||||
* Do not extend this directly, please pick from PerColumnSecondaryIndex or PerRowSecondaryIndex
|
||||
*/
|
||||
public abstract class SecondaryIndex
|
||||
{
|
||||
protected static final Logger logger = LoggerFactory.getLogger(SecondaryIndex.class);
|
||||
|
||||
public static final String CUSTOM_INDEX_OPTION_NAME = "class_name";
|
||||
|
||||
/**
|
||||
* The name of the option used to specify that the index is on the collection keys.
|
||||
*/
|
||||
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";
|
||||
|
||||
/**
|
||||
* Base CF that has many indexes
|
||||
*/
|
||||
protected ColumnFamilyStore baseCfs;
|
||||
|
||||
|
||||
/**
|
||||
* The column definitions which this index is responsible for
|
||||
*/
|
||||
protected final Set<ColumnDefinition> columnDefs = Collections.newSetFromMap(new ConcurrentHashMap<ColumnDefinition,Boolean>());
|
||||
|
||||
protected IndexMetadata indexMetadata;
|
||||
|
||||
/**
|
||||
* Perform any initialization work
|
||||
*/
|
||||
public abstract void init();
|
||||
|
||||
/**
|
||||
* Reload an existing index following a change to its configuration,
|
||||
* or that of the indexed column(s). Differs from init() in that we expect
|
||||
* expect new resources (such as CFS for a KEYS index) to be created by
|
||||
* init() but not here
|
||||
*/
|
||||
public abstract void reload();
|
||||
|
||||
/**
|
||||
* Validates the index_options passed in the IndexMetadata
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public abstract void validateOptions(CFMetaData baseCfm, IndexMetadata def) throws ConfigurationException;
|
||||
|
||||
/**
|
||||
* @return The name of the index
|
||||
*/
|
||||
abstract public String getIndexName();
|
||||
|
||||
/**
|
||||
* All internal 2ndary indexes will return "_internal_" for this. Custom
|
||||
* 2ndary indexes will return their class name. This only matter for
|
||||
* SecondaryIndexManager.groupByIndexType.
|
||||
*/
|
||||
String indexTypeForGrouping()
|
||||
{
|
||||
// Our internal indexes overwrite this
|
||||
return getClass().getCanonicalName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the unique name for this index and column
|
||||
* to be stored in the SystemKeyspace that tracks if each column is built
|
||||
*
|
||||
* @param columnName the name of the column
|
||||
* @return the unique name
|
||||
*/
|
||||
abstract public String getNameForSystemKeyspace(ByteBuffer columnName);
|
||||
|
||||
/**
|
||||
* Checks if the index for specified column is fully built
|
||||
*
|
||||
* @param columnName the column
|
||||
* @return true if the index is fully built
|
||||
*/
|
||||
public boolean isIndexBuilt(ByteBuffer columnName)
|
||||
{
|
||||
return SystemKeyspace.isIndexBuilt(baseCfs.keyspace.getName(), getNameForSystemKeyspace(columnName));
|
||||
}
|
||||
|
||||
public void setIndexBuilt()
|
||||
{
|
||||
for (ColumnDefinition columnDef : columnDefs)
|
||||
SystemKeyspace.setIndexBuilt(baseCfs.keyspace.getName(), getNameForSystemKeyspace(columnDef.name.bytes));
|
||||
}
|
||||
|
||||
public void setIndexRemoved()
|
||||
{
|
||||
for (ColumnDefinition columnDef : columnDefs)
|
||||
SystemKeyspace.setIndexRemoved(baseCfs.keyspace.getName(), getNameForSystemKeyspace(columnDef.name.bytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at query time
|
||||
* Creates a implementation specific searcher instance for this index type
|
||||
* @param columns the list of columns which belong to this index type
|
||||
* @return the secondary index search impl
|
||||
*/
|
||||
protected abstract SecondaryIndexSearcher createSecondaryIndexSearcher(Set<ColumnDefinition> columns);
|
||||
|
||||
/**
|
||||
* Forces this indexes' in memory data to disk
|
||||
*/
|
||||
public abstract void forceBlockingFlush();
|
||||
|
||||
/**
|
||||
* Allow access to the underlying column family store if there is one
|
||||
* @return the underlying column family store or null
|
||||
*/
|
||||
public abstract ColumnFamilyStore getIndexCfs();
|
||||
|
||||
|
||||
/**
|
||||
* Delete all files and references to this index
|
||||
* @param columnName the indexed column to remove
|
||||
*/
|
||||
public abstract void removeIndex(ByteBuffer columnName);
|
||||
|
||||
/**
|
||||
* Remove the index and unregisters this index's mbean if one exists
|
||||
*/
|
||||
public abstract void invalidate();
|
||||
|
||||
/**
|
||||
* Truncate all the data from the current index
|
||||
*
|
||||
* @param truncatedAt The truncation timestamp, all data before that timestamp should be rejected.
|
||||
*/
|
||||
public abstract void truncateBlocking(long truncatedAt);
|
||||
|
||||
/**
|
||||
* Builds the index using the data in the underlying CFS
|
||||
* Blocks till it's complete
|
||||
*/
|
||||
protected void buildIndexBlocking()
|
||||
{
|
||||
logger.info(String.format("Submitting index build of %s for data in %s",
|
||||
getIndexName(), StringUtils.join(baseCfs.getSSTables(SSTableSet.CANONICAL), ", ")));
|
||||
|
||||
try (Refs<SSTableReader> sstables = baseCfs.selectAndReference(View.select(SSTableSet.CANONICAL)).refs)
|
||||
{
|
||||
SecondaryIndexBuilder builder = new SecondaryIndexBuilder(baseCfs,
|
||||
Collections.singleton(getIndexName()),
|
||||
new ReducingKeyIterator(sstables));
|
||||
Future<?> future = CompactionManager.instance.submitIndexBuild(builder);
|
||||
FBUtilities.waitOnFuture(future);
|
||||
forceBlockingFlush();
|
||||
setIndexBuilt();
|
||||
}
|
||||
logger.info("Index build of {} complete", getIndexName());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds the index using the data in the underlying CF, non blocking
|
||||
*
|
||||
*
|
||||
* @return A future object which the caller can block on (optional)
|
||||
*/
|
||||
public Future<?> buildIndexAsync()
|
||||
{
|
||||
// if we're just linking in the index to indexedColumns on an already-built index post-restart, we're done
|
||||
boolean allAreBuilt = true;
|
||||
for (ColumnDefinition cdef : columnDefs)
|
||||
{
|
||||
if (!SystemKeyspace.isIndexBuilt(baseCfs.keyspace.getName(), getNameForSystemKeyspace(cdef.name.bytes)))
|
||||
{
|
||||
allAreBuilt = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allAreBuilt)
|
||||
return null;
|
||||
|
||||
// build it asynchronously; addIndex gets called by CFS open and schema update, neither of which
|
||||
// we want to block for a long period. (actual build is serialized on CompactionManager.)
|
||||
Runnable runnable = new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
baseCfs.forceBlockingFlush();
|
||||
buildIndexBlocking();
|
||||
}
|
||||
};
|
||||
FutureTask<?> f = new FutureTask<Object>(runnable, null);
|
||||
|
||||
new Thread(f, "Creating index: " + getIndexName()).start();
|
||||
return f;
|
||||
}
|
||||
|
||||
public ColumnFamilyStore getBaseCfs()
|
||||
{
|
||||
return baseCfs;
|
||||
}
|
||||
|
||||
private void setBaseCfs(ColumnFamilyStore baseCfs)
|
||||
{
|
||||
this.baseCfs = baseCfs;
|
||||
}
|
||||
|
||||
public Set<ColumnDefinition> getColumnDefs()
|
||||
{
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
void setIndexMetadata(IndexMetadata indexDef)
|
||||
{
|
||||
this.indexMetadata = indexDef;
|
||||
for (ColumnIdentifier col : indexDef.columns)
|
||||
this.columnDefs.add(baseCfs.metadata.getColumnDefinition(col));
|
||||
}
|
||||
|
||||
void addColumnDef(ColumnDefinition columnDef)
|
||||
{
|
||||
columnDefs.add(columnDef);
|
||||
}
|
||||
|
||||
void removeColumnDef(ByteBuffer name)
|
||||
{
|
||||
Iterator<ColumnDefinition> it = columnDefs.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
if (it.next().name.bytes.equals(name))
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the index supports lookups for the given operator, false otherwise. */
|
||||
public boolean supportsOperator(Operator operator)
|
||||
{
|
||||
return operator == Operator.EQ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the decoratedKey for a column value. Assumes an index CFS is present.
|
||||
* @param value column value
|
||||
* @return decorated key
|
||||
*/
|
||||
public DecoratedKey getIndexKeyFor(ByteBuffer value)
|
||||
{
|
||||
return getIndexCfs().decorateKey(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the provided column is indexed by this secondary index.
|
||||
*
|
||||
* The default implementation checks whether the name is one the columnDef name,
|
||||
* but this should be overriden but subclass if needed.
|
||||
*/
|
||||
public abstract boolean indexes(ColumnDefinition column);
|
||||
|
||||
/**
|
||||
* This is the primary way to create a secondary index instance for a CF column.
|
||||
* It will validate the index_options before initializing.
|
||||
*
|
||||
* @param baseCfs the source of data for the Index
|
||||
* @param indexDef the meta information about this index (index_type, index_options, name, etc...)
|
||||
*
|
||||
* @return The secondary index instance for this column
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public static SecondaryIndex createInstance(ColumnFamilyStore baseCfs,
|
||||
IndexMetadata indexDef) throws ConfigurationException
|
||||
{
|
||||
SecondaryIndex index = uninitializedInstance(baseCfs.metadata, indexDef);
|
||||
index.validateOptions(baseCfs.metadata, indexDef);
|
||||
index.setBaseCfs(baseCfs);
|
||||
index.setIndexMetadata(indexDef);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public static void validate(CFMetaData baseMetadata,
|
||||
IndexMetadata indexDef) throws ConfigurationException
|
||||
{
|
||||
SecondaryIndex index = uninitializedInstance(baseMetadata, indexDef);
|
||||
index.validateOptions(baseMetadata, indexDef);
|
||||
}
|
||||
|
||||
private static SecondaryIndex uninitializedInstance(CFMetaData baseMetadata,
|
||||
IndexMetadata indexDef) throws ConfigurationException
|
||||
{
|
||||
if (indexDef.isKeys())
|
||||
{
|
||||
return new KeysIndex();
|
||||
}
|
||||
else if (indexDef.isComposites())
|
||||
{
|
||||
return CompositesIndex.create(indexDef, baseMetadata);
|
||||
}
|
||||
else if (indexDef.isCustom())
|
||||
{
|
||||
assert indexDef.options != null;
|
||||
String class_name = indexDef.options.get(CUSTOM_INDEX_OPTION_NAME);
|
||||
assert class_name != null;
|
||||
try
|
||||
{
|
||||
return (SecondaryIndex) Class.forName(class_name).newInstance();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
throw new AssertionError("Unknown index type: " + indexDef.name);
|
||||
}
|
||||
|
||||
public abstract void validate(DecoratedKey partitionKey) throws InvalidRequestException;
|
||||
public abstract void validate(Clustering clustering) throws InvalidRequestException;
|
||||
public abstract void validate(ByteBuffer cellValue, CellPath path) throws InvalidRequestException;
|
||||
|
||||
public abstract long estimateResultRows();
|
||||
|
||||
protected String baseKeyspace()
|
||||
{
|
||||
return baseCfs.metadata.ksName;
|
||||
}
|
||||
|
||||
protected String baseTable()
|
||||
{
|
||||
return baseCfs.metadata.cfName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the index metadata for the index on a given column of a given table.
|
||||
*/
|
||||
public static CFMetaData newIndexMetadata(CFMetaData baseMetadata, IndexMetadata def)
|
||||
{
|
||||
return newIndexMetadata(baseMetadata, def, def.indexedColumn(baseMetadata).type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the index metadata for the index on a given column of a given table.
|
||||
*/
|
||||
static CFMetaData newIndexMetadata(CFMetaData baseMetadata, IndexMetadata def, AbstractType<?> comparator)
|
||||
{
|
||||
assert !def.isCustom();
|
||||
|
||||
CFMetaData.Builder builder = CFMetaData.Builder.create(baseMetadata.ksName, baseMetadata.indexColumnFamilyName(def))
|
||||
.withId(baseMetadata.cfId)
|
||||
.withPartitioner(new LocalPartitioner(comparator))
|
||||
.addPartitionKey(def.indexedColumn(baseMetadata).name, comparator);
|
||||
|
||||
if (def.isComposites())
|
||||
{
|
||||
CompositesIndex.addIndexClusteringColumns(builder, baseMetadata, def);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert def.isKeys();
|
||||
KeysIndex.addIndexClusteringColumns(builder, baseMetadata);
|
||||
}
|
||||
|
||||
return builder.build().reloadIndexMetadataProperties(baseMetadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return Objects.toStringHelper(this).add("columnDefs", columnDefs).toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,792 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ConcurrentNavigableMap;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.db.compaction.CompactionManager;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
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.concurrent.OpOrder;
|
||||
|
||||
/**
|
||||
* Manages all the indexes associated with a given CFS
|
||||
* Different types of indexes can be created across the same CF
|
||||
*/
|
||||
public class SecondaryIndexManager
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(SecondaryIndexManager.class);
|
||||
|
||||
public static final Updater nullUpdater = new Updater()
|
||||
{
|
||||
public void maybeIndex(Clustering clustering, long timestamp, int ttl, DeletionTime deletion) {}
|
||||
public void insert(Clustering clustering, Cell cell) {}
|
||||
public void update(Clustering clustering, Cell oldCell, Cell cell) {}
|
||||
public void remove(Clustering clustering, Cell current) {}
|
||||
|
||||
public void updateRowLevelIndexes() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Organizes the indexes by column name
|
||||
*/
|
||||
private final ConcurrentNavigableMap<ByteBuffer, SecondaryIndex> indexesByColumn;
|
||||
|
||||
|
||||
/**
|
||||
* Keeps a single instance of a SecondaryIndex for many columns when the index type
|
||||
* has isRowLevelIndex() == true
|
||||
*
|
||||
* This allows updates to happen to an entire row at once
|
||||
*/
|
||||
private final ConcurrentMap<Class<? extends SecondaryIndex>, SecondaryIndex> rowLevelIndexMap;
|
||||
|
||||
|
||||
/**
|
||||
* Keeps all secondary index instances, either per-column or per-row
|
||||
*/
|
||||
private final Set<SecondaryIndex> allIndexes;
|
||||
|
||||
|
||||
/**
|
||||
* The underlying column family containing the source data for these indexes
|
||||
*/
|
||||
public final ColumnFamilyStore baseCfs;
|
||||
|
||||
public SecondaryIndexManager(ColumnFamilyStore baseCfs)
|
||||
{
|
||||
indexesByColumn = new ConcurrentSkipListMap<>();
|
||||
rowLevelIndexMap = new ConcurrentHashMap<>();
|
||||
allIndexes = Collections.newSetFromMap(new ConcurrentHashMap<SecondaryIndex, Boolean>());
|
||||
|
||||
this.baseCfs = baseCfs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops and adds new indexes associated with the underlying CF
|
||||
*/
|
||||
public void reload()
|
||||
{
|
||||
// figure out what needs to be added and dropped.
|
||||
// future: if/when we have modifiable settings for secondary indexes,
|
||||
// they'll need to be handled here.
|
||||
Collection<ByteBuffer> indexedColumnNames = indexesByColumn.keySet();
|
||||
for (ByteBuffer indexedColumn : indexedColumnNames)
|
||||
{
|
||||
ColumnDefinition def = baseCfs.metadata.getColumnDefinition(indexedColumn);
|
||||
if (def == null || !baseCfs.metadata.getIndexes().get(def).isPresent())
|
||||
removeIndexedColumn(indexedColumn);
|
||||
}
|
||||
|
||||
// TODO: allow all ColumnDefinition type
|
||||
for (IndexMetadata indexDef : baseCfs.metadata.getIndexes())
|
||||
{
|
||||
if (!indexedColumnNames.contains(indexDef.indexedColumn(baseCfs.metadata).name.bytes))
|
||||
addIndexedColumn(indexDef);
|
||||
}
|
||||
|
||||
for (SecondaryIndex index : allIndexes)
|
||||
index.reload();
|
||||
}
|
||||
|
||||
public Set<String> allIndexesNames()
|
||||
{
|
||||
Set<String> names = new HashSet<>(allIndexes.size());
|
||||
for (SecondaryIndex index : allIndexes)
|
||||
names.add(index.getIndexName());
|
||||
return names;
|
||||
}
|
||||
|
||||
public Set<PerColumnSecondaryIndex> perColumnIndexes()
|
||||
{
|
||||
Set<PerColumnSecondaryIndex> s = new HashSet<>();
|
||||
for (SecondaryIndex index : allIndexes)
|
||||
if (index instanceof PerColumnSecondaryIndex)
|
||||
s.add((PerColumnSecondaryIndex)index);
|
||||
return s;
|
||||
}
|
||||
|
||||
public Set<PerRowSecondaryIndex> perRowIndexes()
|
||||
{
|
||||
Set<PerRowSecondaryIndex> s = new HashSet<>();
|
||||
for (SecondaryIndex index : allIndexes)
|
||||
if (index instanceof PerRowSecondaryIndex)
|
||||
s.add((PerRowSecondaryIndex)index);
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a full, blocking rebuild of the indexes specified by columns from the sstables.
|
||||
* Does nothing if columns is empty.
|
||||
*
|
||||
* Caller must acquire and release references to the sstables used here.
|
||||
*
|
||||
* @param sstables the data to build from
|
||||
* @param idxNames the list of columns to index, ordered by comparator
|
||||
*/
|
||||
public void maybeBuildSecondaryIndexes(Collection<SSTableReader> sstables, Set<String> idxNames)
|
||||
{
|
||||
idxNames = filterByColumn(idxNames);
|
||||
if (idxNames.isEmpty())
|
||||
return;
|
||||
|
||||
logger.info(String.format("Submitting index build of %s for data in %s",
|
||||
idxNames, StringUtils.join(sstables, ", ")));
|
||||
|
||||
SecondaryIndexBuilder builder = new SecondaryIndexBuilder(baseCfs, idxNames, new ReducingKeyIterator(sstables));
|
||||
Future<?> future = CompactionManager.instance.submitIndexBuild(builder);
|
||||
FBUtilities.waitOnFuture(future);
|
||||
|
||||
flushIndexesBlocking();
|
||||
|
||||
logger.info("Index build of {} complete", idxNames);
|
||||
}
|
||||
|
||||
public boolean indexes(ColumnDefinition column)
|
||||
{
|
||||
for (SecondaryIndex index : allIndexes)
|
||||
if (index.indexes(column))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private Set<SecondaryIndex> indexFor(ColumnDefinition column)
|
||||
{
|
||||
Set<SecondaryIndex> matching = null;
|
||||
for (SecondaryIndex index : allIndexes)
|
||||
{
|
||||
if (index.indexes(column))
|
||||
{
|
||||
if (matching == null)
|
||||
matching = new HashSet<>();
|
||||
matching.add(index);
|
||||
}
|
||||
}
|
||||
return matching == null ? Collections.<SecondaryIndex>emptySet() : matching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a existing index
|
||||
* @param column the indexed column to remove
|
||||
*/
|
||||
public void removeIndexedColumn(ByteBuffer column)
|
||||
{
|
||||
SecondaryIndex index = indexesByColumn.remove(column);
|
||||
|
||||
if (index == null)
|
||||
return;
|
||||
|
||||
// Remove this column from from row level index map as well as all indexes set
|
||||
if (index instanceof PerRowSecondaryIndex)
|
||||
{
|
||||
index.removeColumnDef(column);
|
||||
|
||||
// If no columns left remove from row level lookup as well as all indexes set
|
||||
if (index.getColumnDefs().isEmpty())
|
||||
{
|
||||
allIndexes.remove(index);
|
||||
rowLevelIndexMap.remove(index.getClass());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
allIndexes.remove(index);
|
||||
}
|
||||
|
||||
index.removeIndex(column);
|
||||
SystemKeyspace.setIndexRemoved(baseCfs.metadata.ksName, index.getNameForSystemKeyspace(column));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds and builds a index for a column
|
||||
* @param indexDef the index metadata
|
||||
* @return a future which the caller can optionally block on signaling the index is built
|
||||
*/
|
||||
public synchronized Future<?> addIndexedColumn(IndexMetadata indexDef)
|
||||
{
|
||||
ColumnDefinition cdef = indexDef.indexedColumn(baseCfs.metadata);
|
||||
if (indexesByColumn.containsKey(cdef.name.bytes))
|
||||
return null;
|
||||
|
||||
SecondaryIndex index = SecondaryIndex.createInstance(baseCfs, indexDef);
|
||||
|
||||
// Keep a single instance of the index per-cf for row level indexes
|
||||
// since we want all columns to be under the index
|
||||
if (index instanceof PerRowSecondaryIndex)
|
||||
{
|
||||
SecondaryIndex currentIndex = rowLevelIndexMap.get(index.getClass());
|
||||
|
||||
if (currentIndex == null)
|
||||
{
|
||||
rowLevelIndexMap.put(index.getClass(), index);
|
||||
index.init();
|
||||
}
|
||||
else
|
||||
{
|
||||
index = currentIndex;
|
||||
index.setIndexMetadata(indexDef);
|
||||
logger.info("Creating new index : {}",indexDef.name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: We sould do better than throw a RuntimeException
|
||||
if (indexDef.isCustom() && index instanceof AbstractSimplePerColumnSecondaryIndex)
|
||||
throw new RuntimeException("Cannot use a subclass of AbstractSimplePerColumnSecondaryIndex as a CUSTOM index, as they assume they are CFS backed");
|
||||
index.init();
|
||||
}
|
||||
|
||||
// link in indexedColumns. this means that writes will add new data to
|
||||
// the index immediately,
|
||||
// so we don't have to lock everything while we do the build. it's up to
|
||||
// the operator to wait
|
||||
// until the index is actually built before using in queries.
|
||||
indexesByColumn.put(cdef.name.bytes, index);
|
||||
|
||||
// Add to all indexes set:
|
||||
allIndexes.add(index);
|
||||
|
||||
// if we're just linking in the index to indexedColumns on an
|
||||
// already-built index post-restart, we're done
|
||||
if (index.isIndexBuilt(cdef.name.bytes))
|
||||
return null;
|
||||
|
||||
return index.buildIndexAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param column the name of indexes column
|
||||
* @return the index
|
||||
*/
|
||||
public SecondaryIndex getIndexForColumn(ColumnDefinition column)
|
||||
{
|
||||
return indexesByColumn.get(column.name.bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the index
|
||||
*/
|
||||
public void invalidate()
|
||||
{
|
||||
for (SecondaryIndex index : allIndexes)
|
||||
index.invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush all indexes to disk
|
||||
*/
|
||||
public void flushIndexesBlocking()
|
||||
{
|
||||
// despatch flushes for all CFS backed indexes
|
||||
List<Future<?>> wait = new ArrayList<>();
|
||||
synchronized (baseCfs.getTracker())
|
||||
{
|
||||
for (SecondaryIndex index : allIndexes)
|
||||
if (index.getIndexCfs() != null)
|
||||
wait.add(index.getIndexCfs().forceFlush());
|
||||
}
|
||||
|
||||
// blockingFlush any non-CFS-backed indexes
|
||||
for (SecondaryIndex index : allIndexes)
|
||||
if (index.getIndexCfs() == null)
|
||||
index.forceBlockingFlush();
|
||||
|
||||
// wait for the CFS-backed index flushes to complete
|
||||
FBUtilities.waitOnFutures(wait);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all built indexes (ready to use)
|
||||
*/
|
||||
public List<String> getBuiltIndexes()
|
||||
{
|
||||
List<String> indexList = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<ByteBuffer, SecondaryIndex> entry : indexesByColumn.entrySet())
|
||||
{
|
||||
SecondaryIndex index = entry.getValue();
|
||||
|
||||
if (index.isIndexBuilt(entry.getKey()))
|
||||
indexList.add(entry.getValue().getIndexName());
|
||||
}
|
||||
|
||||
return indexList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all CFS from indexes which use a backing CFS internally (KEYS)
|
||||
*/
|
||||
public Set<ColumnFamilyStore> getIndexesBackedByCfs()
|
||||
{
|
||||
Set<ColumnFamilyStore> cfsList = new HashSet<>();
|
||||
|
||||
for (SecondaryIndex index: allIndexes)
|
||||
{
|
||||
ColumnFamilyStore cfs = index.getIndexCfs();
|
||||
if (cfs != null)
|
||||
cfsList.add(cfs);
|
||||
}
|
||||
|
||||
return cfsList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all indexes which do *not* use a backing CFS internally
|
||||
*/
|
||||
public Set<SecondaryIndex> getIndexesNotBackedByCfs()
|
||||
{
|
||||
// we use identity map because per row indexes use same instance across many columns
|
||||
Set<SecondaryIndex> indexes = Collections.newSetFromMap(new IdentityHashMap<SecondaryIndex, Boolean>());
|
||||
for (SecondaryIndex index: allIndexes)
|
||||
if (index.getIndexCfs() == null)
|
||||
indexes.add(index);
|
||||
return indexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all of the secondary indexes without distinction to the (non-)backed by secondary ColumnFamilyStore.
|
||||
*/
|
||||
public Set<SecondaryIndex> getIndexes()
|
||||
{
|
||||
return allIndexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if there are ANY indexes for this table..
|
||||
*/
|
||||
public boolean hasIndexes()
|
||||
{
|
||||
return !indexesByColumn.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* When building an index against existing data, add the given partition to the index
|
||||
*/
|
||||
public void indexPartition(UnfilteredRowIterator partition, OpOrder.Group opGroup, Set<SecondaryIndex> allIndexes, int nowInSec)
|
||||
{
|
||||
Set<PerRowSecondaryIndex> perRowIndexes = perRowIndexes();
|
||||
Set<PerColumnSecondaryIndex> perColumnIndexes = perColumnIndexes();
|
||||
|
||||
if (!perRowIndexes.isEmpty())
|
||||
{
|
||||
// TODO: This is passing the same partition iterator to all perRow index, which means this only
|
||||
// work if there is only one of them. We should change the API so it doesn't work directly on the
|
||||
// partition, but rather on individual rows, so we can do a single iteration on the partition in this
|
||||
// method and pass the rows to index to all indexes.
|
||||
|
||||
// Update entire partition only once per row level index
|
||||
Set<Class<? extends SecondaryIndex>> appliedRowLevelIndexes = new HashSet<>();
|
||||
for (PerRowSecondaryIndex index : perRowIndexes)
|
||||
{
|
||||
if (appliedRowLevelIndexes.add(index.getClass()))
|
||||
((PerRowSecondaryIndex)index).index(partition.partitionKey().getKey(), partition);
|
||||
}
|
||||
}
|
||||
|
||||
if (!perColumnIndexes.isEmpty())
|
||||
{
|
||||
DecoratedKey key = partition.partitionKey();
|
||||
|
||||
if (!partition.staticRow().isEmpty())
|
||||
{
|
||||
for (PerColumnSecondaryIndex index : perColumnIndexes)
|
||||
index.indexRow(key, partition.staticRow(), opGroup, nowInSec);
|
||||
}
|
||||
|
||||
try (RowIterator filtered = UnfilteredRowIterators.filter(partition, nowInSec))
|
||||
{
|
||||
while (filtered.hasNext())
|
||||
{
|
||||
Row row = filtered.next();
|
||||
for (PerColumnSecondaryIndex index : perColumnIndexes)
|
||||
index.indexRow(key, row, opGroup, nowInSec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all data from all indexes for this partition. For when cleanup rips a partition out entirely.
|
||||
*/
|
||||
public void deleteFromIndexes(UnfilteredRowIterator partition, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
ByteBuffer key = partition.partitionKey().getKey();
|
||||
|
||||
for (PerRowSecondaryIndex index : perRowIndexes())
|
||||
index.delete(key, opGroup);
|
||||
|
||||
Set<PerColumnSecondaryIndex> indexes = perColumnIndexes();
|
||||
|
||||
while (partition.hasNext())
|
||||
{
|
||||
Unfiltered unfiltered = partition.next();
|
||||
if (unfiltered.kind() != Unfiltered.Kind.ROW)
|
||||
continue;
|
||||
|
||||
Row row = (Row) unfiltered;
|
||||
Clustering clustering = row.clustering();
|
||||
if (!row.deletion().isLive())
|
||||
for (PerColumnSecondaryIndex index : indexes)
|
||||
index.maybeDelete(key, clustering, row.deletion(), opGroup);
|
||||
|
||||
for (Cell cell : row.cells())
|
||||
{
|
||||
for (PerColumnSecondaryIndex index : indexes)
|
||||
{
|
||||
if (!index.indexes(cell.column()))
|
||||
continue;
|
||||
|
||||
((PerColumnSecondaryIndex) index).deleteForCleanup(key, clustering, cell, opGroup, nowInSec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This helper acts as a closure around the indexManager and updated data
|
||||
* to ensure that down in Memtable's ColumnFamily implementation, the index
|
||||
* can get updated.
|
||||
*/
|
||||
public Updater updaterFor(PartitionUpdate update, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
return (indexesByColumn.isEmpty() && rowLevelIndexMap.isEmpty())
|
||||
? nullUpdater
|
||||
: new StandardUpdater(update, opGroup, nowInSec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updated closure with only the modified row key.
|
||||
*/
|
||||
public Updater gcUpdaterFor(DecoratedKey key, int nowInSec)
|
||||
{
|
||||
return new GCUpdater(key, nowInSec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of IndexSearchers from the union of expression index types
|
||||
* @param command the query
|
||||
* @return the searchers needed to query the index
|
||||
*/
|
||||
public List<SecondaryIndexSearcher> getIndexSearchersFor(ReadCommand command)
|
||||
{
|
||||
Map<String, Set<ColumnDefinition>> groupByIndexType = new HashMap<>();
|
||||
|
||||
//Group columns by type
|
||||
for (RowFilter.Expression e : command.rowFilter())
|
||||
{
|
||||
SecondaryIndex index = getIndexForColumn(e.column());
|
||||
|
||||
if (index == null || !index.supportsOperator(e.operator()))
|
||||
continue;
|
||||
|
||||
Set<ColumnDefinition> columns = groupByIndexType.get(index.indexTypeForGrouping());
|
||||
|
||||
if (columns == null)
|
||||
{
|
||||
columns = new HashSet<>();
|
||||
groupByIndexType.put(index.indexTypeForGrouping(), columns);
|
||||
}
|
||||
|
||||
columns.add(e.column());
|
||||
}
|
||||
|
||||
List<SecondaryIndexSearcher> indexSearchers = new ArrayList<>(groupByIndexType.size());
|
||||
|
||||
//create searcher per type
|
||||
for (Set<ColumnDefinition> column : groupByIndexType.values())
|
||||
indexSearchers.add(getIndexForColumn(column.iterator().next()).createSecondaryIndexSearcher(column));
|
||||
|
||||
return indexSearchers;
|
||||
}
|
||||
|
||||
public SecondaryIndexSearcher getBestIndexSearcherFor(ReadCommand command)
|
||||
{
|
||||
List<SecondaryIndexSearcher> indexSearchers = getIndexSearchersFor(command);
|
||||
|
||||
if (indexSearchers.isEmpty())
|
||||
return null;
|
||||
|
||||
SecondaryIndexSearcher mostSelective = null;
|
||||
long bestEstimate = Long.MAX_VALUE;
|
||||
for (SecondaryIndexSearcher searcher : indexSearchers)
|
||||
{
|
||||
SecondaryIndex highestSelectivityIndex = searcher.highestSelectivityIndex(command.rowFilter());
|
||||
long estimate = highestSelectivityIndex.estimateResultRows();
|
||||
if (estimate <= bestEstimate)
|
||||
{
|
||||
bestEstimate = estimate;
|
||||
mostSelective = searcher;
|
||||
}
|
||||
}
|
||||
return mostSelective;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an union of expression index types. It will throw an {@link InvalidRequestException} if
|
||||
* any of the expressions in the provided clause is not valid for its index implementation.
|
||||
* @param filter the filter to check
|
||||
* @throws org.apache.cassandra.exceptions.InvalidRequestException in case of validation errors
|
||||
*/
|
||||
public void validateFilter(RowFilter filter) throws InvalidRequestException
|
||||
{
|
||||
for (RowFilter.Expression expression : filter)
|
||||
{
|
||||
SecondaryIndex index = getIndexForColumn(expression.column());
|
||||
if (index != null && index.supportsOperator(expression.operator()))
|
||||
expression.validateForIndexing();
|
||||
}
|
||||
}
|
||||
|
||||
public Set<SecondaryIndex> getIndexesByNames(Set<String> idxNames)
|
||||
{
|
||||
Set<SecondaryIndex> result = new HashSet<>();
|
||||
for (SecondaryIndex index : allIndexes)
|
||||
if (idxNames.contains(index.getIndexName()))
|
||||
result.add(index);
|
||||
return result;
|
||||
}
|
||||
|
||||
public SecondaryIndex getIndexByName(String idxName)
|
||||
{
|
||||
for (SecondaryIndex index : allIndexes)
|
||||
if (idxName.equals(index.getIndexName()))
|
||||
return index;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setIndexBuilt(Set<String> idxNames)
|
||||
{
|
||||
for (SecondaryIndex index : getIndexesByNames(idxNames))
|
||||
index.setIndexBuilt();
|
||||
}
|
||||
|
||||
public void setIndexRemoved(Set<String> idxNames)
|
||||
{
|
||||
for (SecondaryIndex index : getIndexesByNames(idxNames))
|
||||
index.setIndexRemoved();
|
||||
}
|
||||
|
||||
public void validate(DecoratedKey partitionKey) throws InvalidRequestException
|
||||
{
|
||||
for (SecondaryIndex index : perColumnIndexes())
|
||||
index.validate(partitionKey);
|
||||
}
|
||||
|
||||
public void validate(Clustering clustering) throws InvalidRequestException
|
||||
{
|
||||
for (SecondaryIndex index : perColumnIndexes())
|
||||
index.validate(clustering);
|
||||
}
|
||||
|
||||
public void validate(ColumnDefinition column, ByteBuffer value, CellPath path) throws InvalidRequestException
|
||||
{
|
||||
for (SecondaryIndex index : indexFor(column))
|
||||
index.validate(value, path);
|
||||
}
|
||||
|
||||
static boolean shouldCleanupOldValue(Cell oldCell, Cell newCell)
|
||||
{
|
||||
// If either the value or timestamp is different, then we
|
||||
// should delete from the index. If not, then we can infer that
|
||||
// at least one of the cells is an ExpiringColumn and that the
|
||||
// difference is in the expiry time. In this case, we don't want to
|
||||
// delete the old value from the index as the tombstone we insert
|
||||
// will just hide the inserted value.
|
||||
// Completely identical cells (including expiring columns with
|
||||
// identical ttl & localExpirationTime) will not get this far due
|
||||
// to the oldCell.equals(newCell) in StandardUpdater.update
|
||||
return !oldCell.value().equals(newCell.value()) || oldCell.timestamp() != newCell.timestamp();
|
||||
}
|
||||
|
||||
private Set<String> filterByColumn(Set<String> idxNames)
|
||||
{
|
||||
Set<SecondaryIndex> indexes = getIndexesByNames(idxNames);
|
||||
Set<String> filtered = new HashSet<>(idxNames.size());
|
||||
for (SecondaryIndex candidate : indexes)
|
||||
{
|
||||
for (ColumnDefinition column : baseCfs.metadata.allColumns())
|
||||
{
|
||||
if (candidate.indexes(column))
|
||||
{
|
||||
filtered.add(candidate.getIndexName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
public static interface Updater
|
||||
{
|
||||
/** Called when a row with the provided clustering and row infos is inserted */
|
||||
public void maybeIndex(Clustering clustering, long timestamp, int ttl, DeletionTime deletion);
|
||||
|
||||
/** called when constructing the index against pre-existing data */
|
||||
public void insert(Clustering clustering, Cell cell);
|
||||
|
||||
/** called when updating the index from a memtable */
|
||||
public void update(Clustering clustering, Cell oldCell, Cell cell);
|
||||
|
||||
/** called when lazy-updating the index during compaction (CASSANDRA-2897) */
|
||||
public void remove(Clustering clustering, Cell current);
|
||||
|
||||
/** called after memtable updates are complete (CASSANDRA-5397) */
|
||||
public void updateRowLevelIndexes();
|
||||
}
|
||||
|
||||
private final class GCUpdater implements Updater
|
||||
{
|
||||
private final DecoratedKey key;
|
||||
private final int nowInSec;
|
||||
|
||||
public GCUpdater(DecoratedKey key, int nowInSec)
|
||||
{
|
||||
this.key = key;
|
||||
this.nowInSec = nowInSec;
|
||||
}
|
||||
|
||||
public void maybeIndex(Clustering clustering, long timestamp, int ttl, DeletionTime deletion)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void insert(Clustering clustering, Cell cell)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void update(Clustering clustering, Cell oldCell, Cell newCell)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void remove(Clustering clustering, Cell cell)
|
||||
{
|
||||
for (SecondaryIndex index : indexFor(cell.column()))
|
||||
{
|
||||
if (index instanceof PerColumnSecondaryIndex)
|
||||
{
|
||||
try (OpOrder.Group opGroup = baseCfs.keyspace.writeOrder.start())
|
||||
{
|
||||
((PerColumnSecondaryIndex) index).delete(key.getKey(), clustering, cell, opGroup, nowInSec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updateRowLevelIndexes()
|
||||
{
|
||||
for (SecondaryIndex index : rowLevelIndexMap.values())
|
||||
((PerRowSecondaryIndex) index).index(key.getKey(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private final class StandardUpdater implements Updater
|
||||
{
|
||||
private final PartitionUpdate update;
|
||||
private final OpOrder.Group opGroup;
|
||||
private final int nowInSec;
|
||||
|
||||
public StandardUpdater(PartitionUpdate update, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
this.update = update;
|
||||
this.opGroup = opGroup;
|
||||
this.nowInSec = nowInSec;
|
||||
}
|
||||
|
||||
public void maybeIndex(Clustering clustering, long timestamp, int ttl, DeletionTime deletion)
|
||||
{
|
||||
for (PerColumnSecondaryIndex index : perColumnIndexes())
|
||||
{
|
||||
if (timestamp != LivenessInfo.NO_TIMESTAMP)
|
||||
index.maybeIndex(update.partitionKey().getKey(), clustering, timestamp, ttl, opGroup, nowInSec);
|
||||
if (!deletion.isLive())
|
||||
index.maybeDelete(update.partitionKey().getKey(), clustering, deletion, opGroup);
|
||||
}
|
||||
}
|
||||
|
||||
public void insert(Clustering clustering, Cell cell)
|
||||
{
|
||||
if (!cell.isLive(nowInSec))
|
||||
return;
|
||||
|
||||
for (SecondaryIndex index : indexFor(cell.column()))
|
||||
if (index instanceof PerColumnSecondaryIndex)
|
||||
((PerColumnSecondaryIndex) index).insert(update.partitionKey().getKey(), clustering, cell, opGroup);
|
||||
}
|
||||
|
||||
public void update(Clustering clustering, Cell oldCell, Cell cell)
|
||||
{
|
||||
if (oldCell.equals(cell))
|
||||
return;
|
||||
|
||||
for (SecondaryIndex index : indexFor(cell.column()))
|
||||
{
|
||||
if (index instanceof PerColumnSecondaryIndex)
|
||||
{
|
||||
if (cell.isLive(nowInSec))
|
||||
{
|
||||
((PerColumnSecondaryIndex) index).update(update.partitionKey().getKey(), clustering, oldCell, cell, opGroup, nowInSec);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Usually we want to delete the old value from the index, except when
|
||||
// name/value/timestamp are all equal, but the columns themselves
|
||||
// are not (as is the case when overwriting expiring columns with
|
||||
// identical values and ttl) Then, we don't want to delete as the
|
||||
// tombstone will hide the new value we just inserted; see CASSANDRA-7268
|
||||
if (shouldCleanupOldValue(oldCell, cell))
|
||||
((PerColumnSecondaryIndex) index).delete(update.partitionKey().getKey(), clustering, oldCell, opGroup, nowInSec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(Clustering clustering, Cell cell)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void updateRowLevelIndexes()
|
||||
{
|
||||
for (SecondaryIndex index : rowLevelIndexMap.values())
|
||||
((PerRowSecondaryIndex) index).index(update.partitionKey().getKey(), update.unfilteredIterator());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,244 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.filter.*;
|
||||
import org.apache.cassandra.db.partitions.PartitionIterator;
|
||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.dht.AbstractBounds;
|
||||
import org.apache.cassandra.tracing.Tracing;
|
||||
import org.apache.cassandra.utils.btree.BTreeSet;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
public abstract class SecondaryIndexSearcher
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(SecondaryIndexSearcher.class);
|
||||
|
||||
protected final SecondaryIndexManager indexManager;
|
||||
protected final Set<ColumnDefinition> columns;
|
||||
protected final ColumnFamilyStore baseCfs;
|
||||
|
||||
public SecondaryIndexSearcher(SecondaryIndexManager indexManager, Set<ColumnDefinition> columns)
|
||||
{
|
||||
this.indexManager = indexManager;
|
||||
this.columns = columns;
|
||||
this.baseCfs = indexManager.baseCfs;
|
||||
}
|
||||
|
||||
public SecondaryIndex highestSelectivityIndex(RowFilter filter)
|
||||
{
|
||||
RowFilter.Expression expr = highestSelectivityPredicate(filter, false);
|
||||
return expr == null ? null : indexManager.getIndexForColumn(expr.column());
|
||||
}
|
||||
|
||||
public RowFilter.Expression primaryClause(ReadCommand command)
|
||||
{
|
||||
return highestSelectivityPredicate(command.rowFilter(), false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource") // Both the OpOrder and 'indexIter' are closed on exception, or through the closing of the result
|
||||
// of this method.
|
||||
public UnfilteredPartitionIterator search(ReadCommand command, ReadOrderGroup orderGroup)
|
||||
{
|
||||
RowFilter.Expression primary = highestSelectivityPredicate(command.rowFilter(), true);
|
||||
assert primary != null;
|
||||
|
||||
AbstractSimplePerColumnSecondaryIndex index = (AbstractSimplePerColumnSecondaryIndex)indexManager.getIndexForColumn(primary.column());
|
||||
assert index != null && index.getIndexCfs() != null;
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Most-selective indexed predicate is {}", primary);
|
||||
|
||||
DecoratedKey indexKey = index.getIndexKeyFor(primary.getIndexValue());
|
||||
|
||||
UnfilteredRowIterator indexIter = queryIndex(index, indexKey, command, orderGroup);
|
||||
try
|
||||
{
|
||||
return queryDataFromIndex(index, indexKey, UnfilteredRowIterators.filter(indexIter, command.nowInSec()), command, orderGroup);
|
||||
}
|
||||
catch (RuntimeException | Error e)
|
||||
{
|
||||
indexIter.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private UnfilteredRowIterator queryIndex(AbstractSimplePerColumnSecondaryIndex index, DecoratedKey indexKey, ReadCommand command, ReadOrderGroup orderGroup)
|
||||
{
|
||||
ClusteringIndexFilter filter = makeIndexFilter(index, command);
|
||||
CFMetaData indexMetadata = index.getIndexCfs().metadata;
|
||||
return SinglePartitionReadCommand.create(indexMetadata, command.nowInSec(), indexKey, ColumnFilter.all(indexMetadata), filter)
|
||||
.queryMemtableAndDisk(index.getIndexCfs(), orderGroup.indexReadOpOrderGroup());
|
||||
}
|
||||
|
||||
private ClusteringIndexFilter makeIndexFilter(AbstractSimplePerColumnSecondaryIndex index, ReadCommand command)
|
||||
{
|
||||
if (command instanceof SinglePartitionReadCommand)
|
||||
{
|
||||
// Note: as yet there's no route to get here - a 2i query *always* uses a
|
||||
// PartitionRangeReadCommand. This is here in preparation for coming changes
|
||||
// in SelectStatement.
|
||||
SinglePartitionReadCommand sprc = (SinglePartitionReadCommand)command;
|
||||
ByteBuffer pk = sprc.partitionKey().getKey();
|
||||
ClusteringIndexFilter filter = sprc.clusteringIndexFilter();
|
||||
|
||||
if (filter instanceof ClusteringIndexNamesFilter)
|
||||
{
|
||||
NavigableSet<Clustering> requested = ((ClusteringIndexNamesFilter)filter).requestedRows();
|
||||
BTreeSet.Builder<Clustering> clusterings = BTreeSet.builder(index.getIndexComparator());
|
||||
for (Clustering c : requested)
|
||||
clusterings.add(index.makeIndexClustering(pk, c, (Cell)null));
|
||||
return new ClusteringIndexNamesFilter(clusterings.build(), filter.isReversed());
|
||||
}
|
||||
else
|
||||
{
|
||||
Slices requested = ((ClusteringIndexSliceFilter)filter).requestedSlices();
|
||||
Slices.Builder builder = new Slices.Builder(index.getIndexComparator());
|
||||
for (Slice slice : requested)
|
||||
builder.add(index.makeIndexBound(pk, slice.start()), index.makeIndexBound(pk, slice.end()));
|
||||
return new ClusteringIndexSliceFilter(builder.build(), filter.isReversed());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
DataRange dataRange = ((PartitionRangeReadCommand)command).dataRange();
|
||||
AbstractBounds<PartitionPosition> range = dataRange.keyRange();
|
||||
|
||||
Slice slice = Slice.ALL;
|
||||
|
||||
/*
|
||||
* XXX: If the range requested is a token range, we'll have to start at the beginning (and stop at the end) of
|
||||
* the indexed row unfortunately (which will be inefficient), because we have no way to intuit the smallest possible
|
||||
* key having a given token. A potential fix would be to actually store the token along the key in the indexed row.
|
||||
*/
|
||||
if (range.left instanceof DecoratedKey)
|
||||
{
|
||||
// the right hand side of the range may not be a DecoratedKey (for instance if we're paging),
|
||||
// but if it is, we can optimise slightly by restricting the slice
|
||||
if (range.right instanceof DecoratedKey)
|
||||
{
|
||||
|
||||
DecoratedKey startKey = (DecoratedKey) range.left;
|
||||
DecoratedKey endKey = (DecoratedKey) range.right;
|
||||
|
||||
Slice.Bound start = Slice.Bound.BOTTOM;
|
||||
Slice.Bound end = Slice.Bound.TOP;
|
||||
|
||||
/*
|
||||
* For index queries over a range, we can't do a whole lot better than querying everything for the key range, though for
|
||||
* slice queries where we can slightly restrict the beginning and end.
|
||||
*/
|
||||
if (!dataRange.isNamesQuery())
|
||||
{
|
||||
ClusteringIndexSliceFilter startSliceFilter = ((ClusteringIndexSliceFilter) dataRange.clusteringIndexFilter(startKey));
|
||||
ClusteringIndexSliceFilter endSliceFilter = ((ClusteringIndexSliceFilter) dataRange.clusteringIndexFilter(endKey));
|
||||
|
||||
// We can't effectively support reversed queries when we have a range, so we don't support it
|
||||
// (or through post-query reordering) and shouldn't get there.
|
||||
assert !startSliceFilter.isReversed() && !endSliceFilter.isReversed();
|
||||
|
||||
Slices startSlices = startSliceFilter.requestedSlices();
|
||||
Slices endSlices = endSliceFilter.requestedSlices();
|
||||
|
||||
if (startSlices.size() > 0)
|
||||
start = startSlices.get(0).start();
|
||||
|
||||
if (endSlices.size() > 0)
|
||||
end = endSlices.get(endSlices.size() - 1).end();
|
||||
}
|
||||
|
||||
slice = Slice.make(index.makeIndexBound(startKey.getKey(), start),
|
||||
index.makeIndexBound(endKey.getKey(), end));
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise, just start the index slice from the key we do have
|
||||
slice = Slice.make(index.makeIndexBound(((DecoratedKey)range.left).getKey(), Slice.Bound.BOTTOM),
|
||||
Slice.Bound.TOP);
|
||||
}
|
||||
}
|
||||
return new ClusteringIndexSliceFilter(Slices.with(index.getIndexComparator(), slice), false);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract UnfilteredPartitionIterator queryDataFromIndex(AbstractSimplePerColumnSecondaryIndex index,
|
||||
DecoratedKey indexKey,
|
||||
RowIterator indexHits,
|
||||
ReadCommand command,
|
||||
ReadOrderGroup orderGroup);
|
||||
|
||||
protected RowFilter.Expression highestSelectivityPredicate(RowFilter filter, boolean includeInTrace)
|
||||
{
|
||||
RowFilter.Expression best = null;
|
||||
int bestMeanCount = Integer.MAX_VALUE;
|
||||
Map<SecondaryIndex, Integer> candidates = new HashMap<>();
|
||||
|
||||
for (RowFilter.Expression expression : filter)
|
||||
{
|
||||
// skip columns belonging to a different index type
|
||||
if (!columns.contains(expression.column()))
|
||||
continue;
|
||||
|
||||
SecondaryIndex index = indexManager.getIndexForColumn(expression.column());
|
||||
if (index == null || index.getIndexCfs() == null || !index.supportsOperator(expression.operator()))
|
||||
continue;
|
||||
|
||||
int columns = index.getIndexCfs().getMeanColumns();
|
||||
candidates.put(index, columns);
|
||||
if (columns < bestMeanCount)
|
||||
{
|
||||
best = expression;
|
||||
bestMeanCount = columns;
|
||||
}
|
||||
}
|
||||
|
||||
if (includeInTrace)
|
||||
{
|
||||
if (best == null)
|
||||
Tracing.trace("No applicable indexes found");
|
||||
else if (Tracing.isTracing())
|
||||
// pay for an additional threadlocal get() rather than build the strings unnecessarily
|
||||
Tracing.trace("Candidate index mean cardinalities are {}. Scanning with {}.", FBUtilities.toString(candidates), indexManager.getIndexForColumn(best.column()).getIndexName());
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-process the result of an index query. This is done by the coordinator node after it has reconciled
|
||||
* the replica responses.
|
||||
*
|
||||
* @param command The {@code ReadCommand} use for the query.
|
||||
* @param result The index query results to be post-processed
|
||||
* @return The post-processed results
|
||||
*/
|
||||
public PartitionIterator postReconciliationProcessing(RowFilter filter, PartitionIterator result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index.composites;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.index.AbstractSimplePerColumnSecondaryIndex;
|
||||
import org.apache.cassandra.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexSearcher;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
|
||||
/**
|
||||
* Base class for internal secondary indexes (this could be merged with AbstractSimplePerColumnSecondaryIndex).
|
||||
*/
|
||||
public abstract class CompositesIndex extends AbstractSimplePerColumnSecondaryIndex
|
||||
{
|
||||
public static CompositesIndex create(IndexMetadata indexDef, CFMetaData baseMetadata)
|
||||
{
|
||||
ColumnDefinition cfDef = indexDef.indexedColumn(baseMetadata);
|
||||
if (cfDef.type.isCollection() && cfDef.type.isMultiCell())
|
||||
{
|
||||
switch (((CollectionType)cfDef.type).kind)
|
||||
{
|
||||
case LIST:
|
||||
return new CompositesIndexOnCollectionValue();
|
||||
case SET:
|
||||
return new CompositesIndexOnCollectionKey();
|
||||
case MAP:
|
||||
if (indexDef.options.containsKey(SecondaryIndex.INDEX_KEYS_OPTION_NAME))
|
||||
return new CompositesIndexOnCollectionKey();
|
||||
else if (indexDef.options.containsKey(SecondaryIndex.INDEX_ENTRIES_OPTION_NAME))
|
||||
return new CompositesIndexOnCollectionKeyAndValue();
|
||||
else
|
||||
return new CompositesIndexOnCollectionValue();
|
||||
}
|
||||
}
|
||||
|
||||
switch (cfDef.kind)
|
||||
{
|
||||
case CLUSTERING:
|
||||
return new CompositesIndexOnClusteringKey();
|
||||
case REGULAR:
|
||||
return new CompositesIndexOnRegular();
|
||||
case PARTITION_KEY:
|
||||
return new CompositesIndexOnPartitionKey();
|
||||
//case COMPACT_VALUE:
|
||||
// return new CompositesIndexOnCompactValue();
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static void addIndexClusteringColumns(CFMetaData.Builder indexMetadata, CFMetaData baseMetadata, IndexMetadata indexDef)
|
||||
{
|
||||
ColumnDefinition cfDef = indexDef.indexedColumn(baseMetadata);
|
||||
if (cfDef.type.isCollection() && cfDef.type.isMultiCell())
|
||||
{
|
||||
CollectionType type = (CollectionType)cfDef.type;
|
||||
if (type.kind == CollectionType.Kind.LIST
|
||||
|| (type.kind == CollectionType.Kind.MAP && indexDef.options.containsKey(SecondaryIndex.INDEX_VALUES_OPTION_NAME)))
|
||||
{
|
||||
CompositesIndexOnCollectionValue.addClusteringColumns(indexMetadata, baseMetadata, cfDef);
|
||||
}
|
||||
else
|
||||
{
|
||||
addGenericClusteringColumns(indexMetadata, baseMetadata, cfDef);
|
||||
}
|
||||
}
|
||||
else if (cfDef.isClusteringColumn())
|
||||
{
|
||||
CompositesIndexOnClusteringKey.addClusteringColumns(indexMetadata, baseMetadata, cfDef);
|
||||
}
|
||||
else
|
||||
{
|
||||
addGenericClusteringColumns(indexMetadata, baseMetadata, cfDef);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void addGenericClusteringColumns(CFMetaData.Builder indexMetadata, CFMetaData baseMetadata, ColumnDefinition columnDef)
|
||||
{
|
||||
indexMetadata.addClusteringColumn("partition_key", baseMetadata.partitioner.partitionOrdering());
|
||||
for (ColumnDefinition def : baseMetadata.clusteringColumns())
|
||||
indexMetadata.addClusteringColumn(def.name, def.type);
|
||||
}
|
||||
|
||||
public abstract IndexedEntry decodeEntry(DecoratedKey indexedValue, Row indexEntry);
|
||||
|
||||
public abstract boolean isStale(Row row, ByteBuffer indexValue, int nowInSec);
|
||||
|
||||
public void delete(IndexedEntry entry, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
Row row = BTreeRow.emptyDeletedRow(entry.indexClustering, new DeletionTime(entry.timestamp, nowInSec));
|
||||
PartitionUpdate upd = PartitionUpdate.singleRowUpdate(indexCfs.metadata, entry.indexValue, row);
|
||||
indexCfs.apply(upd, SecondaryIndexManager.nullUpdater, opGroup, null);
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("removed index entry for cleaned-up value {}:{}", entry.indexValue, upd);
|
||||
}
|
||||
|
||||
public SecondaryIndexSearcher createSecondaryIndexSearcher(Set<ColumnDefinition> columns)
|
||||
{
|
||||
return new CompositesSearcher(baseCfs.indexManager, columns);
|
||||
}
|
||||
|
||||
public void validateOptions(CFMetaData baseCfm, IndexMetadata indexMetadata) throws ConfigurationException
|
||||
{
|
||||
ColumnDefinition columnDef = indexMetadata.indexedColumn(baseCfm);
|
||||
Map<String, String> options = new HashMap<String, String>(indexMetadata.options);
|
||||
|
||||
// We used to have an option called "prefix_size" so skip it silently for backward compatibility sake.
|
||||
options.remove("prefix_size");
|
||||
|
||||
if (columnDef.type.isCollection())
|
||||
{
|
||||
options.remove(SecondaryIndex.INDEX_VALUES_OPTION_NAME);
|
||||
options.remove(SecondaryIndex.INDEX_KEYS_OPTION_NAME);
|
||||
options.remove(SecondaryIndex.INDEX_ENTRIES_OPTION_NAME);
|
||||
}
|
||||
|
||||
if (!options.isEmpty())
|
||||
throw new ConfigurationException("Unknown options provided for COMPOSITES index: " + options.keySet());
|
||||
}
|
||||
|
||||
public static class IndexedEntry
|
||||
{
|
||||
public final DecoratedKey indexValue;
|
||||
public final Clustering indexClustering;
|
||||
public final long timestamp;
|
||||
|
||||
public final ByteBuffer indexedKey;
|
||||
public final Clustering indexedEntryClustering;
|
||||
|
||||
public IndexedEntry(DecoratedKey indexValue, Clustering indexClustering, long timestamp, ByteBuffer indexedKey, Clustering indexedEntryClustering)
|
||||
{
|
||||
this.indexValue = indexValue;
|
||||
this.indexClustering = indexClustering;
|
||||
this.timestamp = timestamp;
|
||||
this.indexedKey = indexedKey;
|
||||
this.indexedEntryClustering = indexedEntryClustering;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index.composites;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
|
||||
/**
|
||||
* Index on a CLUSTERING column definition.
|
||||
*
|
||||
* A cell indexed by this index will have the general form:
|
||||
* ck_0 ... ck_n c_name : v
|
||||
* where ck_i are the cluster keys, c_name the last component of the cell
|
||||
* composite name (or second to last if collections are in use, but this
|
||||
* has no impact) and v the cell value.
|
||||
*
|
||||
* Such a cell is always indexed by this index (or rather, it is indexed if
|
||||
* n >= columnDef.componentIndex, which will always be the case in practice)
|
||||
* and it will generate (makeIndexColumnName()) an index entry whose:
|
||||
* - row key will be ck_i (getIndexedValue()) where i == columnDef.componentIndex.
|
||||
* - cell name will
|
||||
* rk ck_0 ... ck_{i-1} ck_{i+1} ck_n
|
||||
* where rk is the row key of the initial cell and i == columnDef.componentIndex.
|
||||
*/
|
||||
public class CompositesIndexOnClusteringKey extends CompositesIndex
|
||||
{
|
||||
public static void addClusteringColumns(CFMetaData.Builder indexMetadata, CFMetaData baseMetadata, ColumnDefinition columnDef)
|
||||
{
|
||||
indexMetadata.addClusteringColumn("partition_key", baseMetadata.partitioner.partitionOrdering());
|
||||
|
||||
List<ColumnDefinition> cks = baseMetadata.clusteringColumns();
|
||||
for (int i = 0; i < columnDef.position(); i++)
|
||||
{
|
||||
ColumnDefinition def = cks.get(i);
|
||||
indexMetadata.addClusteringColumn(def.name, def.type);
|
||||
}
|
||||
for (int i = columnDef.position() + 1; i < cks.size(); i++)
|
||||
{
|
||||
ColumnDefinition def = cks.get(i);
|
||||
indexMetadata.addClusteringColumn(def.name, def.type);
|
||||
}
|
||||
}
|
||||
|
||||
protected ByteBuffer getIndexedValue(ByteBuffer rowKey, Clustering clustering, ByteBuffer cellValue, CellPath path)
|
||||
{
|
||||
return clustering.get(columnDef.position());
|
||||
}
|
||||
|
||||
protected CBuilder buildIndexClusteringPrefix(ByteBuffer rowKey, ClusteringPrefix prefix, CellPath path)
|
||||
{
|
||||
CBuilder builder = CBuilder.create(getIndexComparator());
|
||||
builder.add(rowKey);
|
||||
for (int i = 0; i < Math.min(columnDef.position(), prefix.size()); i++)
|
||||
builder.add(prefix.get(i));
|
||||
for (int i = columnDef.position() + 1; i < prefix.size(); i++)
|
||||
builder.add(prefix.get(i));
|
||||
return builder;
|
||||
}
|
||||
|
||||
public IndexedEntry decodeEntry(DecoratedKey indexedValue, Row indexEntry)
|
||||
{
|
||||
int ckCount = baseCfs.metadata.clusteringColumns().size();
|
||||
|
||||
Clustering clustering = indexEntry.clustering();
|
||||
CBuilder builder = CBuilder.create(baseCfs.getComparator());
|
||||
for (int i = 0; i < columnDef.position(); i++)
|
||||
builder.add(clustering.get(i + 1));
|
||||
|
||||
builder.add(indexedValue.getKey());
|
||||
|
||||
for (int i = columnDef.position() + 1; i < ckCount; i++)
|
||||
builder.add(clustering.get(i));
|
||||
|
||||
return new IndexedEntry(indexedValue, clustering, indexEntry.primaryKeyLivenessInfo().timestamp(), clustering.get(0), builder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean indexPrimaryKeyColumn()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean indexes(ColumnDefinition c)
|
||||
{
|
||||
// Actual indexing for this index type is done through maybeIndex
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isStale(Row data, ByteBuffer indexValue, int nowInSec)
|
||||
{
|
||||
return !data.hasLiveData(nowInSec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeIndex(ByteBuffer partitionKey, Clustering clustering, long timestamp, int ttl, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
if (clustering != Clustering.STATIC_CLUSTERING && clustering.get(columnDef.position()) != null)
|
||||
insert(partitionKey, clustering, null, LivenessInfo.create(indexCfs.metadata, timestamp, ttl, nowInSec), opGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeDelete(ByteBuffer partitionKey, Clustering clustering, DeletionTime deletion, OpOrder.Group opGroup)
|
||||
{
|
||||
if (clustering.get(columnDef.position()) != null && !deletion.isLive())
|
||||
delete(partitionKey, clustering, null, null, deletion, opGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(ByteBuffer rowKey, Clustering clustering, Cell cell, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
// We only know that one column of the CQL row has been updated/deleted, but we don't know if the
|
||||
// full row has been deleted so we should not do anything. If it ends up that the whole row has
|
||||
// been deleted, it will be eventually cleaned up on read because the entry will be detected stale.
|
||||
}
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index.keys;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.index.AbstractSimplePerColumnSecondaryIndex;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexSearcher;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
|
||||
/**
|
||||
* Implements a secondary index for a column family using a second column family.
|
||||
* The design uses inverted index http://en.wikipedia.org/wiki/Inverted_index.
|
||||
* The row key is the indexed value. For example, if we're indexing a column named
|
||||
* city, the index value of city is the row key.
|
||||
* The column names are the keys of the records. To see a detailed example, please
|
||||
* refer to wikipedia.
|
||||
*/
|
||||
public class KeysIndex extends AbstractSimplePerColumnSecondaryIndex
|
||||
{
|
||||
public static void addIndexClusteringColumns(CFMetaData.Builder indexMetadata, CFMetaData baseMetadata)
|
||||
{
|
||||
indexMetadata.addClusteringColumn("partition_key", baseMetadata.partitioner.partitionOrdering());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void indexRow(DecoratedKey key, Row row, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
super.indexRow(key, row, opGroup, nowInSec);
|
||||
|
||||
// This is used when building indexes, in particular when the index is first created. On thrift, this
|
||||
// potentially means the column definition just got created, and so we need to check if's not a "dynamic"
|
||||
// row that actually correspond to the index definition.
|
||||
assert baseCfs.metadata.isCompactTable();
|
||||
if (!row.isStatic())
|
||||
{
|
||||
Clustering clustering = row.clustering();
|
||||
if (clustering.get(0).equals(columnDef.name.bytes))
|
||||
{
|
||||
Cell cell = row.getCell(baseCfs.metadata.compactValueColumn());
|
||||
if (cell != null && cell.isLive(nowInSec))
|
||||
insert(key.getKey(), clustering, cell, opGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected ByteBuffer getIndexedValue(ByteBuffer rowKey, Clustering clustering, ByteBuffer cellValue, CellPath path)
|
||||
{
|
||||
return cellValue;
|
||||
}
|
||||
|
||||
protected CBuilder buildIndexClusteringPrefix(ByteBuffer rowKey, ClusteringPrefix prefix, CellPath path)
|
||||
{
|
||||
CBuilder builder = CBuilder.create(getIndexComparator());
|
||||
builder.add(rowKey);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public SecondaryIndexSearcher createSecondaryIndexSearcher(Set<ColumnDefinition> columns)
|
||||
{
|
||||
return new KeysSearcher(baseCfs.indexManager, columns);
|
||||
}
|
||||
|
||||
public void validateOptions(CFMetaData baseCfm, IndexMetadata def) throws ConfigurationException
|
||||
{
|
||||
// no options used
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,6 @@ package org.apache.cassandra.db.partitions;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
|
@ -27,17 +26,18 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
|||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.db.rows.EncodingStats;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.db.rows.Rows;
|
||||
import org.apache.cassandra.index.transactions.UpdateTransaction;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.btree.BTree;
|
||||
import org.apache.cassandra.utils.btree.UpdateFunction;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.utils.concurrent.Locks;
|
||||
import org.apache.cassandra.utils.memory.MemtableAllocator;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.utils.memory.HeapAllocator;
|
||||
import static org.apache.cassandra.db.index.SecondaryIndexManager.Updater;
|
||||
import org.apache.cassandra.utils.memory.MemtableAllocator;
|
||||
|
||||
/**
|
||||
* A thread-safe and atomic Partition implementation.
|
||||
|
|
@ -86,7 +86,7 @@ public class AtomicBTreePartition extends AbstractBTreePartition
|
|||
|
||||
public AtomicBTreePartition(CFMetaData metadata, DecoratedKey partitionKey, MemtableAllocator allocator)
|
||||
{
|
||||
// TODO: is this a potential bug? partition columns may be a subset if we alter columns while it's in memtable
|
||||
// involved in potential bug? partition columns may be a subset if we alter columns while it's in memtable
|
||||
super(metadata, partitionKey, metadata.partitionColumns());
|
||||
this.allocator = allocator;
|
||||
this.ref = EMPTY;
|
||||
|
|
@ -108,11 +108,10 @@ public class AtomicBTreePartition extends AbstractBTreePartition
|
|||
* @return an array containing first the difference in size seen after merging the updates, and second the minimum
|
||||
* time detla between updates.
|
||||
*/
|
||||
public long[] addAllWithSizeDelta(final PartitionUpdate update, OpOrder.Group writeOp, Updater indexer)
|
||||
public long[] addAllWithSizeDelta(final PartitionUpdate update, OpOrder.Group writeOp, UpdateTransaction indexer)
|
||||
{
|
||||
RowUpdater updater = new RowUpdater(this, allocator, writeOp, indexer);
|
||||
DeletionInfo inputDeletionInfoCopy = null;
|
||||
|
||||
boolean monitorOwned = false;
|
||||
try
|
||||
{
|
||||
|
|
@ -121,12 +120,21 @@ public class AtomicBTreePartition extends AbstractBTreePartition
|
|||
Locks.monitorEnterUnsafe(this);
|
||||
monitorOwned = true;
|
||||
}
|
||||
|
||||
indexer.start();
|
||||
|
||||
while (true)
|
||||
{
|
||||
Holder current = ref;
|
||||
updater.ref = current;
|
||||
updater.reset();
|
||||
|
||||
if (!update.deletionInfo().isLive())
|
||||
indexer.onPartitionDeletion(update.deletionInfo().getPartitionDeletion());
|
||||
|
||||
if (update.deletionInfo().hasRanges())
|
||||
update.deletionInfo().rangeIterator(false).forEachRemaining(indexer::onRangeTombstone);
|
||||
|
||||
DeletionInfo deletionInfo;
|
||||
if (update.deletionInfo().mayModify(current.deletionInfo))
|
||||
{
|
||||
|
|
@ -150,7 +158,6 @@ public class AtomicBTreePartition extends AbstractBTreePartition
|
|||
|
||||
if (tree != null && refUpdater.compareAndSet(this, current, new Holder(tree, deletionInfo, staticRow, newStats)))
|
||||
{
|
||||
indexer.updateRowLevelIndexes();
|
||||
updater.finish();
|
||||
return new long[]{ updater.dataSize, updater.colUpdateTimeDelta };
|
||||
}
|
||||
|
|
@ -171,6 +178,7 @@ public class AtomicBTreePartition extends AbstractBTreePartition
|
|||
}
|
||||
finally
|
||||
{
|
||||
indexer.commit();
|
||||
if (monitorOwned)
|
||||
Locks.monitorExitUnsafe(this);
|
||||
}
|
||||
|
|
@ -230,7 +238,7 @@ public class AtomicBTreePartition extends AbstractBTreePartition
|
|||
final AtomicBTreePartition updating;
|
||||
final MemtableAllocator allocator;
|
||||
final OpOrder.Group writeOp;
|
||||
final Updater indexer;
|
||||
final UpdateTransaction indexer;
|
||||
final int nowInSec;
|
||||
Holder ref;
|
||||
Row.Builder regularBuilder;
|
||||
|
|
@ -240,7 +248,7 @@ public class AtomicBTreePartition extends AbstractBTreePartition
|
|||
final MemtableAllocator.DataReclaimer reclaimer;
|
||||
List<Row> inserted; // TODO: replace with walk of aborted BTree
|
||||
|
||||
private RowUpdater(AtomicBTreePartition updating, MemtableAllocator allocator, OpOrder.Group writeOp, Updater indexer)
|
||||
private RowUpdater(AtomicBTreePartition updating, MemtableAllocator allocator, OpOrder.Group writeOp, UpdateTransaction indexer)
|
||||
{
|
||||
this.updating = updating;
|
||||
this.allocator = allocator;
|
||||
|
|
@ -265,7 +273,7 @@ public class AtomicBTreePartition extends AbstractBTreePartition
|
|||
public Row apply(Row insert)
|
||||
{
|
||||
Row data = Rows.copy(insert, builder(insert.clustering())).build();
|
||||
insertIntoIndexes(data);
|
||||
indexer.onInserted(insert);
|
||||
|
||||
this.dataSize += data.dataSize();
|
||||
this.heapSize += data.unsharedHeapSizeExcludingData();
|
||||
|
|
@ -280,10 +288,12 @@ public class AtomicBTreePartition extends AbstractBTreePartition
|
|||
Columns mergedColumns = existing.columns().mergeTo(update.columns());
|
||||
|
||||
Row.Builder builder = builder(existing.clustering());
|
||||
colUpdateTimeDelta = Math.min(colUpdateTimeDelta, Rows.merge(existing, update, mergedColumns, builder, nowInSec, indexer));
|
||||
colUpdateTimeDelta = Math.min(colUpdateTimeDelta, Rows.merge(existing, update, mergedColumns, builder, nowInSec));
|
||||
|
||||
Row reconciled = builder.build();
|
||||
|
||||
indexer.onUpdated(existing, reconciled);
|
||||
|
||||
dataSize += reconciled.dataSize() - existing.dataSize();
|
||||
heapSize += reconciled.unsharedHeapSizeExcludingData() - existing.unsharedHeapSizeExcludingData();
|
||||
if (inserted == null)
|
||||
|
|
@ -294,41 +304,6 @@ public class AtomicBTreePartition extends AbstractBTreePartition
|
|||
return reconciled;
|
||||
}
|
||||
|
||||
private void insertIntoIndexes(Row toInsert)
|
||||
{
|
||||
if (indexer == SecondaryIndexManager.nullUpdater)
|
||||
return;
|
||||
|
||||
maybeIndexPrimaryKeyColumns(toInsert);
|
||||
Clustering clustering = toInsert.clustering();
|
||||
for (Cell cell : toInsert.cells())
|
||||
indexer.insert(clustering, cell);
|
||||
}
|
||||
|
||||
private void maybeIndexPrimaryKeyColumns(Row row)
|
||||
{
|
||||
// We want to update a primary key index with the most up to date info contains in that inserted row (if only for
|
||||
// backward compatibility). Note that if there is an index but not a partition key or clustering column one, we've
|
||||
// wasting this work. We might be able to avoid that if row indexing was pushed in the index updater.
|
||||
long timestamp = row.primaryKeyLivenessInfo().timestamp();
|
||||
int ttl = row.primaryKeyLivenessInfo().ttl();
|
||||
|
||||
for (Cell cell : row.cells())
|
||||
{
|
||||
long cellTimestamp = cell.timestamp();
|
||||
if (cell.isLive(nowInSec))
|
||||
{
|
||||
if (cellTimestamp > timestamp)
|
||||
{
|
||||
timestamp = cellTimestamp;
|
||||
ttl = cell.ttl();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
indexer.maybeIndex(row.clustering(), timestamp, ttl, row.deletion());
|
||||
}
|
||||
|
||||
protected void reset()
|
||||
{
|
||||
this.dataSize = 0;
|
||||
|
|
|
|||
|
|
@ -19,30 +19,23 @@ package org.apache.cassandra.db.partitions;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.context.CounterContext;
|
||||
import org.apache.cassandra.db.filter.ColumnFilter;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.io.util.DataInputBuffer;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputBuffer;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.io.util.*;
|
||||
import org.apache.cassandra.net.MessagingService;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.MergeIterator;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
import org.apache.cassandra.utils.btree.BTree;
|
||||
import org.apache.cassandra.utils.btree.UpdateFunction;
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ import java.util.Comparator;
|
|||
import java.util.Iterator;
|
||||
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.Conflicts;
|
||||
import org.apache.cassandra.db.DeletionTime;
|
||||
import org.apache.cassandra.db.partitions.PartitionStatisticsCollector;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
|
||||
/**
|
||||
* Static methods to work on cells.
|
||||
|
|
@ -58,9 +58,6 @@ public abstract class Cells
|
|||
* Also note that which cell is provided as {@code existing} and which is
|
||||
* provided as {@code update} matters for index updates.
|
||||
*
|
||||
* @param clustering the clustering for the row the cells to merge originate from.
|
||||
* This is only used for index updates, so this can be {@code null} if
|
||||
* {@code indexUpdater == SecondaryIndexManager.nullUpdater}.
|
||||
* @param existing the pre-existing cell, the one that is updated. This can be
|
||||
* {@code null} if this reconciliation correspond to an insertion.
|
||||
* @param update the newly added cell, the update. This can be {@code null} out
|
||||
|
|
@ -72,20 +69,15 @@ public abstract class Cells
|
|||
* @param nowInSec the current time in seconds (which plays a role during reconciliation
|
||||
* because deleted cells always have precedence on timestamp equality and deciding if a
|
||||
* cell is a live or not depends on the current time due to expiring cells).
|
||||
* @param indexUpdater an index updater to which the result of the reconciliation is
|
||||
* signaled (if relevant, that is if the update is not simply ignored by the reconciliation).
|
||||
* This cannot be {@code null} but {@code SecondaryIndexManager.nullUpdater} can be passed.
|
||||
*
|
||||
* @return the timestamp delta between existing and update, or {@code Long.MAX_VALUE} if one
|
||||
* of them is {@code null} or deleted by {@code deletion}).
|
||||
*/
|
||||
public static long reconcile(Clustering clustering,
|
||||
Cell existing,
|
||||
public static long reconcile(Cell existing,
|
||||
Cell update,
|
||||
DeletionTime deletion,
|
||||
Row.Builder builder,
|
||||
int nowInSec,
|
||||
SecondaryIndexManager.Updater indexUpdater)
|
||||
int nowInSec)
|
||||
{
|
||||
existing = existing == null || deletion.deletes(existing) ? null : existing;
|
||||
update = update == null || deletion.deletes(update) ? null : update;
|
||||
|
|
@ -93,10 +85,6 @@ public abstract class Cells
|
|||
{
|
||||
if (update != null)
|
||||
{
|
||||
// It's inefficient that we call maybeIndex (which is for primary key indexes) on every cell, but
|
||||
// we'll need to fix that damn 2ndary index API to avoid that.
|
||||
updatePKIndexes(clustering, update, nowInSec, indexUpdater);
|
||||
indexUpdater.insert(clustering, update);
|
||||
builder.addCell(update);
|
||||
}
|
||||
else if (existing != null)
|
||||
|
|
@ -109,21 +97,9 @@ public abstract class Cells
|
|||
Cell reconciled = reconcile(existing, update, nowInSec);
|
||||
builder.addCell(reconciled);
|
||||
|
||||
// Note that this test rely on reconcile returning either 'existing' or 'update'. That's not true for counters but we don't index them
|
||||
if (reconciled == update)
|
||||
{
|
||||
updatePKIndexes(clustering, update, nowInSec, indexUpdater);
|
||||
indexUpdater.update(clustering, existing, reconciled);
|
||||
}
|
||||
return Math.abs(existing.timestamp() - update.timestamp());
|
||||
}
|
||||
|
||||
private static void updatePKIndexes(Clustering clustering, Cell cell, int nowInSec, SecondaryIndexManager.Updater indexUpdater)
|
||||
{
|
||||
if (indexUpdater != SecondaryIndexManager.nullUpdater && cell.isLive(nowInSec))
|
||||
indexUpdater.maybeIndex(clustering, cell.timestamp(), cell.ttl(), DeletionTime.LIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconciles/merge two cells.
|
||||
* <p>
|
||||
|
|
@ -202,9 +178,6 @@ public abstract class Cells
|
|||
* Also note that which cells is provided as {@code existing} and which are
|
||||
* provided as {@code update} matters for index updates.
|
||||
*
|
||||
* @param clustering the clustering for the row the cells to merge originate from.
|
||||
* This is only used for index updates, so this can be {@code null} if
|
||||
* {@code indexUpdater == SecondaryIndexManager.nullUpdater}.
|
||||
* @param column the complex column the cells are for.
|
||||
* @param existing the pre-existing cells, the ones that are updated. This can be
|
||||
* {@code null} if this reconciliation correspond to an insertion.
|
||||
|
|
@ -217,9 +190,6 @@ public abstract class Cells
|
|||
* @param nowInSec the current time in seconds (which plays a role during reconciliation
|
||||
* because deleted cells always have precedence on timestamp equality and deciding if a
|
||||
* cell is a live or not depends on the current time due to expiring cells).
|
||||
* @param indexUpdater an index updater to which the result of the reconciliation is
|
||||
* signaled (if relevant, that is if the updates are not simply ignored by the reconciliation).
|
||||
* This cannot be {@code null} but {@code SecondaryIndexManager.nullUpdater} can be passed.
|
||||
*
|
||||
* @return the smallest timestamp delta between corresponding cells from existing and update. A
|
||||
* timestamp delta being computed as the difference between a cell from {@code update} and the
|
||||
|
|
@ -227,14 +197,12 @@ public abstract class Cells
|
|||
* of cells from {@code existing} and {@code update} having the same cell path is empty, this
|
||||
* returns {@code Long.MAX_VALUE}.
|
||||
*/
|
||||
public static long reconcileComplex(Clustering clustering,
|
||||
ColumnDefinition column,
|
||||
public static long reconcileComplex(ColumnDefinition column,
|
||||
Iterator<Cell> existing,
|
||||
Iterator<Cell> update,
|
||||
DeletionTime deletion,
|
||||
Row.Builder builder,
|
||||
int nowInSec,
|
||||
SecondaryIndexManager.Updater indexUpdater)
|
||||
int nowInSec)
|
||||
{
|
||||
Comparator<CellPath> comparator = column.cellPathComparator();
|
||||
Cell nextExisting = getNext(existing);
|
||||
|
|
@ -247,17 +215,17 @@ public abstract class Cells
|
|||
: comparator.compare(nextExisting.path(), nextUpdate.path()));
|
||||
if (cmp < 0)
|
||||
{
|
||||
reconcile(clustering, nextExisting, null, deletion, builder, nowInSec, indexUpdater);
|
||||
reconcile(nextExisting, null, deletion, builder, nowInSec);
|
||||
nextExisting = getNext(existing);
|
||||
}
|
||||
else if (cmp > 0)
|
||||
{
|
||||
reconcile(clustering, null, nextUpdate, deletion, builder, nowInSec, indexUpdater);
|
||||
reconcile(null, nextUpdate, deletion, builder, nowInSec);
|
||||
nextUpdate = getNext(update);
|
||||
}
|
||||
else
|
||||
{
|
||||
timeDelta = Math.min(timeDelta, reconcile(clustering, nextExisting, nextUpdate, deletion, builder, nowInSec, indexUpdater));
|
||||
timeDelta = Math.min(timeDelta, reconcile(nextExisting, nextUpdate, deletion, builder, nowInSec));
|
||||
nextExisting = getNext(existing);
|
||||
nextUpdate = getNext(update);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@
|
|||
*/
|
||||
package org.apache.cassandra.db.rows;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.PeekingIterator;
|
||||
|
|
@ -25,7 +27,6 @@ import com.google.common.collect.PeekingIterator;
|
|||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.partitions.PartitionStatisticsCollector;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.utils.SearchIterator;
|
||||
|
||||
/**
|
||||
|
|
@ -127,7 +128,7 @@ public abstract class Rows
|
|||
* @param diffListener the listener to which to signal the differences between the inputs and the merged
|
||||
* result.
|
||||
*/
|
||||
public static void diff(Row merged, Columns columns, Row[] inputs, RowDiffListener diffListener)
|
||||
public static void diff(RowDiffListener diffListener, Row merged, Columns columns, Row...inputs)
|
||||
{
|
||||
Clustering clustering = merged.clustering();
|
||||
LivenessInfo mergedInfo = merged.primaryKeyLivenessInfo().isEmpty() ? null : merged.primaryKeyLivenessInfo();
|
||||
|
|
@ -218,23 +219,17 @@ public abstract class Rows
|
|||
{
|
||||
Columns mergedColumns = row1.columns().mergeTo(row2.columns());
|
||||
Row.Builder builder = BTreeRow.sortedBuilder(mergedColumns);
|
||||
merge(row1, row2, mergedColumns, builder, nowInSec, SecondaryIndexManager.nullUpdater);
|
||||
merge(row1, row2, mergedColumns, builder, nowInSec);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static void merge(Row row1, Row row2, Columns mergedColumns, Row.Builder builder, int nowInSec)
|
||||
{
|
||||
merge(row1, row2, mergedColumns, builder, nowInSec, SecondaryIndexManager.nullUpdater);
|
||||
}
|
||||
|
||||
// Merge rows in memtable
|
||||
// Return the minimum timestamp delta between existing and update
|
||||
public static long merge(Row existing,
|
||||
Row update,
|
||||
Columns mergedColumns,
|
||||
Row.Builder builder,
|
||||
int nowInSec,
|
||||
SecondaryIndexManager.Updater indexUpdater)
|
||||
int nowInSec)
|
||||
{
|
||||
Clustering clustering = existing.clustering();
|
||||
builder.newRow(clustering);
|
||||
|
|
@ -253,20 +248,16 @@ public abstract class Rows
|
|||
builder.addPrimaryKeyLivenessInfo(mergedInfo);
|
||||
builder.addRowDeletion(deletion);
|
||||
|
||||
indexUpdater.maybeIndex(clustering, mergedInfo.timestamp(), mergedInfo.ttl(), deletion);
|
||||
|
||||
for (int i = 0; i < mergedColumns.simpleColumnCount(); i++)
|
||||
{
|
||||
ColumnDefinition c = mergedColumns.getSimple(i);
|
||||
Cell existingCell = existing.getCell(c);
|
||||
Cell updateCell = update.getCell(c);
|
||||
timeDelta = Math.min(timeDelta, Cells.reconcile(clustering,
|
||||
existingCell,
|
||||
timeDelta = Math.min(timeDelta, Cells.reconcile(existingCell,
|
||||
updateCell,
|
||||
deletion,
|
||||
builder,
|
||||
nowInSec,
|
||||
indexUpdater));
|
||||
nowInSec));
|
||||
}
|
||||
|
||||
for (int i = 0; i < mergedColumns.complexColumnCount(); i++)
|
||||
|
|
@ -285,7 +276,7 @@ public abstract class Rows
|
|||
|
||||
Iterator<Cell> existingCells = existingData == null ? null : existingData.iterator();
|
||||
Iterator<Cell> updateCells = updateData == null ? null : updateData.iterator();
|
||||
timeDelta = Math.min(timeDelta, Cells.reconcileComplex(clustering, c, existingCells, updateCells, maxDt, builder, nowInSec, indexUpdater));
|
||||
timeDelta = Math.min(timeDelta, Cells.reconcileComplex(c, existingCells, updateCells, maxDt, builder, nowInSec));
|
||||
}
|
||||
|
||||
return timeDelta;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,388 @@
|
|||
package org.apache.cassandra.index;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.cql3.Operator;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
import org.apache.cassandra.db.partitions.PartitionIterator;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.index.transactions.IndexTransaction;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
|
||||
/**
|
||||
* Consisting of a top level Index interface and two sub-interfaces which handle read and write operations,
|
||||
* Searcher and Indexer respectively, this defines a secondary index implementation.
|
||||
* Instantiation is done via reflection and implementations must provide a constructor which takes the base
|
||||
* table's ColumnFamilyStore and the IndexMetadata which defines the Index as arguments. e.g:
|
||||
* {@code MyCustomIndex( ColumnFamilyStore baseCfs, IndexMetadata indexDef )}
|
||||
*
|
||||
* The main interface defines methods for index management, index selection at both write and query time,
|
||||
* as well as validation of values that will ultimately be indexed.
|
||||
* Two sub-interfaces are also defined, which represent single use helpers for short lived tasks at read and write time.
|
||||
* Indexer: an event listener which receives notifications at particular points during an update of a single partition
|
||||
* in the base table.
|
||||
* Searcher: performs queries against the index based on a predicate defined in a RowFilter. An instance
|
||||
* is expected to be single use, being involved in the execution of a single ReadCommand.
|
||||
*
|
||||
* The main interface includes factory methods for obtaining instances of both of the sub-interfaces;
|
||||
*
|
||||
* The methods defined in the top level interface can be grouped into 3 categories:
|
||||
*
|
||||
* Management Tasks:
|
||||
* This group of methods is primarily concerned with maintenance of secondary indexes are are mainly called from
|
||||
* SecondaryIndexManager. It includes methods for registering and un-registering an index, performing maintenance
|
||||
* tasks such as (re)building an index from SSTable data, flushing, invalidating and so forth, as well as some to
|
||||
* retrieve general metadata about the index (index name, any internal tables used for persistence etc).
|
||||
* Several of these maintenance functions have a return type of Callable<?>; the expectation for these methods is
|
||||
* that any work required to be performed by the method be done inside the Callable so that the responsibility for
|
||||
* scheduling its execution can rest with SecondaryIndexManager. For instance, a task like reloading index metadata
|
||||
* following potential updates caused by modifications to the base table may be performed in a blocking way. In
|
||||
* contrast, adding a new index may require it to be built from existing SSTable data, a potentially expensive task
|
||||
* which should be performed asyncronously.
|
||||
*
|
||||
* Index Selection:
|
||||
* There are two facets to index selection, write time and read time selection. The former is concerned with
|
||||
* identifying whether an index should be informed about a particular write operation. The latter is about providing
|
||||
* means to use the index for search during query execution.
|
||||
*
|
||||
* Validation:
|
||||
* Values that may be written to an index are checked as part of input validation, prior to an update or insert
|
||||
* operation being accepted.
|
||||
*
|
||||
*
|
||||
* Sub-interfaces:
|
||||
*
|
||||
* Update processing:
|
||||
* Indexes are subscribed to the stream of events generated by modifications to the base table. Subscription is
|
||||
* done via first registering the Index with the base table's SecondaryIndexManager. For each partition update, the set
|
||||
* of registered indexes are then filtered based on the properties of the update using the selection methods on the main
|
||||
* interface described above. Each of the indexes in the filtered set then provides an event listener to receive
|
||||
* notifications about the update as it is processed. As such then, a event handler instance is scoped to a single
|
||||
* partition update; SecondaryIndexManager obtains a new handler for every update it processes (via a call to the
|
||||
* factory method, indexerFor. That handler will then receive all events for the update, before being
|
||||
* discarded by the SecondaryIndexManager. Indexer instances are never re-used by SecondaryIndexManager and the
|
||||
* expectation is that each call to indexerFor should return a unique instance, or at least if instances can
|
||||
* be recycled, that a given instance is only used to process a single partition update at a time.
|
||||
*
|
||||
* Search:
|
||||
* Each query (i.e. a single ReadCommand) that uses indexes will use a single instance of Index.Searcher. As with
|
||||
* processing of updates, an Index must be registered with the primary table's SecondaryIndexManager to be able to
|
||||
* support queries. During the processing of a ReadCommand, the Expressions in its RowFilter are examined to determine
|
||||
* whether any of them are supported by a registered Index. supportsExpression is used to filter out Indexes which
|
||||
* cannot support a given Expression. After filtering, the set of candidate indexes are ranked according to the result
|
||||
* of getEstimatedResultRows and the most selective (i.e. the one expected to return the smallest number of results) is
|
||||
* chosen. A Searcher instance is then obtained from the searcherFor method & used to perform the actual Index lookup.
|
||||
* Finally, Indexes can define a post processing step to be performed on the coordinator, after results (partitions from
|
||||
* the primary table) have been received from replicas and reconciled. This post processing is defined as a
|
||||
* java.util.functions.BiFunction<PartitionIterator, RowFilter, PartitionIterator>, that is a function which takes as
|
||||
* arguments a PartitionIterator (containing the reconciled result rows) and a RowFilter (from the ReadCommand being
|
||||
* executed) and returns another iterator of partitions, possibly having transformed the initial results in some way.
|
||||
* The post processing function is obtained from the Index's postProcessorFor method; the built-in indexes which ship
|
||||
* with Cassandra return a no-op function here.
|
||||
*
|
||||
* An optional static method may be provided to validate custom index options:
|
||||
*
|
||||
* <pre> {@code
|
||||
* public static Map<String, String> validateOptions(Map<String, String> options);
|
||||
* } </pre>
|
||||
*
|
||||
* The input is the map of index options supplied in the WITH clause of a CREATE INDEX statement. The method should
|
||||
* return a map containing any of the supplied options which are not valid for the implementation. If the returned
|
||||
* map is not empty, validation is considered failed and an error is raised. Alternatively, the implementation may
|
||||
* choose to throw an org.apache.cassandra.exceptions.ConfigurationException if invalid options are encountered.
|
||||
*
|
||||
*/
|
||||
public interface Index
|
||||
{
|
||||
|
||||
/*
|
||||
* Management functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return a task to perform any initialization work when a new index instance is created.
|
||||
* This may involve costly operations such as (re)building the index, and is performed asynchronously
|
||||
* by SecondaryIndexManager
|
||||
* @return a task to perform any necessary initialization work
|
||||
*/
|
||||
public Callable<?> getInitializationTask();
|
||||
|
||||
/**
|
||||
* Returns the IndexMetadata which configures and defines the index instance. This should be the same
|
||||
* object passed as the argument to setIndexMetadata.
|
||||
* @return the index's metadata
|
||||
*/
|
||||
public IndexMetadata getIndexMetadata();
|
||||
|
||||
/**
|
||||
* Return a task to reload the internal metadata of an index.
|
||||
* Called when the base table metadata is modified or when the configuration of the Index is updated
|
||||
* Implementations should return a task which performs any necessary work to be done due to
|
||||
* updating the configuration(s) such as (re)building etc. This task is performed asynchronously
|
||||
* by SecondaryIndexManager
|
||||
* @return task to be executed by the index manager during a reload
|
||||
*/
|
||||
public Callable<?> getMetadataReloadTask(IndexMetadata indexMetadata);
|
||||
|
||||
/**
|
||||
* An index must be registered in order to be able to either subscribe to update events on the base
|
||||
* table and/or to provide Searcher functionality for reads. The double dispatch involved here, where
|
||||
* the Index actually performs its own registration by calling back to the supplied IndexRegistry's
|
||||
* own registerIndex method, is to make the decision as to whether or not to register an index belong
|
||||
* to the implementation, not the manager.
|
||||
* @param registry the index registry to register the instance with
|
||||
*/
|
||||
public void register(IndexRegistry registry);
|
||||
|
||||
/**
|
||||
* Return an identifier for the index. This should be unique across all indexes on a given base table
|
||||
* @return the name of the index
|
||||
*/
|
||||
public String getIndexName();
|
||||
|
||||
/**
|
||||
* If the index implementation uses a local table to store its index data this method should return a
|
||||
* handle to it. If not, an empty Optional should be returned. Typically, this is useful for the built-in
|
||||
* Index implementations.
|
||||
* @return an Optional referencing the Index's backing storage table if it has one, or Optional.empty() if not.
|
||||
*/
|
||||
public Optional<ColumnFamilyStore> getBackingTable();
|
||||
|
||||
/**
|
||||
* Return a task which performs a blocking flush of the index's data to persistent storage.
|
||||
* @return task to be executed by the index manager to perform the flush.
|
||||
*/
|
||||
public Callable<?> getBlockingFlushTask();
|
||||
|
||||
/**
|
||||
* Return a task which invalidates the index, indicating it should no longer be considered usable.
|
||||
* This should include an clean up and releasing of resources required when dropping an index.
|
||||
* @return task to be executed by the index manager to invalidate the index.
|
||||
*/
|
||||
public Callable<?> getInvalidateTask();
|
||||
|
||||
/**
|
||||
* Return a task to truncate the index with the specified truncation timestamp.
|
||||
* Called when the base table is truncated.
|
||||
* @param truncatedAt timestamp of the truncation operation. This will be the same timestamp used
|
||||
* in the truncation of the base table.
|
||||
* @return task to be executed by the index manager when the base table is truncated.
|
||||
*/
|
||||
public Callable<?> getTruncateTask(long truncatedAt);
|
||||
|
||||
/*
|
||||
* Index selection
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called to determine whether this index should process a particular partition update.
|
||||
* @param columns
|
||||
* @return
|
||||
*/
|
||||
public boolean indexes(PartitionColumns columns);
|
||||
|
||||
// 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
|
||||
* supplied column using the specified operator. This forms part of the query validation done
|
||||
* before a CQL select statement is executed.
|
||||
* @param column the target column of a search query predicate
|
||||
* @param operator the operator of a search query predicate
|
||||
* @return true if this index is capable of supporting such expressions, false otherwise
|
||||
*/
|
||||
public boolean supportsExpression(ColumnDefinition column, Operator operator);
|
||||
|
||||
/**
|
||||
* Transform an initial RowFilter into the filter that will still need to applied
|
||||
* to a set of Rows after the index has performed it's initial scan.
|
||||
* Used in ReadCommand#executeLocal to reduce the amount of filtering performed on the
|
||||
* results of the index query.
|
||||
*
|
||||
* @param filter the intial filter belonging to a ReadCommand
|
||||
* @return the (hopefully) reduced filter that would still need to be applied after
|
||||
* the index was used to narrow the initial result set
|
||||
*/
|
||||
public RowFilter getPostIndexQueryFilter(RowFilter filter);
|
||||
|
||||
/**
|
||||
* Return an estimate of the number of results this index is expected to return for any given
|
||||
* query that it can be used to answer. Used in conjunction with indexes() and supportsExpression()
|
||||
* to determine the most selective index for a given ReadCommand. Additionally, this is also used
|
||||
* by StorageProxy.estimateResultsPerRange to calculate the initial concurrency factor for range requests
|
||||
*
|
||||
* @return the estimated average number of results a Searcher may return for any given query
|
||||
*/
|
||||
public long getEstimatedResultRows();
|
||||
|
||||
/*
|
||||
* Input validation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called at write time to ensure that values present in the update
|
||||
* are valid according to the rules of all registered indexes which
|
||||
* will process it. The partition key as well as the clustering and
|
||||
* cell values for each row in the update may be checked by index
|
||||
* implementations
|
||||
* @param update PartitionUpdate containing the values to be validated by registered Index implementations
|
||||
* @throws InvalidRequestException
|
||||
*/
|
||||
public void validate(PartitionUpdate update) throws InvalidRequestException;
|
||||
|
||||
/*
|
||||
* Update processing
|
||||
*/
|
||||
|
||||
/**
|
||||
* Factory method for write time event handlers.
|
||||
* Callers should check the indexes method first and only get a new
|
||||
* handler when the index claims an interest in the specific update
|
||||
* otherwise work may be done unnecessarily
|
||||
*
|
||||
* @param key key of the partition being modified
|
||||
* @param nowInSec current time of the update operation
|
||||
* @param opGroup operation group spanning the update operation
|
||||
* @param transactionType indicates what kind of update is being performed on the base data
|
||||
* i.e. a write time insert/update/delete or the result of compaction
|
||||
* @return
|
||||
*/
|
||||
public Indexer indexerFor(DecoratedKey key,
|
||||
int nowInSec,
|
||||
OpOrder.Group opGroup,
|
||||
IndexTransaction.Type transactionType);
|
||||
|
||||
/**
|
||||
* Listener for processing events emitted during a single partition update.
|
||||
* Instances of this are responsible for applying modifications to the index in response to a single update
|
||||
* operation on a particular partition of the base table.
|
||||
* That update may be generated by the normal write path, by iterating SSTables during streaming operations or when
|
||||
* building or rebuilding an index from source. Updates also occur during compaction when multiple versions of a
|
||||
* source partition from different SSTables are merged.
|
||||
*/
|
||||
public interface Indexer
|
||||
{
|
||||
/**
|
||||
* Notification of the start of a partition update.
|
||||
* This event always occurs before any other during the update.
|
||||
*/
|
||||
public void begin();
|
||||
|
||||
/**
|
||||
* Notification of a top level partition delete.
|
||||
* @param deletionTime
|
||||
*/
|
||||
public void partitionDelete(DeletionTime deletionTime);
|
||||
|
||||
/**
|
||||
* Notification of a RangeTombstone.
|
||||
* An update of a single partition may contain multiple RangeTombstones,
|
||||
* and a notification will be passed for each of them.
|
||||
* @param tombstone
|
||||
*/
|
||||
public void rangeTombstone(RangeTombstone tombstone);
|
||||
|
||||
/**
|
||||
* Notification that a new row was inserted into the Memtable holding the partition.
|
||||
* This only implies that the inserted row was not already present in the Memtable,
|
||||
* it *does not* guarantee that the row does not exist in an SSTable, potentially with
|
||||
* additional column data.
|
||||
*
|
||||
* @param row the Row being inserted into the base table's Memtable.
|
||||
*/
|
||||
public void insertRow(Row row);
|
||||
|
||||
/**
|
||||
* Notification of a modification to a row in the base table's Memtable.
|
||||
* This is allow an Index implementation to clean up entries for base data which is
|
||||
* never flushed to disk (and so will not be purged during compaction).
|
||||
* It's important to note that the old & new rows supplied here may not represent
|
||||
* the totality of the data for the Row with this particular Clustering. There may be
|
||||
* additional column data in SSTables which is not present in either the old or new row,
|
||||
* so implementations should be aware of that.
|
||||
* The supplied rows contain only column data which has actually been updated.
|
||||
* oldRowData contains only the columns which have been removed from the Row's
|
||||
* representation in the Memtable, while newRowData includes only new columns
|
||||
* which were not previously present. Any column data which is unchanged by
|
||||
* the update is not included.
|
||||
*
|
||||
* @param oldRowData data that was present in existing row and which has been removed from
|
||||
* the base table's Memtable
|
||||
* @param newRowData data that was not present in the existing row and is being inserted
|
||||
* into the base table's Memtable
|
||||
*/
|
||||
public void updateRow(Row oldRowData, Row newRowData);
|
||||
|
||||
/**
|
||||
* Notification that a row was removed from the partition.
|
||||
* Note that this is only called as part of either a compaction or a cleanup.
|
||||
* This context is indicated by the TransactionType supplied to the indexerFor method.
|
||||
*
|
||||
* As with updateRow, it cannot be guaranteed that all data belonging to the Clustering
|
||||
* of the supplied Row has been removed (although in the case of a cleanup, that is the
|
||||
* ultimate intention).
|
||||
* There may be data for the same row in other SSTables, so in this case Indexer implementations
|
||||
* should *not* assume that all traces of the row have been removed. In particular,
|
||||
* it is not safe to assert that all values associated with the Row's Clustering
|
||||
* have been deleted, so implementations which index primary key columns should not
|
||||
* purge those entries from their indexes.
|
||||
*
|
||||
* @param row data being removed from the base table
|
||||
*/
|
||||
public void removeRow(Row row);
|
||||
|
||||
/**
|
||||
* Notification of the end of the partition update.
|
||||
* This event always occurs after all others for the particular update.
|
||||
*/
|
||||
public void finish();
|
||||
}
|
||||
|
||||
/*
|
||||
* Querying
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return a function which performs post processing on the results of a partition range read command.
|
||||
* In future, this may be used as a generalized mechanism for transforming results on the coordinator prior
|
||||
* to returning them to the caller.
|
||||
*
|
||||
* This is used on the coordinator during execution of a range command to perform post
|
||||
* processing of merged results obtained from the necessary replicas. This is the only way in which results are
|
||||
* transformed in this way but this may change over time as usage is generalized.
|
||||
* See CASSANDRA-8717 for further discussion.
|
||||
*
|
||||
* The function takes a PartitionIterator of the results from the replicas which has already been collated
|
||||
* & reconciled, along with the command being executed. It returns another PartitionIterator containing the results
|
||||
* of the transformation (which may be the same as the input if the transformation is a no-op).
|
||||
*/
|
||||
public BiFunction<PartitionIterator, ReadCommand, PartitionIterator> postProcessorFor(ReadCommand command);
|
||||
|
||||
/**
|
||||
* Factory method for query time search helper.
|
||||
* @param command the read command being executed
|
||||
* @return an Searcher with which to perform the supplied command
|
||||
*/
|
||||
public Searcher searcherFor(ReadCommand command);
|
||||
|
||||
/**
|
||||
* Performs the actual index lookup during execution of a ReadCommand.
|
||||
* An instance performs its query according to the RowFilter.Expression it was created for (see searcherFor)
|
||||
* An Expression is a predicate of the form [column] [operator] [value].
|
||||
*/
|
||||
public interface Searcher
|
||||
{
|
||||
/**
|
||||
* @param orderGroup the collection of OpOrder.Groups which the ReadCommand is being performed under.
|
||||
* @return partitions from the base table matching the criteria of the search.
|
||||
*/
|
||||
public UnfilteredPartitionIterator search(ReadOrderGroup orderGroup);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package org.apache.cassandra.index;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
|
||||
/**
|
||||
* The collection of all Index instances for a base table.
|
||||
* The SecondaryIndexManager for a ColumnFamilyStore contains an IndexRegistry
|
||||
* (actually it implements this interface at present) and Index implementations
|
||||
* register in order to:
|
||||
* i) subscribe to the stream of updates being applied to partitions in the base table
|
||||
* ii) provide searchers to support queries with the relevant search predicates
|
||||
*/
|
||||
public interface IndexRegistry
|
||||
{
|
||||
void registerIndex(Index index);
|
||||
void unregisterIndex(Index index);
|
||||
|
||||
Index getIndex(IndexMetadata indexMetadata);
|
||||
Collection<Index> listIndexes();
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index;
|
||||
package org.apache.cassandra.index;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
|
@ -25,10 +25,9 @@ import org.apache.cassandra.db.ColumnFamilyStore;
|
|||
import org.apache.cassandra.db.DecoratedKey;
|
||||
import org.apache.cassandra.db.Keyspace;
|
||||
import org.apache.cassandra.db.compaction.CompactionInfo;
|
||||
import org.apache.cassandra.db.compaction.OperationType;
|
||||
import org.apache.cassandra.db.compaction.CompactionInterruptedException;
|
||||
import org.apache.cassandra.db.compaction.OperationType;
|
||||
import org.apache.cassandra.io.sstable.ReducingKeyIterator;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.UUIDGen;
|
||||
|
||||
/**
|
||||
|
|
@ -37,14 +36,14 @@ import org.apache.cassandra.utils.UUIDGen;
|
|||
public class SecondaryIndexBuilder extends CompactionInfo.Holder
|
||||
{
|
||||
private final ColumnFamilyStore cfs;
|
||||
private final Set<String> idxNames;
|
||||
private final Set<Index> indexers;
|
||||
private final ReducingKeyIterator iter;
|
||||
private final UUID compactionId;
|
||||
|
||||
public SecondaryIndexBuilder(ColumnFamilyStore cfs, Set<String> idxNames, ReducingKeyIterator iter)
|
||||
public SecondaryIndexBuilder(ColumnFamilyStore cfs, Set<Index> indexers, ReducingKeyIterator iter)
|
||||
{
|
||||
this.cfs = cfs;
|
||||
this.idxNames = idxNames;
|
||||
this.indexers = indexers;
|
||||
this.iter = iter;
|
||||
this.compactionId = UUIDGen.getTimeUUID();
|
||||
}
|
||||
|
|
@ -67,7 +66,7 @@ public class SecondaryIndexBuilder extends CompactionInfo.Holder
|
|||
if (isStopRequested())
|
||||
throw new CompactionInterruptedException(getCompactionInfo());
|
||||
DecoratedKey key = iter.next();
|
||||
Keyspace.indexPartition(key, cfs, idxNames);
|
||||
Keyspace.indexPartition(key, cfs, indexers);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
|
@ -0,0 +1,900 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.index;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.concurrent.JMXEnabledThreadPoolExecutor;
|
||||
import org.apache.cassandra.concurrent.NamedThreadFactory;
|
||||
import org.apache.cassandra.concurrent.StageManager;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.cql3.statements.IndexTarget;
|
||||
import org.apache.cassandra.db.*;
|
||||
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.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.index.internal.CassandraIndex;
|
||||
import org.apache.cassandra.index.transactions.*;
|
||||
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.schema.Indexes;
|
||||
import org.apache.cassandra.tracing.Tracing;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.utils.concurrent.Refs;
|
||||
|
||||
/**
|
||||
* Handles the core maintenance functionality associated with indexes: adding/removing them to or from
|
||||
* a table, (re)building during bootstrap or other streaming operations, flushing, reloading metadata
|
||||
* and so on.
|
||||
*
|
||||
* The Index interface defines a number of methods which return Callable<?>. These are primarily the
|
||||
* management tasks for an index implementation. Most of them are currently executed in a blocking
|
||||
* fashion via submission to SIM's blockingExecutor. This provides the desired behaviour in pretty
|
||||
* much all cases, as tasks like flushing an index needs to be executed synchronously to avoid potentially
|
||||
* deadlocking on the FlushWriter or PostFlusher. Several of these Callable<?> returning methods on Index could
|
||||
* then be defined with as void and called directly from SIM (rather than being run via the executor service).
|
||||
* Separating the task defintion from execution gives us greater flexibility though, so that in future, for example,
|
||||
* if the flush process allows it we leave open the possibility of executing more of these tasks asynchronously.
|
||||
*
|
||||
* The primary exception to the above is the Callable returned from Index#addIndexedColumn. This may
|
||||
* involve a significant effort, building a new index over any existing data. We perform this task asynchronously;
|
||||
* as it is called as part of a schema update, which we do not want to block for a long period. Building non-custom
|
||||
* indexes is performed on the CompactionManager.
|
||||
*
|
||||
* This class also provides instances of processors which listen to updates to the base table and forward to
|
||||
* registered Indexes the info required to keep those indexes up to date.
|
||||
* There are two variants of these processors, each with a factory method provided by SIM:
|
||||
* IndexTransaction: deals with updates generated on the regular write path.
|
||||
* CleanupTransaction: used when partitions are modified during compaction or cleanup operations.
|
||||
* Further details on their usage and lifecycles can be found in the interface definitions below.
|
||||
*
|
||||
* Finally, the bestIndexFor method is used at query time to identify the most selective index of those able
|
||||
* to satisfy any search predicates defined by a ReadCommand's RowFilter. It returns a thin IndexAccessor object
|
||||
* which enables the ReadCommand to access the appropriate functions of the Index at various stages in its lifecycle.
|
||||
* e.g. the getEstimatedResultRows is required when StorageProxy calculates the initial concurrency factor for
|
||||
* distributing requests to replicas, whereas a Searcher instance is needed when the ReadCommand is executed locally on
|
||||
* a target replica.
|
||||
*/
|
||||
public class SecondaryIndexManager implements IndexRegistry
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(SecondaryIndexManager.class);
|
||||
|
||||
|
||||
private Map<String, Index> indexes = Maps.newConcurrentMap();
|
||||
|
||||
// executes tasks returned by Indexer#addIndexColumn which may require index(es) to be (re)built
|
||||
private static final ExecutorService asyncExecutor =
|
||||
new JMXEnabledThreadPoolExecutor(1,
|
||||
StageManager.KEEPALIVE,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new NamedThreadFactory("SecondaryIndexManagement"),
|
||||
"internal");
|
||||
|
||||
// executes all blocking tasks produced by Indexers e.g. getFlushTask, getMetadataReloadTask etc
|
||||
private static final ExecutorService blockingExecutor = MoreExecutors.newDirectExecutorService();
|
||||
|
||||
/**
|
||||
* The underlying column family containing the source data for these indexes
|
||||
*/
|
||||
public final ColumnFamilyStore baseCfs;
|
||||
|
||||
public SecondaryIndexManager(ColumnFamilyStore baseCfs)
|
||||
{
|
||||
this.baseCfs = baseCfs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Drops and adds new indexes associated with the underlying CF
|
||||
*/
|
||||
public void reload()
|
||||
{
|
||||
// figure out what needs to be added and dropped.
|
||||
Indexes tableIndexes = baseCfs.metadata.getIndexes();
|
||||
indexes.keySet()
|
||||
.stream()
|
||||
.filter(indexName -> !tableIndexes.has(indexName))
|
||||
.forEach(this::removeIndex);
|
||||
|
||||
// we call add for every index definition in the collection as
|
||||
// some may not have been created here yet, only added to schema
|
||||
for (IndexMetadata tableIndex : tableIndexes)
|
||||
addIndex(tableIndex);
|
||||
}
|
||||
|
||||
private Future<?> reloadIndex(IndexMetadata indexDef)
|
||||
{
|
||||
// if the index metadata has changed, reload the index
|
||||
IndexMetadata registered = indexes.get(indexDef.name).getIndexMetadata();
|
||||
if (!registered.equals(indexDef))
|
||||
{
|
||||
Index index = indexes.remove(registered.name);
|
||||
index.register(this);
|
||||
return blockingExecutor.submit(index.getMetadataReloadTask(indexDef));
|
||||
}
|
||||
|
||||
// otherwise, nothing to do
|
||||
return Futures.immediateFuture(null);
|
||||
}
|
||||
|
||||
private Future<?> createIndex(IndexMetadata indexDef)
|
||||
{
|
||||
Index index = createInstance(indexDef);
|
||||
index.register(this);
|
||||
final Callable<?> initialBuildTask = index.getInitializationTask();
|
||||
return initialBuildTask == null
|
||||
? Futures.immediateFuture(null)
|
||||
: asyncExecutor.submit(initialBuildTask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds and builds a index
|
||||
* @param indexDef the IndexMetadata describing the index
|
||||
*/
|
||||
public synchronized Future<?> addIndex(IndexMetadata indexDef)
|
||||
{
|
||||
if (indexes.containsKey(indexDef.name))
|
||||
return reloadIndex(indexDef);
|
||||
else
|
||||
return createIndex(indexDef);
|
||||
}
|
||||
|
||||
public synchronized void removeIndex(String indexName)
|
||||
{
|
||||
Index index = indexes.remove(indexName);
|
||||
if (null != index)
|
||||
{
|
||||
executeBlocking(index.getInvalidateTask());
|
||||
unregisterIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when dropping a Table
|
||||
*/
|
||||
public void markAllIndexesRemoved()
|
||||
{
|
||||
getBuiltIndexNames().forEach(this::markIndexRemoved);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a full, blocking rebuild of the indexes specified by columns from the sstables.
|
||||
* Caller must acquire and release references to the sstables used here.
|
||||
* Note also that only this method of (re)building indexes:
|
||||
* a) takes a set of index *names* rather than Indexers
|
||||
* b) marks exsiting indexes removed prior to rebuilding
|
||||
*
|
||||
* @param sstables the data to build from
|
||||
* @param indexNames the list of indexes to be rebuilt
|
||||
*/
|
||||
public void rebuildIndexesBlocking(Collection<SSTableReader> sstables, Set<String> indexNames)
|
||||
{
|
||||
Set<Index> toRebuild = indexes.values().stream()
|
||||
.filter(indexer -> indexNames.contains(indexer.getIndexName()))
|
||||
.collect(Collectors.toSet());
|
||||
if (toRebuild.isEmpty())
|
||||
{
|
||||
logger.info("No defined indexes with the supplied names");
|
||||
return;
|
||||
}
|
||||
|
||||
toRebuild.forEach(indexer -> markIndexRemoved(indexer.getIndexName()));
|
||||
|
||||
buildIndexesBlocking(sstables, toRebuild);
|
||||
|
||||
toRebuild.forEach(indexer -> markIndexBuilt(indexer.getIndexName()));
|
||||
}
|
||||
|
||||
public void buildAllIndexesBlocking(Collection<SSTableReader> sstables)
|
||||
{
|
||||
buildIndexesBlocking(sstables, ImmutableSet.copyOf(indexes.values()));
|
||||
}
|
||||
|
||||
// For convenience, may be called directly from Index impls
|
||||
public void buildIndexBlocking(Index index)
|
||||
{
|
||||
try (ColumnFamilyStore.RefViewFragment viewFragment = baseCfs.selectAndReference(View.select(SSTableSet.CANONICAL));
|
||||
Refs<SSTableReader> sstables = viewFragment.refs)
|
||||
{
|
||||
buildIndexesBlocking(sstables, Collections.singleton(index));
|
||||
markIndexBuilt(index.getIndexName());
|
||||
}
|
||||
}
|
||||
|
||||
private void buildIndexesBlocking(Collection<SSTableReader> sstables, Set<Index> indexes)
|
||||
{
|
||||
if (indexes.isEmpty())
|
||||
return;
|
||||
|
||||
logger.info("Submitting index build of {} for data in {}",
|
||||
indexes.stream().map(Index::getIndexName).collect(Collectors.joining(",")),
|
||||
sstables.stream().map(SSTableReader::toString).collect(Collectors.joining(",")));
|
||||
|
||||
SecondaryIndexBuilder builder = new SecondaryIndexBuilder(baseCfs,
|
||||
indexes,
|
||||
new ReducingKeyIterator(sstables));
|
||||
Future<?> future = CompactionManager.instance.submitIndexBuild(builder);
|
||||
FBUtilities.waitOnFuture(future);
|
||||
|
||||
flushIndexesBlocking(indexes);
|
||||
logger.info("Index build of {} complete",
|
||||
indexes.stream().map(Index::getIndexName).collect(Collectors.joining(",")));
|
||||
}
|
||||
|
||||
private void markIndexBuilt(String indexName)
|
||||
{
|
||||
SystemKeyspace.setIndexBuilt(baseCfs.name, indexName);
|
||||
}
|
||||
|
||||
private void markIndexRemoved(String indexName)
|
||||
{
|
||||
SystemKeyspace.setIndexRemoved(baseCfs.name, indexName);
|
||||
}
|
||||
|
||||
|
||||
public Index getIndexByName(String indexName)
|
||||
{
|
||||
return indexes.get(indexName);
|
||||
}
|
||||
|
||||
private Index createInstance(IndexMetadata indexDef)
|
||||
{
|
||||
Index newIndex;
|
||||
if (indexDef.isCustom())
|
||||
{
|
||||
assert indexDef.options != null;
|
||||
String className = indexDef.options.get(IndexTarget.CUSTOM_INDEX_OPTION_NAME);
|
||||
assert ! Strings.isNullOrEmpty(className);
|
||||
try
|
||||
{
|
||||
Class<? extends Index> indexClass = FBUtilities.classForName(className, "Index");
|
||||
Constructor ctor = indexClass.getConstructor(ColumnFamilyStore.class, IndexMetadata.class);
|
||||
newIndex = (Index)ctor.newInstance(baseCfs, indexDef);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newIndex = CassandraIndex.newIndex(baseCfs, indexDef);
|
||||
}
|
||||
return newIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate all indexes
|
||||
*/
|
||||
public void truncateAllIndexesBlocking(final long truncatedAt)
|
||||
{
|
||||
executeAllBlocking(indexes.values().stream(), (index) -> index.getTruncateTask(truncatedAt));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all indexes
|
||||
*/
|
||||
public void invalidateAllIndexesBlocking()
|
||||
{
|
||||
executeAllBlocking(indexes.values().stream(), Index::getInvalidateTask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a blocking flush all indexes
|
||||
*/
|
||||
public void flushAllIndexesBlocking()
|
||||
{
|
||||
flushIndexesBlocking(ImmutableSet.copyOf(indexes.values()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a blocking flush of selected indexes
|
||||
*/
|
||||
public void flushIndexesBlocking(Set<Index> indexes)
|
||||
{
|
||||
if (indexes.isEmpty())
|
||||
return;
|
||||
|
||||
List<Future<?>> wait = new ArrayList<>();
|
||||
List<Index> nonCfsIndexes = new ArrayList<>();
|
||||
|
||||
// for each CFS backed index, submit a flush task which we'll wait on for completion
|
||||
// for the non-CFS backed indexes, we'll flush those while we wait.
|
||||
synchronized (baseCfs.getTracker())
|
||||
{
|
||||
indexes.forEach(index ->
|
||||
index.getBackingTable()
|
||||
.map(cfs -> wait.add(cfs.forceFlush()))
|
||||
.orElse(nonCfsIndexes.add(index)));
|
||||
}
|
||||
executeAllBlocking(nonCfsIndexes.stream(), Index::getBlockingFlushTask);
|
||||
FBUtilities.waitOnFutures(wait);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a blocking flush of all custom indexes
|
||||
*/
|
||||
public void flushAllCustomIndexesBlocking()
|
||||
{
|
||||
Set<Index> customIndexers = indexes.values().stream()
|
||||
.filter(index -> !(index instanceof CassandraIndex))
|
||||
.collect(Collectors.toSet());
|
||||
flushIndexesBlocking(customIndexers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all indexes which are marked as built and ready to use
|
||||
*/
|
||||
public List<String> getBuiltIndexNames()
|
||||
{
|
||||
Set<String> allIndexNames = new HashSet<>();
|
||||
indexes.values().stream()
|
||||
.map(Index::getIndexName)
|
||||
.forEach(allIndexNames::add);
|
||||
return SystemKeyspace.getBuiltIndexes(baseCfs.keyspace.getName(), allIndexNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all backing Tables used by registered indexes
|
||||
*/
|
||||
public Set<ColumnFamilyStore> getAllIndexColumnFamilyStores()
|
||||
{
|
||||
Set<ColumnFamilyStore> backingTables = new HashSet<>();
|
||||
indexes.values().forEach(index -> index.getBackingTable().ifPresent(backingTables::add));
|
||||
return backingTables;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if there are ANY indexes registered for this table
|
||||
*/
|
||||
public boolean hasIndexes()
|
||||
{
|
||||
return !indexes.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* When building an index against existing data in sstables, add the given partition to the index
|
||||
*/
|
||||
public void indexPartition(UnfilteredRowIterator partition, OpOrder.Group opGroup, Set<Index> indexes, int nowInSec)
|
||||
{
|
||||
if (!indexes.isEmpty())
|
||||
{
|
||||
DecoratedKey key = partition.partitionKey();
|
||||
Set<Index.Indexer> indexers = indexes.stream()
|
||||
.map(index -> index.indexerFor(key,
|
||||
nowInSec,
|
||||
opGroup,
|
||||
IndexTransaction.Type.UPDATE))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
indexers.forEach(Index.Indexer::begin);
|
||||
|
||||
try (RowIterator filtered = UnfilteredRowIterators.filter(partition, nowInSec))
|
||||
{
|
||||
if (!filtered.staticRow().isEmpty())
|
||||
indexers.forEach(indexer -> indexer.insertRow(filtered.staticRow()));
|
||||
|
||||
while (filtered.hasNext())
|
||||
{
|
||||
Row row = filtered.next();
|
||||
indexers.forEach(indexer -> indexer.insertRow(row));
|
||||
}
|
||||
}
|
||||
|
||||
indexers.forEach(Index.Indexer::finish);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all data from all indexes for this partition.
|
||||
* For when cleanup rips a partition out entirely.
|
||||
*
|
||||
* TODO : improve cleanup transaction to batch updates & perform them async
|
||||
*/
|
||||
public void deletePartition(UnfilteredRowIterator partition, int nowInSec)
|
||||
{
|
||||
// we need to acquire memtable lock because secondary index deletion may
|
||||
// cause a race (see CASSANDRA-3712). This is done internally by the
|
||||
// index transaction when it commits
|
||||
CleanupTransaction indexTransaction = newCleanupTransaction(partition.partitionKey(),
|
||||
partition.columns(),
|
||||
nowInSec);
|
||||
indexTransaction.start();
|
||||
indexTransaction.onPartitionDeletion(partition.partitionLevelDeletion());
|
||||
indexTransaction.commit();
|
||||
|
||||
while (partition.hasNext())
|
||||
{
|
||||
Unfiltered unfiltered = partition.next();
|
||||
if (unfiltered.kind() != Unfiltered.Kind.ROW)
|
||||
continue;
|
||||
|
||||
indexTransaction = newCleanupTransaction(partition.partitionKey(),
|
||||
partition.columns(),
|
||||
nowInSec);
|
||||
indexTransaction.start();
|
||||
indexTransaction.onRowDelete((Row)unfiltered);
|
||||
indexTransaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at query time to find the most selective of the registered index implementation
|
||||
* (i.e. the one likely to return the fewest results) from those registered.
|
||||
* Implementation specific validation of the target expression by the most selective
|
||||
* index should be performed in the searcherFor method to ensure that we pick the right
|
||||
* index regardless of the validity of the expression.
|
||||
*
|
||||
* This method is called at various points during the lifecycle of a ReadCommand (to obtain a Searcher,
|
||||
* get the index's underlying CFS for ReadOrderGroup, or an estimate of the result size from an average index
|
||||
* query).
|
||||
*
|
||||
* Ideally, we would do this relatively expensive operation only once, and attach the index to the
|
||||
* ReadCommand for future reference. This requires the index be passed onto additional commands generated
|
||||
* to process subranges etc.
|
||||
*
|
||||
* @param command ReadCommand to be executed
|
||||
* @return an Index instance, ready to use during execution of the command, or null if none
|
||||
* of the registered indexes can support the command.
|
||||
*/
|
||||
public Index getBestIndexFor(ReadCommand command, boolean includeInTrace)
|
||||
{
|
||||
if (indexes.isEmpty() || command.rowFilter().isEmpty())
|
||||
return null;
|
||||
|
||||
Set<Index> searchableIndexes = new HashSet<>();
|
||||
for (RowFilter.Expression expression : command.rowFilter())
|
||||
{
|
||||
indexes.values().stream()
|
||||
.filter(index -> index.supportsExpression(expression.column(), expression.operator()))
|
||||
.forEach(searchableIndexes::add);
|
||||
}
|
||||
|
||||
if (searchableIndexes.isEmpty())
|
||||
{
|
||||
logger.debug("No applicable indexes found");
|
||||
if (includeInTrace)
|
||||
Tracing.trace("No applicable indexes found");
|
||||
return null;
|
||||
}
|
||||
|
||||
Index selected = searchableIndexes.stream()
|
||||
.max((a, b) -> Longs.compare(a.getEstimatedResultRows(),
|
||||
b.getEstimatedResultRows()))
|
||||
.orElseThrow(() -> new AssertionError("Could not select most selective index"));
|
||||
|
||||
// pay for an additional threadlocal get() rather than build the strings unnecessarily
|
||||
if (includeInTrace && Tracing.isTracing())
|
||||
{
|
||||
Tracing.trace("Index mean cardinalities are {}. Scanning with {}.",
|
||||
searchableIndexes.stream().map(i -> i.getIndexName() + ':' + i.getEstimatedResultRows())
|
||||
.collect(Collectors.joining(",")),
|
||||
selected.getIndexName());
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
// convenience method which doesn't emit tracing messages
|
||||
public Index getBestIndexFor(ReadCommand command)
|
||||
{
|
||||
return getBestIndexFor(command, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at write time to ensure that values present in the update
|
||||
* are valid according to the rules of all registered indexes which
|
||||
* will process it. The partition key as well as the clustering and
|
||||
* cell values for each row in the update may be checked by index
|
||||
* implementations
|
||||
* @param update PartitionUpdate containing the values to be validated by registered Index implementations
|
||||
* @throws InvalidRequestException
|
||||
*/
|
||||
public void validate(PartitionUpdate update) throws InvalidRequestException
|
||||
{
|
||||
indexes.values()
|
||||
.stream()
|
||||
.filter(i -> i.indexes(update.columns()))
|
||||
.forEach(i -> i.validate(update));
|
||||
}
|
||||
|
||||
/**
|
||||
* IndexRegistry methods
|
||||
*/
|
||||
public void registerIndex(Index index)
|
||||
{
|
||||
indexes.put(index.getIndexMetadata().name, index);
|
||||
logger.debug("Registered index {}", index.getIndexMetadata().name);
|
||||
}
|
||||
|
||||
public void unregisterIndex(Index index)
|
||||
{
|
||||
Index removed = indexes.remove(index.getIndexMetadata().name);
|
||||
logger.debug(removed == null ? "Index {} was not registered" : "Removed index {} from registry",
|
||||
index.getIndexMetadata().name);
|
||||
}
|
||||
|
||||
public Index getIndex(IndexMetadata metadata)
|
||||
{
|
||||
return indexes.get(metadata.name);
|
||||
}
|
||||
|
||||
public Collection<Index> listIndexes()
|
||||
{
|
||||
return ImmutableSet.copyOf(indexes.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handling of index updates.
|
||||
* Implementations of the various IndexTransaction interfaces, for keeping indexes in sync with base data
|
||||
* during updates, compaction and cleanup. Plus factory methods for obtaining transaction instances.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Transaction for updates on the write path.
|
||||
*/
|
||||
public UpdateTransaction newUpdateTransaction(PartitionUpdate update, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
if (!hasIndexes())
|
||||
return UpdateTransaction.NO_OP;
|
||||
|
||||
// todo : optimize lookup, we can probably cache quite a bit of stuff, rather than doing
|
||||
// a linear scan every time. Holding off that though until CASSANDRA-7771 to figure out
|
||||
// exactly how indexes are to be identified & associated with a given partition update
|
||||
Index.Indexer[] indexers = indexes.values().stream()
|
||||
.filter(i -> i.indexes(update.columns()))
|
||||
.map(i -> i.indexerFor(update.partitionKey(),
|
||||
nowInSec,
|
||||
opGroup,
|
||||
IndexTransaction.Type.UPDATE))
|
||||
.toArray(Index.Indexer[]::new);
|
||||
|
||||
return indexers.length == 0 ? UpdateTransaction.NO_OP : new WriteTimeTransaction(indexers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction for use when merging rows during compaction
|
||||
*/
|
||||
public CompactionTransaction newCompactionTransaction(DecoratedKey key,
|
||||
PartitionColumns partitionColumns,
|
||||
int versions,
|
||||
int nowInSec)
|
||||
{
|
||||
// the check for whether there are any registered indexes is already done in CompactionIterator
|
||||
|
||||
Index[] interestedIndexes = indexes.values().stream()
|
||||
.filter(i -> i.indexes(partitionColumns))
|
||||
.toArray(Index[]::new);
|
||||
|
||||
return interestedIndexes.length == 0
|
||||
? CompactionTransaction.NO_OP
|
||||
: new IndexGCTransaction(key, versions, nowInSec, interestedIndexes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction for use when removing partitions during cleanup
|
||||
*/
|
||||
public CleanupTransaction newCleanupTransaction(DecoratedKey key,
|
||||
PartitionColumns partitionColumns,
|
||||
int nowInSec)
|
||||
{
|
||||
//
|
||||
if (!hasIndexes())
|
||||
return CleanupTransaction.NO_OP;
|
||||
|
||||
Index[] interestedIndexes = indexes.values().stream()
|
||||
.filter(i -> i.indexes(partitionColumns))
|
||||
.toArray(Index[]::new);
|
||||
|
||||
return interestedIndexes.length == 0
|
||||
? CleanupTransaction.NO_OP
|
||||
: new CleanupGCTransaction(key, nowInSec, interestedIndexes);
|
||||
}
|
||||
|
||||
/**
|
||||
* A single use transaction for processing a partition update on the regular write path
|
||||
*/
|
||||
private static final class WriteTimeTransaction implements UpdateTransaction
|
||||
{
|
||||
private final Index.Indexer[] indexers;
|
||||
|
||||
private WriteTimeTransaction(Index.Indexer...indexers)
|
||||
{
|
||||
// don't allow null indexers, if we don't need any use a NullUpdater object
|
||||
for (Index.Indexer indexer : indexers) assert indexer != null;
|
||||
this.indexers = indexers;
|
||||
}
|
||||
|
||||
public void start()
|
||||
{
|
||||
Arrays.stream(indexers).forEach(Index.Indexer::begin);
|
||||
}
|
||||
|
||||
public void onPartitionDeletion(DeletionTime deletionTime)
|
||||
{
|
||||
Arrays.stream(indexers).forEach(h -> h.partitionDelete(deletionTime));
|
||||
}
|
||||
|
||||
public void onRangeTombstone(RangeTombstone tombstone)
|
||||
{
|
||||
Arrays.stream(indexers) .forEach(h -> h.rangeTombstone(tombstone));
|
||||
}
|
||||
|
||||
public void onInserted(Row row)
|
||||
{
|
||||
Arrays.stream(indexers).forEach(h -> h.insertRow(row));
|
||||
}
|
||||
|
||||
public void onUpdated(Row existing, Row updated)
|
||||
{
|
||||
final Row.Builder toRemove = BTreeRow.sortedBuilder(existing.columns());
|
||||
toRemove.newRow(existing.clustering());
|
||||
final Row.Builder toInsert = BTreeRow.sortedBuilder(updated.columns());
|
||||
toInsert.newRow(updated.clustering());
|
||||
// diff listener collates the columns to be added & removed from the indexes
|
||||
RowDiffListener diffListener = new RowDiffListener()
|
||||
{
|
||||
public void onPrimaryKeyLivenessInfo(int i, Clustering clustering, LivenessInfo merged, LivenessInfo original)
|
||||
{
|
||||
if (merged != null && merged != original)
|
||||
toInsert.addPrimaryKeyLivenessInfo(merged);
|
||||
}
|
||||
|
||||
public void onDeletion(int i, Clustering clustering, DeletionTime merged, DeletionTime original)
|
||||
{
|
||||
}
|
||||
|
||||
public void onComplexDeletion(int i, Clustering clustering, ColumnDefinition column, DeletionTime merged, DeletionTime original)
|
||||
{
|
||||
}
|
||||
|
||||
public void onCell(int i, Clustering clustering, Cell merged, Cell original)
|
||||
{
|
||||
if (merged != null && merged != original)
|
||||
toInsert.addCell(merged);
|
||||
|
||||
if (merged == null || (original != null && shouldCleanupOldValue(original, merged)))
|
||||
toRemove.addCell(original);
|
||||
|
||||
}
|
||||
};
|
||||
Rows.diff(diffListener, updated, updated.columns().mergeTo(existing.columns()), existing);
|
||||
Row oldRow = toRemove.build();
|
||||
Row newRow = toInsert.build();
|
||||
Arrays.stream(indexers).forEach(i -> i.updateRow(oldRow, newRow));
|
||||
}
|
||||
|
||||
public void commit()
|
||||
{
|
||||
Arrays.stream(indexers).forEach(Index.Indexer::finish);
|
||||
}
|
||||
|
||||
private boolean shouldCleanupOldValue(Cell oldCell, Cell newCell)
|
||||
{
|
||||
// If either the value or timestamp is different, then we
|
||||
// should delete from the index. If not, then we can infer that
|
||||
// at least one of the cells is an ExpiringColumn and that the
|
||||
// difference is in the expiry time. In this case, we don't want to
|
||||
// delete the old value from the index as the tombstone we insert
|
||||
// will just hide the inserted value.
|
||||
// Completely identical cells (including expiring columns with
|
||||
// identical ttl & localExpirationTime) will not get this far due
|
||||
// to the oldCell.equals(newCell) in StandardUpdater.update
|
||||
return !oldCell.value().equals(newCell.value()) || oldCell.timestamp() != newCell.timestamp();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A single-use transaction for updating indexes for a single partition during compaction where the only
|
||||
* operation is to merge rows
|
||||
* TODO : make this smarter at batching updates so we can use a single transaction to process multiple rows in
|
||||
* a single partition
|
||||
*/
|
||||
private static final class IndexGCTransaction implements CompactionTransaction
|
||||
{
|
||||
private final DecoratedKey key;
|
||||
private final int versions;
|
||||
private final int nowInSec;
|
||||
private final Index[] indexes;
|
||||
|
||||
private Row[] rows;
|
||||
|
||||
private IndexGCTransaction(DecoratedKey key,
|
||||
int versions,
|
||||
int nowInSec,
|
||||
Index...indexes)
|
||||
{
|
||||
// don't allow null indexers, if we don't have any, use a noop transaction
|
||||
for (Index index : indexes) assert index != null;
|
||||
|
||||
this.key = key;
|
||||
this.versions = versions;
|
||||
this.indexes = indexes;
|
||||
this.nowInSec = nowInSec;
|
||||
}
|
||||
|
||||
public void start()
|
||||
{
|
||||
if (versions > 0)
|
||||
rows = new Row[versions];
|
||||
}
|
||||
|
||||
public void onRowMerge(Columns columns, Row merged, Row...versions)
|
||||
{
|
||||
// Diff listener constructs rows representing deltas between the merged and original versions
|
||||
// These delta rows are then passed to registered indexes for removal processing
|
||||
final Row.Builder[] builders = new Row.Builder[versions.length];
|
||||
RowDiffListener diffListener = new RowDiffListener()
|
||||
{
|
||||
public void onPrimaryKeyLivenessInfo(int i, Clustering clustering, LivenessInfo merged, LivenessInfo original)
|
||||
{
|
||||
}
|
||||
|
||||
public void onDeletion(int i, Clustering clustering, DeletionTime merged, DeletionTime original)
|
||||
{
|
||||
}
|
||||
|
||||
public void onComplexDeletion(int i, Clustering clustering, ColumnDefinition column, DeletionTime merged, DeletionTime original)
|
||||
{
|
||||
}
|
||||
|
||||
public void onCell(int i, Clustering clustering, Cell merged, Cell original)
|
||||
{
|
||||
if (original != null && merged == null)
|
||||
{
|
||||
if (builders[i] == null)
|
||||
{
|
||||
builders[i] = BTreeRow.sortedBuilder(columns);
|
||||
builders[i].newRow(clustering);
|
||||
}
|
||||
builders[i].addCell(original);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Rows.diff(diffListener, merged, columns, versions);
|
||||
|
||||
for(int i = 0; i < builders.length; i++)
|
||||
if (builders[i] != null)
|
||||
rows[i] = builders[i].build();
|
||||
}
|
||||
|
||||
public void commit()
|
||||
{
|
||||
if (rows == null)
|
||||
return;
|
||||
|
||||
try (OpOrder.Group opGroup = Keyspace.writeOrder.start())
|
||||
{
|
||||
Index.Indexer[] indexers = Arrays.stream(indexes)
|
||||
.map(i -> i.indexerFor(key, nowInSec, opGroup, Type.COMPACTION))
|
||||
.toArray(Index.Indexer[]::new);
|
||||
|
||||
Arrays.stream(indexers).forEach(Index.Indexer::begin);
|
||||
|
||||
for (Row row : rows)
|
||||
if (row != null)
|
||||
Arrays.stream(indexers).forEach(indexer -> indexer.removeRow(row));
|
||||
|
||||
Arrays.stream(indexers).forEach(Index.Indexer::finish);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A single-use transaction for updating indexes for a single partition during cleanup, where
|
||||
* partitions and rows are only removed
|
||||
* TODO : make this smarter at batching updates so we can use a single transaction to process multiple rows in
|
||||
* a single partition
|
||||
*/
|
||||
private static final class CleanupGCTransaction implements CleanupTransaction
|
||||
{
|
||||
private final DecoratedKey key;
|
||||
private final int nowInSec;
|
||||
private final Index[] indexes;
|
||||
|
||||
private Row row;
|
||||
private DeletionTime partitionDelete;
|
||||
|
||||
private CleanupGCTransaction(DecoratedKey key,
|
||||
int nowInSec,
|
||||
Index...indexes)
|
||||
{
|
||||
// don't allow null indexers, if we don't have any, use a noop transaction
|
||||
for (Index index : indexes) assert index != null;
|
||||
|
||||
this.key = key;
|
||||
this.indexes = indexes;
|
||||
this.nowInSec = nowInSec;
|
||||
}
|
||||
|
||||
public void start()
|
||||
{
|
||||
}
|
||||
|
||||
public void onPartitionDeletion(DeletionTime deletionTime)
|
||||
{
|
||||
partitionDelete = deletionTime;
|
||||
}
|
||||
|
||||
public void onRowDelete(Row row)
|
||||
{
|
||||
this.row = row;
|
||||
}
|
||||
|
||||
public void commit()
|
||||
{
|
||||
if (row == null && partitionDelete == null)
|
||||
return;
|
||||
|
||||
try (OpOrder.Group opGroup = Keyspace.writeOrder.start())
|
||||
{
|
||||
Index.Indexer[] indexers = Arrays.stream(indexes)
|
||||
.map(i -> i.indexerFor(key, nowInSec, opGroup, Type.CLEANUP))
|
||||
.toArray(Index.Indexer[]::new);
|
||||
|
||||
Arrays.stream(indexers).forEach(Index.Indexer::begin);
|
||||
|
||||
if (partitionDelete != null)
|
||||
Arrays.stream(indexers).forEach(indexer -> indexer.partitionDelete(partitionDelete));
|
||||
|
||||
if (row != null)
|
||||
Arrays.stream(indexers).forEach(indexer -> indexer.removeRow(row));
|
||||
|
||||
Arrays.stream(indexers).forEach(Index.Indexer::finish);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void executeBlocking(Callable<?> task)
|
||||
{
|
||||
if (null != task)
|
||||
FBUtilities.waitOnFuture(blockingExecutor.submit(task));
|
||||
}
|
||||
|
||||
private static void executeAllBlocking(Stream<Index> indexers, Function<Index, Callable<?>> function)
|
||||
{
|
||||
List<Future<?>> waitFor = new ArrayList<>();
|
||||
indexers.forEach(indexer -> {
|
||||
Callable<?> task = function.apply(indexer);
|
||||
if (null != task)
|
||||
waitFor.add(blockingExecutor.submit(task));
|
||||
});
|
||||
FBUtilities.waitOnFutures(waitFor);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,755 @@
|
|||
package org.apache.cassandra.index.internal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
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.Operator;
|
||||
import org.apache.cassandra.cql3.statements.IndexTarget;
|
||||
import org.apache.cassandra.db.*;
|
||||
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.concurrent.OpOrder;
|
||||
import org.apache.cassandra.utils.concurrent.Refs;
|
||||
|
||||
/**
|
||||
* Index implementation which indexes the values for a single column in the base
|
||||
* table and which stores its index data in a local, hidden table.
|
||||
*/
|
||||
public abstract class CassandraIndex implements Index
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(CassandraIndex.class);
|
||||
|
||||
public final ColumnFamilyStore baseCfs;
|
||||
protected IndexMetadata metadata;
|
||||
protected ColumnFamilyStore indexCfs;
|
||||
protected ColumnDefinition indexedColumn;
|
||||
protected CassandraIndexFunctions functions;
|
||||
|
||||
public CassandraIndex(ColumnFamilyStore baseCfs, IndexMetadata indexDef)
|
||||
{
|
||||
this.baseCfs = baseCfs;
|
||||
setMetadata(indexDef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if an index of this type can support search predicates of the form [column] OPERATOR [value]
|
||||
* @param indexedColumn
|
||||
* @param operator
|
||||
* @return
|
||||
*/
|
||||
protected boolean supportsOperator(ColumnDefinition indexedColumn, Operator operator)
|
||||
{
|
||||
return operator.equals(Operator.EQ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to construct an the clustering for an entry in the index table based on values from the base data.
|
||||
* 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
|
||||
* @return a clustering prefix to be used to insert into the index table
|
||||
*/
|
||||
protected abstract CBuilder buildIndexClusteringPrefix(ByteBuffer partitionKey,
|
||||
ClusteringPrefix prefix,
|
||||
CellPath path);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public abstract IndexEntry decodeEntry(DecoratedKey indexedValue,
|
||||
Row indexEntry);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return true if the index is out of date and the entry should be dropped
|
||||
*/
|
||||
public abstract boolean isStale(Row row, ByteBuffer indexValue, int nowInSec);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param cellValue from the primary data
|
||||
* @return a ByteBuffer containing the value to be inserted in the index. This will be used to make the partition
|
||||
* key in the index table
|
||||
*/
|
||||
protected abstract ByteBuffer getIndexedValue(ByteBuffer partitionKey,
|
||||
Clustering clustering,
|
||||
CellPath path,
|
||||
ByteBuffer cellValue);
|
||||
|
||||
public ColumnDefinition getIndexedColumn()
|
||||
{
|
||||
return indexedColumn;
|
||||
}
|
||||
|
||||
public ClusteringComparator getIndexComparator()
|
||||
{
|
||||
return indexCfs.metadata.comparator;
|
||||
}
|
||||
|
||||
public ColumnFamilyStore getIndexCfs()
|
||||
{
|
||||
return indexCfs;
|
||||
}
|
||||
|
||||
public void register(IndexRegistry registry)
|
||||
{
|
||||
registry.registerIndex(this);
|
||||
}
|
||||
|
||||
public Callable<?> getInitializationTask()
|
||||
{
|
||||
// if we're just linking in the index on an already-built index post-restart
|
||||
// we've nothing to do. Otherwise, submit for building via SecondaryIndexBuilder
|
||||
return isBuilt() ? null : getBuildIndexTask();
|
||||
}
|
||||
|
||||
public IndexMetadata getIndexMetadata()
|
||||
{
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public String getIndexName()
|
||||
{
|
||||
// should return metadata.name, see CASSANDRA-10127
|
||||
return indexCfs.name;
|
||||
}
|
||||
|
||||
public Optional<ColumnFamilyStore> getBackingTable()
|
||||
{
|
||||
return indexCfs == null ? Optional.empty() : Optional.of(indexCfs);
|
||||
}
|
||||
|
||||
public Callable<Void> getBlockingFlushTask()
|
||||
{
|
||||
return () -> {
|
||||
indexCfs.forceBlockingFlush();
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
public Callable<?> getInvalidateTask()
|
||||
{
|
||||
return () -> {
|
||||
markRemoved();
|
||||
invalidate();
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
public Callable<?> getMetadataReloadTask(IndexMetadata indexDef)
|
||||
{
|
||||
setMetadata(indexDef);
|
||||
return () -> {
|
||||
indexCfs.metadata.reloadIndexMetadataProperties(baseCfs.metadata);
|
||||
indexCfs.reload();
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
private void setMetadata(IndexMetadata indexDef)
|
||||
{
|
||||
metadata = indexDef;
|
||||
functions = getFunctions(baseCfs.metadata, indexDef);
|
||||
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);
|
||||
}
|
||||
|
||||
public Callable<?> getTruncateTask(final long truncatedAt)
|
||||
{
|
||||
return () -> {
|
||||
indexCfs.discardSSTables(truncatedAt);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
public boolean indexes(PartitionColumns columns)
|
||||
{
|
||||
// if we have indexes on the partition key or clustering columns, return true
|
||||
return isPrimaryKeyIndex() || columns.contains(indexedColumn);
|
||||
}
|
||||
|
||||
public boolean supportsExpression(ColumnDefinition column, Operator operator)
|
||||
{
|
||||
return indexedColumn.name.equals(column.name)
|
||||
&& supportsOperator(indexedColumn, operator);
|
||||
}
|
||||
|
||||
private boolean supportsExpression(RowFilter.Expression expression)
|
||||
{
|
||||
return supportsExpression(expression.column(), expression.operator());
|
||||
}
|
||||
|
||||
public long getEstimatedResultRows()
|
||||
{
|
||||
return indexCfs.getMeanColumns();
|
||||
}
|
||||
|
||||
/**
|
||||
* No post processing of query results, just return them unchanged
|
||||
*/
|
||||
public BiFunction<PartitionIterator, ReadCommand, PartitionIterator> postProcessorFor(ReadCommand command)
|
||||
{
|
||||
return (partitionIterator, readCommand) -> partitionIterator;
|
||||
}
|
||||
|
||||
public RowFilter getPostIndexQueryFilter(RowFilter filter)
|
||||
{
|
||||
return getTargetExpression(filter.getExpressions()).map(filter::without)
|
||||
.orElse(filter);
|
||||
}
|
||||
|
||||
private Optional<RowFilter.Expression> getTargetExpression(List<RowFilter.Expression> expressions)
|
||||
{
|
||||
return expressions.stream().filter(this::supportsExpression).findFirst();
|
||||
}
|
||||
|
||||
public Index.Searcher searcherFor(ReadCommand command)
|
||||
{
|
||||
Optional<RowFilter.Expression> target = getTargetExpression(command.rowFilter().getExpressions());
|
||||
|
||||
if (target.isPresent())
|
||||
{
|
||||
target.get().validateForIndexing();
|
||||
switch (getIndexMetadata().indexType)
|
||||
{
|
||||
case COMPOSITES:
|
||||
return new CompositesSearcher(command, target.get(), this);
|
||||
case KEYS:
|
||||
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.name,
|
||||
indexedColumn.name.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
public void validate(PartitionUpdate update) throws InvalidRequestException
|
||||
{
|
||||
switch (indexedColumn.kind)
|
||||
{
|
||||
case PARTITION_KEY:
|
||||
validatePartitionKey(update.partitionKey());
|
||||
break;
|
||||
case CLUSTERING:
|
||||
validateClusterings(update);
|
||||
break;
|
||||
case REGULAR:
|
||||
validateRows(update);
|
||||
break;
|
||||
case STATIC:
|
||||
validateRows(Collections.singleton(update.staticRow()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public Indexer indexerFor(final DecoratedKey key,
|
||||
final int nowInSec,
|
||||
final OpOrder.Group opGroup,
|
||||
final IndexTransaction.Type transactionType)
|
||||
{
|
||||
return new Indexer()
|
||||
{
|
||||
public void begin()
|
||||
{
|
||||
}
|
||||
|
||||
public void partitionDelete(DeletionTime deletionTime)
|
||||
{
|
||||
}
|
||||
|
||||
public void rangeTombstone(RangeTombstone tombstone)
|
||||
{
|
||||
}
|
||||
|
||||
public void insertRow(Row row)
|
||||
{
|
||||
if (isPrimaryKeyIndex())
|
||||
{
|
||||
indexPrimaryKey(row.clustering(),
|
||||
getPrimaryKeyIndexLiveness(row),
|
||||
row.deletion());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (indexedColumn.isComplex())
|
||||
indexCells(row.clustering(), row.getComplexColumnData(indexedColumn));
|
||||
else
|
||||
indexCell(row.clustering(), row.getCell(indexedColumn));
|
||||
}
|
||||
}
|
||||
|
||||
public void removeRow(Row row)
|
||||
{
|
||||
if (isPrimaryKeyIndex())
|
||||
indexPrimaryKey(row.clustering(), row.primaryKeyLivenessInfo(), row.deletion());
|
||||
|
||||
if (indexedColumn.isComplex())
|
||||
removeCells(row.clustering(), row.getComplexColumnData(indexedColumn));
|
||||
else
|
||||
removeCell(row.clustering(), row.getCell(indexedColumn));
|
||||
}
|
||||
|
||||
|
||||
public void updateRow(Row oldRow, Row newRow)
|
||||
{
|
||||
if (isPrimaryKeyIndex())
|
||||
indexPrimaryKey(newRow.clustering(),
|
||||
newRow.primaryKeyLivenessInfo(),
|
||||
newRow.deletion());
|
||||
|
||||
if (indexedColumn.isComplex())
|
||||
{
|
||||
indexCells(newRow.clustering(), newRow.getComplexColumnData(indexedColumn));
|
||||
removeCells(oldRow.clustering(), oldRow.getComplexColumnData(indexedColumn));
|
||||
}
|
||||
else
|
||||
{
|
||||
indexCell(newRow.clustering(), newRow.getCell(indexedColumn));
|
||||
removeCell(oldRow.clustering(), oldRow.getCell(indexedColumn));
|
||||
}
|
||||
}
|
||||
|
||||
public void finish()
|
||||
{
|
||||
}
|
||||
|
||||
private void indexCells(Clustering clustering, Iterable<Cell> cells)
|
||||
{
|
||||
if (cells == null)
|
||||
return;
|
||||
|
||||
for (Cell cell : cells)
|
||||
indexCell(clustering, cell);
|
||||
}
|
||||
|
||||
private void indexCell(Clustering clustering, Cell cell)
|
||||
{
|
||||
if (cell == null || !cell.isLive(nowInSec))
|
||||
return;
|
||||
|
||||
insert(key.getKey(),
|
||||
clustering,
|
||||
cell,
|
||||
LivenessInfo.create(cell.timestamp(), cell.ttl(), cell.localDeletionTime()),
|
||||
opGroup);
|
||||
}
|
||||
|
||||
private void removeCells(Clustering clustering, Iterable<Cell> cells)
|
||||
{
|
||||
if (cells == null)
|
||||
return;
|
||||
|
||||
for (Cell cell : cells)
|
||||
removeCell(clustering, cell);
|
||||
}
|
||||
|
||||
private void removeCell(Clustering clustering, Cell cell)
|
||||
{
|
||||
if (cell == null || !cell.isLive(nowInSec))
|
||||
return;
|
||||
|
||||
delete(key.getKey(), clustering, cell, opGroup, nowInSec);
|
||||
}
|
||||
|
||||
private void indexPrimaryKey(final Clustering clustering,
|
||||
final LivenessInfo liveness,
|
||||
final DeletionTime deletion)
|
||||
{
|
||||
if (liveness.timestamp() != LivenessInfo.NO_TIMESTAMP)
|
||||
insert(key.getKey(), clustering, null, liveness, opGroup);
|
||||
|
||||
if (!deletion.isLive())
|
||||
delete(key.getKey(), clustering, deletion, opGroup);
|
||||
}
|
||||
|
||||
private LivenessInfo getPrimaryKeyIndexLiveness(Row row)
|
||||
{
|
||||
long timestamp = row.primaryKeyLivenessInfo().timestamp();
|
||||
int ttl = row.primaryKeyLivenessInfo().ttl();
|
||||
for (Cell cell : row.cells())
|
||||
{
|
||||
long cellTimestamp = cell.timestamp();
|
||||
if (cell.isLive(nowInSec))
|
||||
{
|
||||
if (cellTimestamp > timestamp)
|
||||
{
|
||||
timestamp = cellTimestamp;
|
||||
ttl = cell.ttl();
|
||||
}
|
||||
}
|
||||
}
|
||||
return LivenessInfo.create(baseCfs.metadata, timestamp, ttl, nowInSec);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific to internal indexes, this is called by a
|
||||
* searcher when it encounters a stale entry in the index
|
||||
* @param indexKey the partition key in the index table
|
||||
* @param indexClustering the clustering in the index table
|
||||
* @param deletion deletion timestamp etc
|
||||
* @param opGroup the operation under which to perform the deletion
|
||||
*/
|
||||
public void deleteStaleEntry(DecoratedKey indexKey,
|
||||
Clustering indexClustering,
|
||||
DeletionTime deletion,
|
||||
OpOrder.Group opGroup)
|
||||
{
|
||||
doDelete(indexKey, indexClustering, deletion, opGroup);
|
||||
logger.debug("Removed index entry for stale value {}", indexKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when adding a new entry to the index
|
||||
*/
|
||||
private void insert(ByteBuffer rowKey,
|
||||
Clustering clustering,
|
||||
Cell cell,
|
||||
LivenessInfo info,
|
||||
OpOrder.Group opGroup)
|
||||
{
|
||||
DecoratedKey valueKey = getIndexKeyFor(getIndexedValue(rowKey,
|
||||
clustering,
|
||||
cell));
|
||||
Row row = BTreeRow.noCellLiveRow(buildIndexClustering(rowKey, clustering, cell), info);
|
||||
PartitionUpdate upd = partitionUpdate(valueKey, row);
|
||||
indexCfs.apply(upd, UpdateTransaction.NO_OP, opGroup, null);
|
||||
logger.debug("Inserted entry into index for value {}", valueKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when deleting entries on non-primary key columns
|
||||
*/
|
||||
private void delete(ByteBuffer rowKey,
|
||||
Clustering clustering,
|
||||
Cell cell,
|
||||
OpOrder.Group opGroup,
|
||||
int nowInSec)
|
||||
{
|
||||
DecoratedKey valueKey = getIndexKeyFor(getIndexedValue(rowKey,
|
||||
clustering,
|
||||
cell));
|
||||
doDelete(valueKey,
|
||||
buildIndexClustering(rowKey, clustering, cell),
|
||||
new DeletionTime(cell.timestamp(), nowInSec),
|
||||
opGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when deleting entries from indexes on primary key columns
|
||||
*/
|
||||
private void delete(ByteBuffer rowKey,
|
||||
Clustering clustering,
|
||||
DeletionTime deletion,
|
||||
OpOrder.Group opGroup)
|
||||
{
|
||||
DecoratedKey valueKey = getIndexKeyFor(getIndexedValue(rowKey,
|
||||
clustering,
|
||||
null));
|
||||
doDelete(valueKey,
|
||||
buildIndexClustering(rowKey, clustering, null),
|
||||
deletion,
|
||||
opGroup);
|
||||
}
|
||||
|
||||
private void doDelete(DecoratedKey indexKey,
|
||||
Clustering indexClustering,
|
||||
DeletionTime deletion,
|
||||
OpOrder.Group opGroup)
|
||||
{
|
||||
Row row = BTreeRow.emptyDeletedRow(indexClustering, deletion);
|
||||
PartitionUpdate upd = partitionUpdate(indexKey, row);
|
||||
indexCfs.apply(upd, UpdateTransaction.NO_OP, opGroup, null);
|
||||
logger.debug("Removed index entry for value {}", indexKey);
|
||||
}
|
||||
|
||||
private void validatePartitionKey(DecoratedKey partitionKey) throws InvalidRequestException
|
||||
{
|
||||
assert indexedColumn.isPartitionKey();
|
||||
validateIndexedValue(getIndexedValue(partitionKey.getKey(), null, null ));
|
||||
}
|
||||
|
||||
private void validateClusterings(PartitionUpdate update) throws InvalidRequestException
|
||||
{
|
||||
assert indexedColumn.isClusteringColumn();
|
||||
for (Row row : update)
|
||||
validateIndexedValue(getIndexedValue(null, row.clustering(), null));
|
||||
}
|
||||
|
||||
private void validateRows(Iterable<Row> rows)
|
||||
{
|
||||
assert !indexedColumn.isPrimaryKeyColumn();
|
||||
for (Row row : rows)
|
||||
{
|
||||
if (indexedColumn.isComplex())
|
||||
{
|
||||
ComplexColumnData data = row.getComplexColumnData(indexedColumn);
|
||||
if (data != null)
|
||||
{
|
||||
for (Cell cell : data)
|
||||
{
|
||||
validateIndexedValue(getIndexedValue(null, null, cell.path(), cell.value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
validateIndexedValue(getIndexedValue(null, null, row.getCell(indexedColumn)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateIndexedValue(ByteBuffer value)
|
||||
{
|
||||
if (value != null && value.remaining() >= FBUtilities.MAX_UNSIGNED_SHORT)
|
||||
throw new InvalidRequestException(String.format(
|
||||
"Cannot index value of size %d for index %s on %s.%s(%s) (maximum allowed size=%d)",
|
||||
value.remaining(),
|
||||
getIndexName(),
|
||||
baseCfs.metadata.ksName,
|
||||
baseCfs.metadata.cfName,
|
||||
indexedColumn.name.toString(),
|
||||
FBUtilities.MAX_UNSIGNED_SHORT));
|
||||
}
|
||||
|
||||
private ByteBuffer getIndexedValue(ByteBuffer rowKey,
|
||||
Clustering clustering,
|
||||
Cell cell)
|
||||
{
|
||||
return getIndexedValue(rowKey,
|
||||
clustering,
|
||||
cell == null ? null : cell.path(),
|
||||
cell == null ? null : cell.value()
|
||||
);
|
||||
}
|
||||
|
||||
private Clustering buildIndexClustering(ByteBuffer rowKey,
|
||||
Clustering clustering,
|
||||
Cell cell)
|
||||
{
|
||||
return buildIndexClusteringPrefix(rowKey,
|
||||
clustering,
|
||||
cell == null ? null : cell.path()).build();
|
||||
}
|
||||
|
||||
private DecoratedKey getIndexKeyFor(ByteBuffer value)
|
||||
{
|
||||
return indexCfs.decorateKey(value);
|
||||
}
|
||||
|
||||
private PartitionUpdate partitionUpdate(DecoratedKey valueKey, Row row)
|
||||
{
|
||||
return PartitionUpdate.singleRowUpdate(indexCfs.metadata, valueKey, row);
|
||||
}
|
||||
|
||||
private void invalidate()
|
||||
{
|
||||
// interrupt in-progress compactions
|
||||
Collection<ColumnFamilyStore> cfss = Collections.singleton(indexCfs);
|
||||
CompactionManager.instance.interruptCompactionForCFs(cfss, true);
|
||||
CompactionManager.instance.waitForCessation(cfss);
|
||||
indexCfs.keyspace.writeOrder.awaitNewBarrier();
|
||||
indexCfs.forceBlockingFlush();
|
||||
indexCfs.readOrdering.awaitNewBarrier();
|
||||
indexCfs.invalidate();
|
||||
}
|
||||
|
||||
private boolean isBuilt()
|
||||
{
|
||||
return SystemKeyspace.isIndexBuilt(baseCfs.keyspace.getName(), getIndexName());
|
||||
}
|
||||
|
||||
private void markBuilt()
|
||||
{
|
||||
SystemKeyspace.setIndexBuilt(baseCfs.keyspace.getName(), getIndexName());
|
||||
}
|
||||
|
||||
private void markRemoved()
|
||||
{
|
||||
SystemKeyspace.setIndexRemoved(baseCfs.keyspace.getName(), getIndexName());
|
||||
}
|
||||
|
||||
private boolean isPrimaryKeyIndex()
|
||||
{
|
||||
return indexedColumn.isPrimaryKeyColumn();
|
||||
}
|
||||
|
||||
private Callable<?> getBuildIndexTask()
|
||||
{
|
||||
return () -> {
|
||||
buildBlocking();
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
private void buildBlocking()
|
||||
{
|
||||
baseCfs.forceBlockingFlush();
|
||||
|
||||
try (ColumnFamilyStore.RefViewFragment viewFragment = baseCfs.selectAndReference(View.select(SSTableSet.CANONICAL));
|
||||
Refs<SSTableReader> sstables = viewFragment.refs)
|
||||
{
|
||||
if (sstables.isEmpty())
|
||||
{
|
||||
logger.info("No SSTable data for {}.{} to build index {} from, marking empty index as built",
|
||||
baseCfs.metadata.ksName,
|
||||
baseCfs.metadata.cfName,
|
||||
getIndexName());
|
||||
markBuilt();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Submitting index build of {} for data in {}",
|
||||
getIndexName(),
|
||||
getSSTableNames(sstables));
|
||||
|
||||
SecondaryIndexBuilder builder = new SecondaryIndexBuilder(baseCfs,
|
||||
Collections.singleton(this),
|
||||
new ReducingKeyIterator(sstables));
|
||||
Future<?> future = CompactionManager.instance.submitIndexBuild(builder);
|
||||
FBUtilities.waitOnFuture(future);
|
||||
indexCfs.forceBlockingFlush();
|
||||
markBuilt();
|
||||
}
|
||||
logger.info("Index build of {} complete", getIndexName());
|
||||
}
|
||||
|
||||
private static String getSSTableNames(Collection<SSTableReader> sstables)
|
||||
{
|
||||
return StreamSupport.stream(sstables.spliterator(), false)
|
||||
.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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.index.internal;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.db.marshal.CompositeType;
|
||||
import org.apache.cassandra.index.internal.composites.*;
|
||||
import org.apache.cassandra.index.internal.keys.KeysIndex;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
|
||||
public interface CassandraIndexFunctions
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @param baseCfs
|
||||
* @param indexMetadata
|
||||
* @return
|
||||
*/
|
||||
public CassandraIndex newIndexInstance(ColumnFamilyStore baseCfs, IndexMetadata indexMetadata);
|
||||
|
||||
/**
|
||||
* Returns the type of the the values in the index. For most columns this is simply its type, but for collections
|
||||
* it depends on whether the index is on the collection name/value element or on a frozen collection
|
||||
* @param indexedColumn
|
||||
* @return
|
||||
*/
|
||||
default AbstractType<?> getIndexedValueType(ColumnDefinition indexedColumn)
|
||||
{
|
||||
return indexedColumn.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the clustering columns for a specific type of index table to the a CFMetaData.Builder (which is being
|
||||
* used to construct the index table's CFMetadata. In the default implementation, the clustering columns of the
|
||||
* index table hold the partition key & clustering columns of the base table. This is overridden in several cases:
|
||||
* * When the indexed value is itself a clustering column, in which case, we only need store the base table's
|
||||
* *other* clustering values in the index - the indexed value being the index table's partition key
|
||||
* * When the indexed value is a collection value, in which case we also need to capture the cell path from the base
|
||||
* table
|
||||
* * In a KEYS index (for thrift/compact storage/static column indexes), where only the base partition key is
|
||||
* held in the index table.
|
||||
*
|
||||
* Called from indexCfsMetadata
|
||||
* @param builder
|
||||
* @param baseMetadata
|
||||
* @param cfDef
|
||||
* @return
|
||||
*/
|
||||
default CFMetaData.Builder addIndexClusteringColumns(CFMetaData.Builder builder,
|
||||
CFMetaData baseMetadata,
|
||||
ColumnDefinition cfDef)
|
||||
{
|
||||
for (ColumnDefinition def : baseMetadata.clusteringColumns())
|
||||
builder.addClusteringColumn(def.name, def.type);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/*
|
||||
* implementations providing specializations for the built in index types
|
||||
*/
|
||||
|
||||
static final CassandraIndexFunctions KEYS_INDEX_FUNCTIONS = new CassandraIndexFunctions()
|
||||
{
|
||||
public CassandraIndex newIndexInstance(ColumnFamilyStore baseCfs, IndexMetadata indexMetadata)
|
||||
{
|
||||
return new KeysIndex(baseCfs, indexMetadata);
|
||||
}
|
||||
};
|
||||
|
||||
static final CassandraIndexFunctions REGULAR_COLUMN_INDEX_FUNCTIONS = new CassandraIndexFunctions()
|
||||
{
|
||||
public CassandraIndex newIndexInstance(ColumnFamilyStore baseCfs, IndexMetadata indexMetadata)
|
||||
{
|
||||
return new RegularColumnIndex(baseCfs, indexMetadata);
|
||||
}
|
||||
};
|
||||
|
||||
static final CassandraIndexFunctions CLUSTERING_COLUMN_INDEX_FUNCTIONS = new CassandraIndexFunctions()
|
||||
{
|
||||
public CassandraIndex newIndexInstance(ColumnFamilyStore baseCfs, IndexMetadata indexMetadata)
|
||||
{
|
||||
return new ClusteringColumnIndex(baseCfs, indexMetadata);
|
||||
}
|
||||
|
||||
public CFMetaData.Builder addIndexClusteringColumns(CFMetaData.Builder builder,
|
||||
CFMetaData baseMetadata,
|
||||
ColumnDefinition columnDef)
|
||||
{
|
||||
List<ColumnDefinition> cks = baseMetadata.clusteringColumns();
|
||||
for (int i = 0; i < columnDef.position(); i++)
|
||||
{
|
||||
ColumnDefinition def = cks.get(i);
|
||||
builder.addClusteringColumn(def.name, def.type);
|
||||
}
|
||||
for (int i = columnDef.position() + 1; i < cks.size(); i++)
|
||||
{
|
||||
ColumnDefinition def = cks.get(i);
|
||||
builder.addClusteringColumn(def.name, def.type);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
};
|
||||
|
||||
static final CassandraIndexFunctions PARTITION_KEY_INDEX_FUNCTIONS = new CassandraIndexFunctions()
|
||||
{
|
||||
public CassandraIndex newIndexInstance(ColumnFamilyStore baseCfs, IndexMetadata indexMetadata)
|
||||
{
|
||||
return new PartitionKeyIndex(baseCfs, indexMetadata);
|
||||
}
|
||||
};
|
||||
|
||||
static final CassandraIndexFunctions COLLECTION_KEY_INDEX_FUNCTIONS = new CassandraIndexFunctions()
|
||||
{
|
||||
public CassandraIndex newIndexInstance(ColumnFamilyStore baseCfs, IndexMetadata indexMetadata)
|
||||
{
|
||||
return new CollectionKeyIndex(baseCfs, indexMetadata);
|
||||
}
|
||||
|
||||
public AbstractType<?> getIndexedValueType(ColumnDefinition indexedColumn)
|
||||
{
|
||||
return ((CollectionType) indexedColumn.type).nameComparator();
|
||||
}
|
||||
};
|
||||
|
||||
static final CassandraIndexFunctions COLLECTION_VALUE_INDEX_FUNCTIONS = new CassandraIndexFunctions()
|
||||
{
|
||||
|
||||
public CassandraIndex newIndexInstance(ColumnFamilyStore baseCfs, IndexMetadata indexMetadata)
|
||||
{
|
||||
return new CollectionValueIndex(baseCfs, indexMetadata);
|
||||
}
|
||||
|
||||
public AbstractType<?> getIndexedValueType(ColumnDefinition indexedColumn)
|
||||
{
|
||||
return ((CollectionType)indexedColumn.type).valueComparator();
|
||||
}
|
||||
|
||||
public CFMetaData.Builder addIndexClusteringColumns(CFMetaData.Builder builder,
|
||||
CFMetaData baseMetadata,
|
||||
ColumnDefinition columnDef)
|
||||
{
|
||||
for (ColumnDefinition def : baseMetadata.clusteringColumns())
|
||||
builder.addClusteringColumn(def.name, def.type);
|
||||
|
||||
// collection key
|
||||
builder.addClusteringColumn("cell_path", ((CollectionType)columnDef.type).nameComparator());
|
||||
return builder;
|
||||
}
|
||||
};
|
||||
|
||||
static final CassandraIndexFunctions COLLECTION_ENTRY_INDEX_FUNCTIONS = new CassandraIndexFunctions()
|
||||
{
|
||||
public CassandraIndex newIndexInstance(ColumnFamilyStore baseCfs, IndexMetadata indexMetadata)
|
||||
{
|
||||
return new CollectionEntryIndex(baseCfs, indexMetadata);
|
||||
}
|
||||
|
||||
public AbstractType<?> getIndexedValueType(ColumnDefinition indexedColumn)
|
||||
{
|
||||
CollectionType colType = (CollectionType)indexedColumn.type;
|
||||
return CompositeType.getInstance(colType.nameComparator(), colType.valueComparator());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
package org.apache.cassandra.index.internal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.NavigableSet;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.filter.*;
|
||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
||||
import org.apache.cassandra.db.rows.RowIterator;
|
||||
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
|
||||
import org.apache.cassandra.db.rows.UnfilteredRowIterators;
|
||||
import org.apache.cassandra.dht.AbstractBounds;
|
||||
import org.apache.cassandra.index.Index;
|
||||
import org.apache.cassandra.utils.btree.BTreeSet;
|
||||
|
||||
public abstract class CassandraIndexSearcher implements Index.Searcher
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(CassandraIndexSearcher.class);
|
||||
|
||||
private final RowFilter.Expression expression;
|
||||
protected final CassandraIndex index;
|
||||
protected final ReadCommand command;
|
||||
|
||||
public CassandraIndexSearcher(ReadCommand command,
|
||||
RowFilter.Expression expression,
|
||||
CassandraIndex index)
|
||||
{
|
||||
this.command = command;
|
||||
this.expression = expression;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource") // Both the OpOrder and 'indexIter' are closed on exception, or through the closing of the result
|
||||
// of this method.
|
||||
public UnfilteredPartitionIterator search(ReadOrderGroup orderGroup)
|
||||
{
|
||||
// the value of the index expression is the partition key in the index table
|
||||
DecoratedKey indexKey = index.getBackingTable().get().decorateKey(expression.getIndexValue());
|
||||
UnfilteredRowIterator indexIter = queryIndex(indexKey, command, orderGroup);
|
||||
try
|
||||
{
|
||||
return queryDataFromIndex(indexKey, UnfilteredRowIterators.filter(indexIter, command.nowInSec()), command, orderGroup);
|
||||
}
|
||||
catch (RuntimeException | Error e)
|
||||
{
|
||||
indexIter.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private UnfilteredRowIterator queryIndex(DecoratedKey indexKey, ReadCommand command, ReadOrderGroup orderGroup)
|
||||
{
|
||||
ClusteringIndexFilter filter = makeIndexFilter(command);
|
||||
ColumnFamilyStore indexCfs = index.getBackingTable().get();
|
||||
CFMetaData indexCfm = indexCfs.metadata;
|
||||
return SinglePartitionReadCommand.create(indexCfm, command.nowInSec(), indexKey, ColumnFilter.all(indexCfm), filter)
|
||||
.queryMemtableAndDisk(indexCfs, orderGroup.indexReadOpOrderGroup());
|
||||
}
|
||||
|
||||
private ClusteringIndexFilter makeIndexFilter(ReadCommand command)
|
||||
{
|
||||
if (command instanceof SinglePartitionReadCommand)
|
||||
{
|
||||
// Note: as yet there's no route to get here - a 2i query *always* uses a
|
||||
// PartitionRangeReadCommand. This is here in preparation for coming changes
|
||||
// in SelectStatement.
|
||||
SinglePartitionReadCommand sprc = (SinglePartitionReadCommand)command;
|
||||
ByteBuffer pk = sprc.partitionKey().getKey();
|
||||
ClusteringIndexFilter filter = sprc.clusteringIndexFilter();
|
||||
|
||||
if (filter instanceof ClusteringIndexNamesFilter)
|
||||
{
|
||||
NavigableSet<Clustering> requested = ((ClusteringIndexNamesFilter)filter).requestedRows();
|
||||
BTreeSet.Builder<Clustering> clusterings = BTreeSet.builder(index.getIndexComparator());
|
||||
for (Clustering c : requested)
|
||||
clusterings.add(makeIndexClustering(pk, c));
|
||||
return new ClusteringIndexNamesFilter(clusterings.build(), filter.isReversed());
|
||||
}
|
||||
else
|
||||
{
|
||||
Slices requested = ((ClusteringIndexSliceFilter)filter).requestedSlices();
|
||||
Slices.Builder builder = new Slices.Builder(index.getIndexComparator());
|
||||
for (Slice slice : requested)
|
||||
builder.add(makeIndexBound(pk, slice.start()), makeIndexBound(pk, slice.end()));
|
||||
return new ClusteringIndexSliceFilter(builder.build(), filter.isReversed());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
DataRange dataRange = ((PartitionRangeReadCommand)command).dataRange();
|
||||
AbstractBounds<PartitionPosition> range = dataRange.keyRange();
|
||||
|
||||
Slice slice = Slice.ALL;
|
||||
|
||||
/*
|
||||
* XXX: If the range requested is a token range, we'll have to start at the beginning (and stop at the end) of
|
||||
* the indexed row unfortunately (which will be inefficient), because we have no way to intuit the smallest possible
|
||||
* key having a given token. A potential fix would be to actually store the token along the key in the indexed row.
|
||||
*/
|
||||
if (range.left instanceof DecoratedKey)
|
||||
{
|
||||
// the right hand side of the range may not be a DecoratedKey (for instance if we're paging),
|
||||
// but if it is, we can optimise slightly by restricting the slice
|
||||
if (range.right instanceof DecoratedKey)
|
||||
{
|
||||
|
||||
DecoratedKey startKey = (DecoratedKey) range.left;
|
||||
DecoratedKey endKey = (DecoratedKey) range.right;
|
||||
|
||||
Slice.Bound start = Slice.Bound.BOTTOM;
|
||||
Slice.Bound end = Slice.Bound.TOP;
|
||||
|
||||
/*
|
||||
* For index queries over a range, we can't do a whole lot better than querying everything for the key range, though for
|
||||
* slice queries where we can slightly restrict the beginning and end.
|
||||
*/
|
||||
if (!dataRange.isNamesQuery())
|
||||
{
|
||||
ClusteringIndexSliceFilter startSliceFilter = ((ClusteringIndexSliceFilter) dataRange.clusteringIndexFilter(
|
||||
startKey));
|
||||
ClusteringIndexSliceFilter endSliceFilter = ((ClusteringIndexSliceFilter) dataRange.clusteringIndexFilter(
|
||||
endKey));
|
||||
|
||||
// We can't effectively support reversed queries when we have a range, so we don't support it
|
||||
// (or through post-query reordering) and shouldn't get there.
|
||||
assert !startSliceFilter.isReversed() && !endSliceFilter.isReversed();
|
||||
|
||||
Slices startSlices = startSliceFilter.requestedSlices();
|
||||
Slices endSlices = endSliceFilter.requestedSlices();
|
||||
|
||||
if (startSlices.size() > 0)
|
||||
start = startSlices.get(0).start();
|
||||
|
||||
if (endSlices.size() > 0)
|
||||
end = endSlices.get(endSlices.size() - 1).end();
|
||||
}
|
||||
|
||||
slice = Slice.make(makeIndexBound(startKey.getKey(), start),
|
||||
makeIndexBound(endKey.getKey(), end));
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise, just start the index slice from the key we do have
|
||||
slice = Slice.make(makeIndexBound(((DecoratedKey)range.left).getKey(), Slice.Bound.BOTTOM),
|
||||
Slice.Bound.TOP);
|
||||
}
|
||||
}
|
||||
return new ClusteringIndexSliceFilter(Slices.with(index.getIndexComparator(), slice), false);
|
||||
}
|
||||
}
|
||||
|
||||
private Slice.Bound makeIndexBound(ByteBuffer rowKey, Slice.Bound bound)
|
||||
{
|
||||
return index.buildIndexClusteringPrefix(rowKey, bound, null)
|
||||
.buildBound(bound.isStart(), bound.isInclusive());
|
||||
}
|
||||
|
||||
protected Clustering makeIndexClustering(ByteBuffer rowKey, Clustering clustering)
|
||||
{
|
||||
return index.buildIndexClusteringPrefix(rowKey, clustering, null).build();
|
||||
}
|
||||
|
||||
protected abstract UnfilteredPartitionIterator queryDataFromIndex(DecoratedKey indexKey,
|
||||
RowIterator indexHits,
|
||||
ReadCommand command,
|
||||
ReadOrderGroup orderGroup);
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package org.apache.cassandra.index.internal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.db.Clustering;
|
||||
import org.apache.cassandra.db.DecoratedKey;
|
||||
|
||||
/**
|
||||
* Entries in indexes on non-compact tables (tables with composite comparators)
|
||||
* can be encapsulated as IndexedEntry instances. These are not used when dealing
|
||||
* with indexes on static/compact/thrift tables (i.e. KEYS indexes).
|
||||
*/
|
||||
public final class IndexEntry
|
||||
{
|
||||
public final DecoratedKey indexValue;
|
||||
public final Clustering indexClustering;
|
||||
public final long timestamp;
|
||||
|
||||
public final ByteBuffer indexedKey;
|
||||
public final Clustering indexedEntryClustering;
|
||||
|
||||
public IndexEntry(DecoratedKey indexValue,
|
||||
Clustering indexClustering,
|
||||
long timestamp,
|
||||
ByteBuffer indexedKey,
|
||||
Clustering indexedEntryClustering)
|
||||
{
|
||||
this.indexValue = indexValue;
|
||||
this.indexClustering = indexClustering;
|
||||
this.timestamp = timestamp;
|
||||
this.indexedKey = indexedKey;
|
||||
this.indexedEntryClustering = indexedEntryClustering;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.index.internal.composites;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.CellPath;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.index.internal.CassandraIndex;
|
||||
import org.apache.cassandra.index.internal.IndexEntry;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
|
||||
/**
|
||||
* Index on a CLUSTERING_COLUMN column definition.
|
||||
*
|
||||
* A cell indexed by this index will have the general form:
|
||||
* ck_0 ... ck_n c_name : v
|
||||
* where ck_i are the cluster keys, c_name the last component of the cell
|
||||
* composite name (or second to last if collections are in use, but this
|
||||
* has no impact) and v the cell value.
|
||||
*
|
||||
* Such a cell is always indexed by this index (or rather, it is indexed if
|
||||
* n >= columnDef.componentIndex, which will always be the case in practice)
|
||||
* and it will generate (makeIndexColumnName()) an index entry whose:
|
||||
* - row key will be ck_i (getIndexedValue()) where i == columnDef.componentIndex.
|
||||
* - cell name will
|
||||
* rk ck_0 ... ck_{i-1} ck_{i+1} ck_n
|
||||
* where rk is the row key of the initial cell and i == columnDef.componentIndex.
|
||||
*/
|
||||
public class ClusteringColumnIndex extends CassandraIndex
|
||||
{
|
||||
public ClusteringColumnIndex(ColumnFamilyStore baseCfs, IndexMetadata indexDef)
|
||||
{
|
||||
super(baseCfs, indexDef);
|
||||
}
|
||||
|
||||
|
||||
public ByteBuffer getIndexedValue(ByteBuffer partitionKey,
|
||||
Clustering clustering,
|
||||
CellPath path, ByteBuffer cellValue)
|
||||
{
|
||||
return clustering.get(indexedColumn.position());
|
||||
}
|
||||
|
||||
public CBuilder buildIndexClusteringPrefix(ByteBuffer partitionKey,
|
||||
ClusteringPrefix prefix,
|
||||
CellPath path)
|
||||
{
|
||||
CBuilder builder = CBuilder.create(getIndexComparator());
|
||||
builder.add(partitionKey);
|
||||
for (int i = 0; i < Math.min(indexedColumn.position(), prefix.size()); i++)
|
||||
builder.add(prefix.get(i));
|
||||
for (int i = indexedColumn.position() + 1; i < prefix.size(); i++)
|
||||
builder.add(prefix.get(i));
|
||||
return builder;
|
||||
}
|
||||
|
||||
public IndexEntry decodeEntry(DecoratedKey indexedValue,
|
||||
Row indexEntry)
|
||||
{
|
||||
int ckCount = baseCfs.metadata.clusteringColumns().size();
|
||||
|
||||
Clustering clustering = indexEntry.clustering();
|
||||
CBuilder builder = CBuilder.create(baseCfs.getComparator());
|
||||
for (int i = 0; i < indexedColumn.position(); i++)
|
||||
builder.add(clustering.get(i + 1));
|
||||
|
||||
builder.add(indexedValue.getKey());
|
||||
|
||||
for (int i = indexedColumn.position() + 1; i < ckCount; i++)
|
||||
builder.add(clustering.get(i));
|
||||
|
||||
return new IndexEntry(indexedValue,
|
||||
clustering,
|
||||
indexEntry.primaryKeyLivenessInfo().timestamp(),
|
||||
clustering.get(0),
|
||||
builder.build());
|
||||
}
|
||||
|
||||
public boolean isStale(Row data, ByteBuffer indexValue, int nowInSec)
|
||||
{
|
||||
return !data.hasLiveData(nowInSec);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,13 +15,20 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index.composites;
|
||||
package org.apache.cassandra.index.internal.composites;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.marshal.*;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.Clustering;
|
||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.db.marshal.CompositeType;
|
||||
import org.apache.cassandra.db.rows.Cell;
|
||||
import org.apache.cassandra.db.rows.CellPath;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
|
||||
/**
|
||||
* Index on the element and value of cells participating in a collection.
|
||||
|
|
@ -29,26 +36,28 @@ import org.apache.cassandra.db.marshal.*;
|
|||
* The row keys for this index are a composite of the collection element
|
||||
* and value of indexed columns.
|
||||
*/
|
||||
public class CompositesIndexOnCollectionKeyAndValue extends CompositesIndexIncludingCollectionKey
|
||||
public class CollectionEntryIndex extends CollectionKeyIndexBase
|
||||
{
|
||||
@Override
|
||||
protected AbstractType<?> getIndexKeyComparator()
|
||||
public CollectionEntryIndex(ColumnFamilyStore baseCfs,
|
||||
IndexMetadata indexDef)
|
||||
{
|
||||
CollectionType colType = (CollectionType)columnDef.type;
|
||||
return CompositeType.getInstance(colType.nameComparator(), colType.valueComparator());
|
||||
super(baseCfs, indexDef);
|
||||
}
|
||||
|
||||
protected ByteBuffer getIndexedValue(ByteBuffer rowKey, Clustering clustering, ByteBuffer cellValue, CellPath path)
|
||||
public ByteBuffer getIndexedValue(ByteBuffer partitionKey,
|
||||
Clustering clustering,
|
||||
CellPath path, ByteBuffer cellValue)
|
||||
{
|
||||
return CompositeType.build(path.get(0), cellValue);
|
||||
}
|
||||
|
||||
public boolean isStale(Row data, ByteBuffer indexValue, int nowInSec)
|
||||
{
|
||||
ByteBuffer[] components = ((CompositeType)getIndexKeyComparator()).split(indexValue);
|
||||
ByteBuffer[] components = ((CompositeType)functions.getIndexedValueType(indexedColumn)).split(indexValue);
|
||||
ByteBuffer mapKey = components[0];
|
||||
ByteBuffer mapValue = components[1];
|
||||
|
||||
ColumnDefinition columnDef = indexedColumn;
|
||||
Cell cell = data.getCell(columnDef, CellPath.create(mapKey));
|
||||
if (cell == null || !cell.isLive(nowInSec))
|
||||
return true;
|
||||
|
|
@ -15,14 +15,19 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index.composites;
|
||||
package org.apache.cassandra.index.internal.composites;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.cql3.Operator;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.marshal.*;
|
||||
import org.apache.cassandra.db.Clustering;
|
||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||
import org.apache.cassandra.db.marshal.SetType;
|
||||
import org.apache.cassandra.db.rows.Cell;
|
||||
import org.apache.cassandra.db.rows.CellPath;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
|
||||
/**
|
||||
* Index on the collection element of the cell name of a collection.
|
||||
|
|
@ -30,29 +35,30 @@ import org.apache.cassandra.db.marshal.*;
|
|||
* The row keys for this index are given by the collection element for
|
||||
* indexed columns.
|
||||
*/
|
||||
public class CompositesIndexOnCollectionKey extends CompositesIndexIncludingCollectionKey
|
||||
public class CollectionKeyIndex extends CollectionKeyIndexBase
|
||||
{
|
||||
@Override
|
||||
protected AbstractType<?> getIndexKeyComparator()
|
||||
public CollectionKeyIndex(ColumnFamilyStore baseCfs, IndexMetadata indexDef)
|
||||
{
|
||||
return ((CollectionType)columnDef.type).nameComparator();
|
||||
super(baseCfs, indexDef);
|
||||
}
|
||||
|
||||
protected ByteBuffer getIndexedValue(ByteBuffer rowKey, Clustering clustering, ByteBuffer cellValue, CellPath path)
|
||||
public ByteBuffer getIndexedValue(ByteBuffer partitionKey,
|
||||
Clustering clustering,
|
||||
CellPath path,
|
||||
ByteBuffer cellValue)
|
||||
{
|
||||
return path.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsOperator(Operator operator)
|
||||
{
|
||||
return operator == Operator.CONTAINS_KEY ||
|
||||
operator == Operator.CONTAINS && columnDef.type instanceof SetType;
|
||||
}
|
||||
|
||||
public boolean isStale(Row data, ByteBuffer indexValue, int nowInSec)
|
||||
{
|
||||
Cell cell = data.getCell(columnDef, CellPath.create(indexValue));
|
||||
Cell cell = data.getCell(indexedColumn, CellPath.create(indexValue));
|
||||
return cell == null || !cell.isLive(nowInSec);
|
||||
}
|
||||
|
||||
public boolean supportsOperator(ColumnDefinition indexedColumn, Operator operator)
|
||||
{
|
||||
return operator == Operator.CONTAINS_KEY ||
|
||||
operator == Operator.CONTAINS && indexedColumn.type instanceof SetType;
|
||||
}
|
||||
}
|
||||
|
|
@ -15,12 +15,16 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index.composites;
|
||||
package org.apache.cassandra.index.internal.composites;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.rows.CellPath;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.index.internal.CassandraIndex;
|
||||
import org.apache.cassandra.index.internal.IndexEntry;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
|
||||
/**
|
||||
* Common superclass for indexes that capture collection keys, including
|
||||
|
|
@ -37,24 +41,38 @@ import org.apache.cassandra.db.rows.*;
|
|||
* - the row key is determined by subclasses of this type.
|
||||
* - the cell name will be 'rk ck_0 ... ck_n' where rk is the row key of the initial cell.
|
||||
*/
|
||||
public abstract class CompositesIndexIncludingCollectionKey extends CompositesIndex
|
||||
public abstract class CollectionKeyIndexBase extends CassandraIndex
|
||||
{
|
||||
protected CBuilder buildIndexClusteringPrefix(ByteBuffer rowKey, ClusteringPrefix prefix, CellPath path)
|
||||
public CollectionKeyIndexBase(ColumnFamilyStore baseCfs, IndexMetadata indexDef)
|
||||
{
|
||||
super(baseCfs, indexDef);
|
||||
}
|
||||
|
||||
public CBuilder buildIndexClusteringPrefix(ByteBuffer partitionKey,
|
||||
ClusteringPrefix prefix,
|
||||
CellPath path)
|
||||
{
|
||||
CBuilder builder = CBuilder.create(getIndexComparator());
|
||||
builder.add(rowKey);
|
||||
builder.add(partitionKey);
|
||||
for (int i = 0; i < prefix.size(); i++)
|
||||
builder.add(prefix.get(i));
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public IndexedEntry decodeEntry(DecoratedKey indexedValue, Row indexEntry)
|
||||
public IndexEntry decodeEntry(DecoratedKey indexedValue,
|
||||
Row indexEntry)
|
||||
{
|
||||
int count = 1 + baseCfs.metadata.clusteringColumns().size();
|
||||
Clustering clustering = indexEntry.clustering();
|
||||
CBuilder builder = CBuilder.create(baseCfs.getComparator());
|
||||
for (int i = 0; i < count - 1; i++)
|
||||
builder.add(clustering.get(i + 1));
|
||||
return new IndexedEntry(indexedValue, clustering, indexEntry.primaryKeyLivenessInfo().timestamp(), clustering.get(0), builder.build());
|
||||
|
||||
return new IndexEntry(indexedValue,
|
||||
clustering,
|
||||
indexEntry.primaryKeyLivenessInfo().timestamp(),
|
||||
clustering.get(0),
|
||||
builder.build());
|
||||
}
|
||||
}
|
||||
|
|
@ -15,17 +15,19 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index.composites;
|
||||
package org.apache.cassandra.index.internal.composites;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.cql3.Operator;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.db.marshal.SetType;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.marshal.*;
|
||||
import org.apache.cassandra.index.internal.CassandraIndex;
|
||||
import org.apache.cassandra.index.internal.IndexEntry;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
|
||||
/**
|
||||
* Index the value of a collection cell.
|
||||
|
|
@ -38,31 +40,26 @@ import org.apache.cassandra.db.marshal.*;
|
|||
* for each so that if we delete one of the value only we only delete the
|
||||
* entry corresponding to that value.
|
||||
*/
|
||||
public class CompositesIndexOnCollectionValue extends CompositesIndex
|
||||
public class CollectionValueIndex extends CassandraIndex
|
||||
{
|
||||
public static void addClusteringColumns(CFMetaData.Builder indexMetadata, CFMetaData baseMetadata, ColumnDefinition columnDef)
|
||||
public CollectionValueIndex(ColumnFamilyStore baseCfs, IndexMetadata indexDef)
|
||||
{
|
||||
addGenericClusteringColumns(indexMetadata, baseMetadata, columnDef);
|
||||
|
||||
// collection key
|
||||
indexMetadata.addClusteringColumn("cell_path", ((CollectionType)columnDef.type).nameComparator());
|
||||
super(baseCfs, indexDef);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractType<?> getIndexKeyComparator()
|
||||
{
|
||||
return ((CollectionType)columnDef.type).valueComparator();
|
||||
}
|
||||
|
||||
protected ByteBuffer getIndexedValue(ByteBuffer rowKey, Clustering clustering, ByteBuffer cellValue, CellPath path)
|
||||
public ByteBuffer getIndexedValue(ByteBuffer partitionKey,
|
||||
Clustering clustering,
|
||||
CellPath path, ByteBuffer cellValue)
|
||||
{
|
||||
return cellValue;
|
||||
}
|
||||
|
||||
protected CBuilder buildIndexClusteringPrefix(ByteBuffer rowKey, ClusteringPrefix prefix, CellPath path)
|
||||
public CBuilder buildIndexClusteringPrefix(ByteBuffer partitionKey,
|
||||
ClusteringPrefix prefix,
|
||||
CellPath path)
|
||||
{
|
||||
CBuilder builder = CBuilder.create(getIndexComparator());
|
||||
builder.add(rowKey);
|
||||
builder.add(partitionKey);
|
||||
for (int i = 0; i < prefix.size(); i++)
|
||||
builder.add(prefix.get(i));
|
||||
|
||||
|
|
@ -73,27 +70,36 @@ public class CompositesIndexOnCollectionValue extends CompositesIndex
|
|||
return builder;
|
||||
}
|
||||
|
||||
public IndexedEntry decodeEntry(DecoratedKey indexedValue, Row indexEntry)
|
||||
public IndexEntry decodeEntry(DecoratedKey indexedValue, Row indexEntry)
|
||||
{
|
||||
Clustering clustering = indexEntry.clustering();
|
||||
CBuilder builder = CBuilder.create(baseCfs.getComparator());
|
||||
for (int i = 0; i < baseCfs.getComparator().size(); i++)
|
||||
builder.add(clustering.get(i + 1));
|
||||
return new IndexedEntry(indexedValue, clustering, indexEntry.primaryKeyLivenessInfo().timestamp(), clustering.get(0), builder.build());
|
||||
|
||||
return new IndexEntry(indexedValue,
|
||||
clustering,
|
||||
indexEntry.primaryKeyLivenessInfo().timestamp(),
|
||||
clustering.get(0),
|
||||
builder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsOperator(Operator operator)
|
||||
public boolean supportsOperator(ColumnDefinition indexedColumn, Operator operator)
|
||||
{
|
||||
return operator == Operator.CONTAINS && !(columnDef.type instanceof SetType);
|
||||
return operator == Operator.CONTAINS && !(indexedColumn.type instanceof SetType);
|
||||
}
|
||||
|
||||
public boolean isStale(Row data, ByteBuffer indexValue, int nowInSec)
|
||||
{
|
||||
ColumnDefinition columnDef = indexedColumn;
|
||||
ComplexColumnData complexData = data.getComplexColumnData(columnDef);
|
||||
if (complexData == null)
|
||||
return true;
|
||||
|
||||
for (Cell cell : complexData)
|
||||
{
|
||||
if (cell.isLive(nowInSec) && ((CollectionType) columnDef.type).valueComparator().compare(indexValue, cell.value()) == 0)
|
||||
if (cell.isLive(nowInSec) && ((CollectionType) columnDef.type).valueComparator()
|
||||
.compare(indexValue, cell.value()) == 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -15,53 +15,55 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index.composites;
|
||||
package org.apache.cassandra.index.internal.composites;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.filter.*;
|
||||
import org.apache.cassandra.db.index.*;
|
||||
import org.apache.cassandra.db.filter.ClusteringIndexNamesFilter;
|
||||
import org.apache.cassandra.db.filter.DataLimits;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.index.internal.CassandraIndex;
|
||||
import org.apache.cassandra.index.internal.CassandraIndexSearcher;
|
||||
import org.apache.cassandra.index.internal.IndexEntry;
|
||||
import org.apache.cassandra.utils.btree.BTreeSet;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
|
||||
|
||||
public class CompositesSearcher extends SecondaryIndexSearcher
|
||||
public class CompositesSearcher extends CassandraIndexSearcher
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(CompositesSearcher.class);
|
||||
|
||||
public CompositesSearcher(SecondaryIndexManager indexManager, Set<ColumnDefinition> columns)
|
||||
public CompositesSearcher(ReadCommand command,
|
||||
RowFilter.Expression expression,
|
||||
CassandraIndex index)
|
||||
{
|
||||
super(indexManager, columns);
|
||||
super(command, expression, index);
|
||||
}
|
||||
|
||||
private boolean isMatchingEntry(DecoratedKey partitionKey, CompositesIndex.IndexedEntry entry, ReadCommand command)
|
||||
private boolean isMatchingEntry(DecoratedKey partitionKey, IndexEntry entry, ReadCommand command)
|
||||
{
|
||||
return command.selects(partitionKey, entry.indexedEntryClustering);
|
||||
}
|
||||
|
||||
protected UnfilteredPartitionIterator queryDataFromIndex(AbstractSimplePerColumnSecondaryIndex secondaryIdx,
|
||||
final DecoratedKey indexKey,
|
||||
protected UnfilteredPartitionIterator queryDataFromIndex(final DecoratedKey indexKey,
|
||||
final RowIterator indexHits,
|
||||
final ReadCommand command,
|
||||
final ReadOrderGroup orderGroup)
|
||||
{
|
||||
assert indexHits.staticRow() == Rows.EMPTY_STATIC_ROW;
|
||||
|
||||
assert secondaryIdx instanceof CompositesIndex;
|
||||
final CompositesIndex index = (CompositesIndex)secondaryIdx;
|
||||
|
||||
return new UnfilteredPartitionIterator()
|
||||
{
|
||||
private CompositesIndex.IndexedEntry nextEntry;
|
||||
private IndexEntry nextEntry;
|
||||
|
||||
private UnfilteredRowIterator next;
|
||||
|
||||
|
|
@ -109,9 +111,9 @@ public class CompositesSearcher extends SecondaryIndexSearcher
|
|||
// in memory so we should consider adding some paging mechanism. However, index hits should
|
||||
// be relatively small so it's much better than the previous code that was materializing all
|
||||
// *data* for a given partition.
|
||||
BTreeSet.Builder<Clustering> clusterings = BTreeSet.builder(baseCfs.getComparator());
|
||||
List<CompositesIndex.IndexedEntry> entries = new ArrayList<>();
|
||||
DecoratedKey partitionKey = baseCfs.decorateKey(nextEntry.indexedKey);
|
||||
BTreeSet.Builder<Clustering> clusterings = BTreeSet.builder(index.baseCfs.getComparator());
|
||||
List<IndexEntry> entries = new ArrayList<>();
|
||||
DecoratedKey partitionKey = index.baseCfs.decorateKey(nextEntry.indexedKey);
|
||||
|
||||
while (nextEntry != null && partitionKey.getKey().equals(nextEntry.indexedKey))
|
||||
{
|
||||
|
|
@ -131,7 +133,7 @@ public class CompositesSearcher extends SecondaryIndexSearcher
|
|||
|
||||
// Query the gathered index hits. We still need to filter stale hits from the resulting query.
|
||||
ClusteringIndexNamesFilter filter = new ClusteringIndexNamesFilter(clusterings.build(), false);
|
||||
SinglePartitionReadCommand dataCmd = new SinglePartitionNamesCommand(metadata(),
|
||||
SinglePartitionReadCommand dataCmd = new SinglePartitionNamesCommand(index.baseCfs.metadata,
|
||||
command.nowInSec(),
|
||||
command.columnFilter(),
|
||||
command.rowFilter(),
|
||||
|
|
@ -140,12 +142,14 @@ public class CompositesSearcher extends SecondaryIndexSearcher
|
|||
filter);
|
||||
@SuppressWarnings("resource") // We close right away if empty, and if it's assign to next it will be called either
|
||||
// by the next caller of next, or through closing this iterator is this come before.
|
||||
UnfilteredRowIterator dataIter = filterStaleEntries(dataCmd.queryMemtableAndDisk(baseCfs, orderGroup.baseReadOpOrderGroup()),
|
||||
index,
|
||||
UnfilteredRowIterator dataIter = filterStaleEntries(dataCmd.queryMemtableAndDisk(index.baseCfs,
|
||||
orderGroup.baseReadOpOrderGroup()),
|
||||
indexKey.getKey(),
|
||||
entries,
|
||||
orderGroup.writeOpOrderGroup(),
|
||||
command.nowInSec());
|
||||
|
||||
|
||||
if (dataIter.isEmpty())
|
||||
{
|
||||
dataIter.close();
|
||||
|
|
@ -170,35 +174,62 @@ public class CompositesSearcher extends SecondaryIndexSearcher
|
|||
};
|
||||
}
|
||||
|
||||
private void deleteAllEntries(final List<IndexEntry> entries, final OpOrder.Group writeOp, final int nowInSec)
|
||||
{
|
||||
entries.forEach(entry ->
|
||||
index.deleteStaleEntry(entry.indexValue,
|
||||
entry.indexClustering,
|
||||
new DeletionTime(entry.timestamp, nowInSec),
|
||||
writeOp));
|
||||
}
|
||||
|
||||
private UnfilteredRowIterator filterStaleEntries(UnfilteredRowIterator dataIter,
|
||||
final CompositesIndex index,
|
||||
final ByteBuffer indexValue,
|
||||
final List<CompositesIndex.IndexedEntry> entries,
|
||||
final List<IndexEntry> entries,
|
||||
final OpOrder.Group writeOp,
|
||||
final int nowInSec)
|
||||
{
|
||||
// collect stale index entries and delete them when we close this iterator
|
||||
final List<IndexEntry> staleEntries = new ArrayList<>();
|
||||
|
||||
// if there is a partition level delete in the base table, we need to filter
|
||||
// any index entries which would be shadowed by it
|
||||
if (!dataIter.partitionLevelDeletion().isLive())
|
||||
{
|
||||
DeletionTime deletion = dataIter.partitionLevelDeletion();
|
||||
entries.forEach(e -> {
|
||||
if (deletion.deletes(e.timestamp))
|
||||
staleEntries.add(e);
|
||||
});
|
||||
}
|
||||
|
||||
return new AlteringUnfilteredRowIterator(dataIter)
|
||||
{
|
||||
private int entriesIdx;
|
||||
|
||||
public void close()
|
||||
{
|
||||
deleteAllEntries(staleEntries, writeOp, nowInSec);
|
||||
super.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Row computeNext(Row row)
|
||||
{
|
||||
CompositesIndex.IndexedEntry entry = findEntry(row.clustering(), writeOp, nowInSec);
|
||||
IndexEntry entry = findEntry(row.clustering());
|
||||
if (!index.isStale(row, indexValue, nowInSec))
|
||||
return row;
|
||||
|
||||
// The entry is stale: delete the entry and ignore otherwise
|
||||
index.delete(entry, writeOp, nowInSec);
|
||||
staleEntries.add(entry);
|
||||
return null;
|
||||
}
|
||||
|
||||
private CompositesIndex.IndexedEntry findEntry(Clustering clustering, OpOrder.Group writeOp, int nowInSec)
|
||||
private IndexEntry findEntry(Clustering clustering)
|
||||
{
|
||||
assert entriesIdx < entries.size();
|
||||
while (entriesIdx < entries.size())
|
||||
{
|
||||
CompositesIndex.IndexedEntry entry = entries.get(entriesIdx++);
|
||||
IndexEntry entry = entries.get(entriesIdx++);
|
||||
// The entries are in clustering order. So that the requested entry should be the
|
||||
// next entry, the one at 'entriesIdx'. However, we can have stale entries, entries
|
||||
// that have no corresponding row in the base table typically because of a range
|
||||
|
|
@ -208,7 +239,7 @@ public class CompositesSearcher extends SecondaryIndexSearcher
|
|||
if (cmp == 0)
|
||||
return entry;
|
||||
else
|
||||
index.delete(entry, writeOp, nowInSec);
|
||||
staleEntries.add(entry);
|
||||
}
|
||||
// entries correspond to the rows we've queried, so we shouldn't have a row that has no corresponding entry.
|
||||
throw new AssertionError();
|
||||
|
|
@ -15,15 +15,17 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index.composites;
|
||||
package org.apache.cassandra.index.internal.composites;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.marshal.*;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.db.marshal.CompositeType;
|
||||
import org.apache.cassandra.db.rows.CellPath;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.index.internal.CassandraIndex;
|
||||
import org.apache.cassandra.index.internal.IndexEntry;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
|
||||
/**
|
||||
* Index on a PARTITION_KEY column definition.
|
||||
|
|
@ -43,25 +45,35 @@ import org.apache.cassandra.utils.concurrent.OpOrder;
|
|||
* want to order the index cell name by partitioner first, and skipping a part
|
||||
* of the row key would change the order.
|
||||
*/
|
||||
public class CompositesIndexOnPartitionKey extends CompositesIndex
|
||||
public class PartitionKeyIndex extends CassandraIndex
|
||||
{
|
||||
protected ByteBuffer getIndexedValue(ByteBuffer rowKey, Clustering clustering, ByteBuffer cellValue, CellPath path)
|
||||
public PartitionKeyIndex(ColumnFamilyStore baseCfs, IndexMetadata indexDef)
|
||||
{
|
||||
CompositeType keyComparator = (CompositeType)baseCfs.metadata.getKeyValidator();
|
||||
ByteBuffer[] components = keyComparator.split(rowKey);
|
||||
return components[columnDef.position()];
|
||||
super(baseCfs, indexDef);
|
||||
}
|
||||
|
||||
protected CBuilder buildIndexClusteringPrefix(ByteBuffer rowKey, ClusteringPrefix prefix, CellPath path)
|
||||
public ByteBuffer getIndexedValue(ByteBuffer partitionKey,
|
||||
Clustering clustering,
|
||||
CellPath path,
|
||||
ByteBuffer cellValue)
|
||||
{
|
||||
CompositeType keyComparator = (CompositeType)baseCfs.metadata.getKeyValidator();
|
||||
ByteBuffer[] components = keyComparator.split(partitionKey);
|
||||
return components[indexedColumn.position()];
|
||||
}
|
||||
|
||||
public CBuilder buildIndexClusteringPrefix(ByteBuffer partitionKey,
|
||||
ClusteringPrefix prefix,
|
||||
CellPath path)
|
||||
{
|
||||
CBuilder builder = CBuilder.create(getIndexComparator());
|
||||
builder.add(rowKey);
|
||||
builder.add(partitionKey);
|
||||
for (int i = 0; i < prefix.size(); i++)
|
||||
builder.add(prefix.get(i));
|
||||
return builder;
|
||||
}
|
||||
|
||||
public IndexedEntry decodeEntry(DecoratedKey indexedValue, Row indexEntry)
|
||||
public IndexEntry decodeEntry(DecoratedKey indexedValue, Row indexEntry)
|
||||
{
|
||||
int ckCount = baseCfs.metadata.clusteringColumns().size();
|
||||
Clustering clustering = indexEntry.clustering();
|
||||
|
|
@ -69,38 +81,15 @@ public class CompositesIndexOnPartitionKey extends CompositesIndex
|
|||
for (int i = 0; i < ckCount; i++)
|
||||
builder.add(clustering.get(i + 1));
|
||||
|
||||
return new IndexedEntry(indexedValue, clustering, indexEntry.primaryKeyLivenessInfo().timestamp(), clustering.get(0), builder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean indexPrimaryKeyColumn()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean indexes(ColumnDefinition c)
|
||||
{
|
||||
// Actual indexing for this index type is done through maybeIndex
|
||||
return false;
|
||||
return new IndexEntry(indexedValue,
|
||||
clustering,
|
||||
indexEntry.primaryKeyLivenessInfo().timestamp(),
|
||||
clustering.get(0),
|
||||
builder.build());
|
||||
}
|
||||
|
||||
public boolean isStale(Row data, ByteBuffer indexValue, int nowInSec)
|
||||
{
|
||||
return !data.hasLiveData(nowInSec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeIndex(ByteBuffer partitionKey, Clustering clustering, long timestamp, int ttl, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
insert(partitionKey, clustering, null, LivenessInfo.create(indexCfs.metadata, timestamp, ttl, nowInSec), opGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(ByteBuffer rowKey, Clustering clustering, Cell cell, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
// We only know that one column of the CQL row has been updated/deleted, but we don't know if the
|
||||
// full row has been deleted so we should not do anything. If it ends up that the whole row has
|
||||
// been deleted, it will be eventually cleaned up on read because the entry will be detected stale.
|
||||
}
|
||||
}
|
||||
|
|
@ -15,12 +15,17 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index.composites;
|
||||
package org.apache.cassandra.index.internal.composites;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.rows.Cell;
|
||||
import org.apache.cassandra.db.rows.CellPath;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.index.internal.CassandraIndex;
|
||||
import org.apache.cassandra.index.internal.IndexEntry;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
|
||||
/**
|
||||
* Index on a REGULAR column definition on a composite type.
|
||||
|
|
@ -39,37 +44,53 @@ import org.apache.cassandra.db.rows.*;
|
|||
* where rk is the row key of the initial cell. I.e. the index entry store
|
||||
* all the information require to locate back the indexed cell.
|
||||
*/
|
||||
public class CompositesIndexOnRegular extends CompositesIndex
|
||||
public class RegularColumnIndex extends CassandraIndex
|
||||
{
|
||||
protected ByteBuffer getIndexedValue(ByteBuffer rowKey, Clustering clustering, ByteBuffer cellValue, CellPath path)
|
||||
public RegularColumnIndex(ColumnFamilyStore baseCfs, IndexMetadata indexDef)
|
||||
{
|
||||
super(baseCfs, indexDef);
|
||||
}
|
||||
|
||||
public ByteBuffer getIndexedValue(ByteBuffer partitionKey,
|
||||
Clustering clustering,
|
||||
CellPath path,
|
||||
ByteBuffer cellValue)
|
||||
{
|
||||
return cellValue;
|
||||
}
|
||||
|
||||
protected CBuilder buildIndexClusteringPrefix(ByteBuffer rowKey, ClusteringPrefix prefix, CellPath path)
|
||||
public CBuilder buildIndexClusteringPrefix(ByteBuffer partitionKey,
|
||||
ClusteringPrefix prefix,
|
||||
CellPath path)
|
||||
{
|
||||
CBuilder builder = CBuilder.create(getIndexComparator());
|
||||
builder.add(rowKey);
|
||||
builder.add(partitionKey);
|
||||
for (int i = 0; i < prefix.size(); i++)
|
||||
builder.add(prefix.get(i));
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public IndexedEntry decodeEntry(DecoratedKey indexedValue, Row indexEntry)
|
||||
public IndexEntry decodeEntry(DecoratedKey indexedValue, Row indexEntry)
|
||||
{
|
||||
Clustering clustering = indexEntry.clustering();
|
||||
ClusteringComparator baseComparator = baseCfs.getComparator();
|
||||
CBuilder builder = CBuilder.create(baseComparator);
|
||||
for (int i = 0; i < baseComparator.size(); i++)
|
||||
builder.add(clustering.get(i + 1));
|
||||
return new IndexedEntry(indexedValue, clustering, indexEntry.primaryKeyLivenessInfo().timestamp(), clustering.get(0), builder.build());
|
||||
|
||||
return new IndexEntry(indexedValue,
|
||||
clustering,
|
||||
indexEntry.primaryKeyLivenessInfo().timestamp(),
|
||||
clustering.get(0),
|
||||
builder.build());
|
||||
}
|
||||
|
||||
public boolean isStale(Row data, ByteBuffer indexValue, int nowInSec)
|
||||
{
|
||||
Cell cell = data.getCell(columnDef);
|
||||
Cell cell = data.getCell(indexedColumn);
|
||||
return cell == null
|
||||
|| !cell.isLive(nowInSec)
|
||||
|| columnDef.type.compare(indexValue, cell.value()) != 0;
|
||||
|| indexedColumn.type.compare(indexValue, cell.value()) != 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package org.apache.cassandra.index.internal.keys;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.Cell;
|
||||
import org.apache.cassandra.db.rows.CellPath;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.index.internal.CassandraIndex;
|
||||
import org.apache.cassandra.index.internal.IndexEntry;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
|
||||
public class KeysIndex extends CassandraIndex
|
||||
{
|
||||
public KeysIndex(ColumnFamilyStore baseCfs, IndexMetadata indexDef)
|
||||
{
|
||||
super(baseCfs, indexDef);
|
||||
}
|
||||
|
||||
public CFMetaData.Builder addIndexClusteringColumns(CFMetaData.Builder builder,
|
||||
CFMetaData baseMetadata,
|
||||
ColumnDefinition cfDef)
|
||||
{
|
||||
// no additional clustering columns required
|
||||
return builder;
|
||||
}
|
||||
|
||||
protected CBuilder buildIndexClusteringPrefix(ByteBuffer partitionKey,
|
||||
ClusteringPrefix prefix,
|
||||
CellPath path)
|
||||
{
|
||||
CBuilder builder = CBuilder.create(getIndexComparator());
|
||||
builder.add(partitionKey);
|
||||
return builder;
|
||||
}
|
||||
|
||||
protected ByteBuffer getIndexedValue(ByteBuffer partitionKey,
|
||||
Clustering clustering,
|
||||
CellPath path, ByteBuffer cellValue)
|
||||
{
|
||||
return cellValue;
|
||||
}
|
||||
|
||||
public IndexEntry decodeEntry(DecoratedKey indexedValue, Row indexEntry)
|
||||
{
|
||||
throw new UnsupportedOperationException("KEYS indexes do not use a specialized index entry format");
|
||||
}
|
||||
|
||||
public boolean isStale(Row row, ByteBuffer indexValue, int nowInSec)
|
||||
{
|
||||
if (row == null)
|
||||
return true;
|
||||
|
||||
Cell cell = row.getCell(indexedColumn);
|
||||
|
||||
return (cell == null
|
||||
|| !cell.isLive(nowInSec)
|
||||
|| indexedColumn.type.compare(indexValue, cell.value()) != 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,35 +15,36 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index.keys;
|
||||
package org.apache.cassandra.index.internal.keys;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.index.*;
|
||||
import org.apache.cassandra.db.filter.*;
|
||||
import org.apache.cassandra.db.filter.DataLimits;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
import org.apache.cassandra.db.partitions.ImmutableBTreePartition;
|
||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.index.internal.CassandraIndex;
|
||||
import org.apache.cassandra.index.internal.CassandraIndexSearcher;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
|
||||
public class KeysSearcher extends SecondaryIndexSearcher
|
||||
public class KeysSearcher extends CassandraIndexSearcher
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(KeysSearcher.class);
|
||||
|
||||
public KeysSearcher(SecondaryIndexManager indexManager, Set<ColumnDefinition> columns)
|
||||
public KeysSearcher(ReadCommand command,
|
||||
RowFilter.Expression expression,
|
||||
CassandraIndex indexer)
|
||||
{
|
||||
super(indexManager, columns);
|
||||
super(command, expression, indexer);
|
||||
}
|
||||
|
||||
protected UnfilteredPartitionIterator queryDataFromIndex(final AbstractSimplePerColumnSecondaryIndex index,
|
||||
final DecoratedKey indexKey,
|
||||
protected UnfilteredPartitionIterator queryDataFromIndex(final DecoratedKey indexKey,
|
||||
final RowIterator indexHits,
|
||||
final ReadCommand command,
|
||||
final ReadOrderGroup orderGroup)
|
||||
|
|
@ -84,21 +85,22 @@ public class KeysSearcher extends SecondaryIndexSearcher
|
|||
while (next == null && indexHits.hasNext())
|
||||
{
|
||||
Row hit = indexHits.next();
|
||||
DecoratedKey key = baseCfs.decorateKey(hit.clustering().get(0));
|
||||
DecoratedKey key = index.baseCfs.decorateKey(hit.clustering().get(0));
|
||||
|
||||
SinglePartitionReadCommand dataCmd = SinglePartitionReadCommand.create(isForThrift(),
|
||||
baseCfs.metadata,
|
||||
index.baseCfs.metadata,
|
||||
command.nowInSec(),
|
||||
command.columnFilter(),
|
||||
command.rowFilter(),
|
||||
DataLimits.NONE,
|
||||
key,
|
||||
command.clusteringIndexFilter(key));
|
||||
|
||||
@SuppressWarnings("resource") // filterIfStale closes it's iterator if either it materialize it or if it returns null.
|
||||
// Otherwise, we close right away if empty, and if it's assigned to next it will be called either
|
||||
// by the next caller of next, or through closing this iterator is this come before.
|
||||
UnfilteredRowIterator dataIter = filterIfStale(dataCmd.queryMemtableAndDisk(baseCfs, orderGroup.baseReadOpOrderGroup()),
|
||||
index,
|
||||
UnfilteredRowIterator dataIter = filterIfStale(dataCmd.queryMemtableAndDisk(index.baseCfs,
|
||||
orderGroup.baseReadOpOrderGroup()),
|
||||
hit,
|
||||
indexKey.getKey(),
|
||||
orderGroup.writeOpOrderGroup(),
|
||||
|
|
@ -131,7 +133,6 @@ public class KeysSearcher extends SecondaryIndexSearcher
|
|||
}
|
||||
|
||||
private UnfilteredRowIterator filterIfStale(UnfilteredRowIterator iterator,
|
||||
AbstractSimplePerColumnSecondaryIndex index,
|
||||
Row indexHit,
|
||||
ByteBuffer indexedValue,
|
||||
OpOrder.Group writeOp,
|
||||
|
|
@ -144,19 +145,37 @@ public class KeysSearcher extends SecondaryIndexSearcher
|
|||
// is the indexed name. Ans so we need to materialize the partition.
|
||||
ImmutableBTreePartition result = ImmutableBTreePartition.create(iterator);
|
||||
iterator.close();
|
||||
Row data = result.getRow(new Clustering(index.indexedColumn().name.bytes));
|
||||
Cell cell = data == null ? null : data.getCell(baseCfs.metadata.compactValueColumn());
|
||||
return deleteIfStale(iterator.partitionKey(), cell, index, indexHit, indexedValue, writeOp, nowInSec)
|
||||
? null
|
||||
: result.unfilteredIterator();
|
||||
Row data = result.getRow(new Clustering(index.getIndexedColumn().name.bytes));
|
||||
|
||||
// for thrift tables, we need to compare the index entry against the compact value column,
|
||||
// not the column actually designated as the indexed column so we don't use the index function
|
||||
// lib for the staleness check like we do in every other case
|
||||
Cell baseData = data.getCell(index.baseCfs.metadata.compactValueColumn());
|
||||
if (baseData == null || !baseData.isLive(nowInSec) || index.getIndexedColumn().type.compare(indexedValue, baseData.value()) != 0)
|
||||
{
|
||||
// Index is stale, remove the index entry and ignore
|
||||
index.deleteStaleEntry(index.getIndexCfs().decorateKey(indexedValue),
|
||||
new Clustering(index.getIndexedColumn().name.bytes),
|
||||
new DeletionTime(indexHit.primaryKeyLivenessInfo().timestamp(), nowInSec),
|
||||
writeOp);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return result.unfilteredIterator();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
assert iterator.metadata().isCompactTable();
|
||||
Row data = iterator.staticRow();
|
||||
Cell cell = data == null ? null : data.getCell(index.indexedColumn());
|
||||
if (deleteIfStale(iterator.partitionKey(), cell, index, indexHit, indexedValue, writeOp, nowInSec))
|
||||
if (index.isStale(data, indexedValue, nowInSec))
|
||||
{
|
||||
// Index is stale, remove the index entry and ignore
|
||||
index.deleteStaleEntry(index.getIndexCfs().decorateKey(indexedValue),
|
||||
makeIndexClustering(iterator.partitionKey().getKey(), Clustering.EMPTY),
|
||||
new DeletionTime(indexHit.primaryKeyLivenessInfo().timestamp(), nowInSec),
|
||||
writeOp);
|
||||
iterator.close();
|
||||
return null;
|
||||
}
|
||||
|
|
@ -166,26 +185,4 @@ public class KeysSearcher extends SecondaryIndexSearcher
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean deleteIfStale(DecoratedKey partitionKey,
|
||||
Cell cell,
|
||||
AbstractSimplePerColumnSecondaryIndex index,
|
||||
Row indexHit,
|
||||
ByteBuffer indexedValue,
|
||||
OpOrder.Group writeOp,
|
||||
int nowInSec)
|
||||
{
|
||||
if (cell == null || !cell.isLive(nowInSec) || index.indexedColumn().type.compare(indexedValue, cell.value()) != 0)
|
||||
{
|
||||
// Index is stale, remove the index entry and ignore
|
||||
index.delete(partitionKey.getKey(),
|
||||
new Clustering(index.indexedColumn().name.bytes),
|
||||
indexedValue,
|
||||
null,
|
||||
new DeletionTime(indexHit.primaryKeyLivenessInfo().timestamp(), nowInSec),
|
||||
writeOp);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.index.transactions;
|
||||
|
||||
import org.apache.cassandra.db.DeletionTime;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
|
||||
/**
|
||||
* Performs garbage collection of index entries during a cleanup.
|
||||
*
|
||||
* Notifies registered indexers of each partition being removed and
|
||||
*
|
||||
* Compaction & Cleanup are somewhat simpler than dealing with incoming writes,
|
||||
* being only concerned with cleaning up stale index entries.
|
||||
*
|
||||
* When multiple versions of a row are compacted, the CleanupTransaction is
|
||||
* notified of the versions being merged, which it diffs against the merge result
|
||||
* and forwards to the registered Index.Indexer instances when on commit.
|
||||
*
|
||||
* Instances are currently scoped to a single row within a partition, but this could be improved to batch process
|
||||
* multiple rows within a single partition.
|
||||
*/
|
||||
public interface CleanupTransaction extends IndexTransaction
|
||||
{
|
||||
|
||||
void onPartitionDeletion(DeletionTime deletionTime);
|
||||
void onRowDelete(Row row);
|
||||
|
||||
CleanupTransaction NO_OP = new CleanupTransaction()
|
||||
{
|
||||
public void start(){}
|
||||
public void onPartitionDeletion(DeletionTime deletionTime){}
|
||||
public void onRowDelete(Row row){}
|
||||
public void commit(){}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.index.transactions;
|
||||
|
||||
import org.apache.cassandra.db.Columns;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
|
||||
/**
|
||||
* Performs garbage collection of stale index entries during a regular compaction.
|
||||
*
|
||||
* A CompactionTransaction is concerned with cleaning up stale index entries.
|
||||
* When multiple versions of a row are compacted, the CompactionTransaction is
|
||||
* notified of the versions being merged, which it diffs against the merge result.
|
||||
*
|
||||
* Instances are currently scoped to a single row within a partition, but this could be improved to batch process
|
||||
* multiple rows within a single partition.
|
||||
*/
|
||||
public interface CompactionTransaction extends IndexTransaction
|
||||
{
|
||||
void onRowMerge(Columns columns, Row merged, Row...versions);
|
||||
|
||||
CompactionTransaction NO_OP = new CompactionTransaction()
|
||||
{
|
||||
public void start(){}
|
||||
public void onRowMerge(Columns columns, Row merged, Row...versions){}
|
||||
public void commit(){}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.index.transactions;
|
||||
|
||||
/**
|
||||
* Base interface for the handling of index updates.
|
||||
* There are 3 types of transaction where indexes are updated to stay in sync with the base table, each represented by
|
||||
* a subinterface:
|
||||
* * {@code UpdateTransaction}
|
||||
* Used on the regular write path and when indexing newly acquired SSTables from streaming or sideloading. This type
|
||||
* of transaction may include both row inserts and updates to rows previously existing in the base Memtable. Instances
|
||||
* are scoped to a single partition update and are obtained from the factory method
|
||||
* @{code SecondaryIndexManager#newUpdateTransaction}
|
||||
*
|
||||
* * {@code CompactionTransaction}
|
||||
* Used during compaction when stale entries which have been superceded are cleaned up from the index. As rows in a
|
||||
* partition are merged during the compaction, index entries for any purged rows are cleaned from the index to
|
||||
* compensate for the fact that they may not have been removed at write time if the data in the base table had been
|
||||
* already flushed to disk (and so was processed as an insert, not an update by the UpdateTransaction). These
|
||||
* transactions are currently scoped to a single row within a partition, but this could be improved to batch process
|
||||
* multiple rows within a single partition.
|
||||
*
|
||||
* * @{code CleanupTransaction}
|
||||
* During cleanup no merging is required, the only thing to do is to notify indexes of the partitions being removed,
|
||||
* along with the rows within those partitions. Like with compaction, these transactions are currently scoped to a
|
||||
* single row within a partition, but this could be improved with batching.
|
||||
*/
|
||||
public interface IndexTransaction
|
||||
{
|
||||
/**
|
||||
* Used to differentiate between type of index transaction when obtaining
|
||||
* a handler from Index implementations.
|
||||
*/
|
||||
public enum Type
|
||||
{
|
||||
UPDATE, COMPACTION, CLEANUP
|
||||
}
|
||||
|
||||
void start();
|
||||
void commit();
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.index.transactions;
|
||||
|
||||
import org.apache.cassandra.db.DeletionTime;
|
||||
import org.apache.cassandra.db.RangeTombstone;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
|
||||
/**
|
||||
* Handling of index updates on the write path.
|
||||
*
|
||||
* Instances of an UpdateTransaction are scoped to a single partition update
|
||||
* A new instance is used for every write, obtained from the
|
||||
* newUpdateTransaction(PartitionUpdate) method. Likewise, a single
|
||||
* CleanupTransaction instance is used for each partition processed during a
|
||||
* compaction or cleanup.
|
||||
*
|
||||
* We make certain guarantees about the lifecycle of each UpdateTransaction
|
||||
* instance. Namely that start() will be called before any other method, and
|
||||
* commit() will be called at the end of the update.
|
||||
* Each instance is initialized with 1..many Index.Indexer instances, one per
|
||||
* registered Index. As with the transaction itself, these are scoped to a
|
||||
* specific partition update, so implementations can be assured that all indexing
|
||||
* events they receive relate to the same logical operation.
|
||||
*
|
||||
* onPartitionDelete(), onRangeTombstone(), onInserted() and onUpdated()
|
||||
* calls may arrive in any order, but this should have no impact for the
|
||||
* Indexers being notified as any events delivered to a single instance
|
||||
* necessarily relate to a single partition.
|
||||
*
|
||||
* The typical sequence of events during a Memtable update would be:
|
||||
* start() -- no-op, used to notify Indexers of the start of the transaction
|
||||
* onPartitionDeletion(dt) -- if the PartitionUpdate implies one
|
||||
* onRangeTombstone(rt)* -- for each in the PartitionUpdate, if any
|
||||
*
|
||||
* then:
|
||||
* onInserted(row)* -- called for each Row not already present in the Memtable
|
||||
* onUpdated(existing, updated)* -- called for any Row in the update for where a version was already present
|
||||
* in the Memtable. It's important to note here that existing is the previous
|
||||
* row from the Memtable & updated is the final version replacing it. It is
|
||||
* *not* the incoming row, but the result of merging the incoming and existing
|
||||
* rows.
|
||||
* commit() -- finally, finish is called when the new Partition is swapped into the Memtable
|
||||
*/
|
||||
public interface UpdateTransaction extends IndexTransaction
|
||||
{
|
||||
void onPartitionDeletion(DeletionTime deletionTime);
|
||||
void onRangeTombstone(RangeTombstone rangeTombstone);
|
||||
void onInserted(Row row);
|
||||
void onUpdated(Row existing, Row updated);
|
||||
|
||||
UpdateTransaction NO_OP = new UpdateTransaction()
|
||||
{
|
||||
public void start(){}
|
||||
public void onPartitionDeletion(DeletionTime deletionTime){}
|
||||
public void onRangeTombstone(RangeTombstone rangeTombstone){}
|
||||
public void onInserted(Row row){}
|
||||
public void onUpdated(Row existing, Row updated){}
|
||||
public void commit(){}
|
||||
};
|
||||
}
|
||||
|
|
@ -40,14 +40,18 @@ import org.apache.cassandra.cache.InstrumentingCache;
|
|||
import org.apache.cassandra.cache.KeyCacheKey;
|
||||
import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor;
|
||||
import org.apache.cassandra.concurrent.ScheduledExecutors;
|
||||
import org.apache.cassandra.config.*;
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.config.Schema;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.commitlog.ReplayPosition;
|
||||
import org.apache.cassandra.db.filter.ColumnFilter;
|
||||
import org.apache.cassandra.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.db.lifecycle.TransactionLog;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.dht.*;
|
||||
import org.apache.cassandra.db.rows.SliceableUnfilteredRowIterator;
|
||||
import org.apache.cassandra.dht.AbstractBounds;
|
||||
import org.apache.cassandra.dht.Range;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.index.internal.CassandraIndex;
|
||||
import org.apache.cassandra.io.FSError;
|
||||
import org.apache.cassandra.io.compress.CompressionMetadata;
|
||||
import org.apache.cassandra.io.sstable.*;
|
||||
|
|
@ -340,8 +344,9 @@ public abstract class SSTableReader extends SSTable implements SelfRefCounted<SS
|
|||
CFMetaData parent = Schema.instance.getCFMetaData(descriptor.ksname, parentName);
|
||||
IndexMetadata def = parent.getIndexes()
|
||||
.get(indexName)
|
||||
.orElseThrow(() -> new AssertionError("Could not find index metadata for index cf " + i));
|
||||
metadata = SecondaryIndex.newIndexMetadata(parent, def);
|
||||
.orElseThrow(() -> new AssertionError(
|
||||
"Could not find index metadata for index cf " + i));
|
||||
metadata = CassandraIndex.indexCfsMetadata(parent, def);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package org.apache.cassandra.schema;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
|
@ -25,20 +26,26 @@ import java.util.Set;
|
|||
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;
|
||||
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.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.cql3.statements.IndexTarget;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.index.Index;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
/**
|
||||
* An immutable representation of secondary index metadata.
|
||||
*/
|
||||
public final class IndexMetadata
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(IndexMetadata.class);
|
||||
|
||||
public enum IndexType
|
||||
{
|
||||
KEYS, CUSTOM, COMPOSITES
|
||||
|
|
@ -68,20 +75,20 @@ public final class IndexMetadata
|
|||
this.columns = columns == null ? ImmutableSet.of() : ImmutableSet.copyOf(columns);
|
||||
}
|
||||
|
||||
public static IndexMetadata legacyIndex(ColumnIdentifier column,
|
||||
String name,
|
||||
IndexType type,
|
||||
Map<String, String> options)
|
||||
public static IndexMetadata singleColumnIndex(ColumnIdentifier column,
|
||||
String name,
|
||||
IndexType type,
|
||||
Map<String, String> options)
|
||||
{
|
||||
return new IndexMetadata(name, options, type, TargetType.COLUMN, Collections.singleton(column));
|
||||
}
|
||||
|
||||
public static IndexMetadata legacyIndex(ColumnDefinition column,
|
||||
String name,
|
||||
IndexType type,
|
||||
Map<String, String> options)
|
||||
public static IndexMetadata singleColumnIndex(ColumnDefinition column,
|
||||
String name,
|
||||
IndexType type,
|
||||
Map<String, String> options)
|
||||
{
|
||||
return legacyIndex(column.name, name, type, options);
|
||||
return singleColumnIndex(column.name, name, type, options);
|
||||
}
|
||||
|
||||
public static boolean isNameValid(String name)
|
||||
|
|
@ -107,11 +114,54 @@ public final class IndexMetadata
|
|||
throw new ConfigurationException("Target type is null for index " + name);
|
||||
|
||||
if (indexType == IndexMetadata.IndexType.CUSTOM)
|
||||
if (options == null || !options.containsKey(SecondaryIndex.CUSTOM_INDEX_OPTION_NAME))
|
||||
{
|
||||
if (options == null || !options.containsKey(IndexTarget.CUSTOM_INDEX_OPTION_NAME))
|
||||
throw new ConfigurationException(String.format("Required option missing for index %s : %s",
|
||||
name, SecondaryIndex.CUSTOM_INDEX_OPTION_NAME));
|
||||
name, IndexTarget.CUSTOM_INDEX_OPTION_NAME));
|
||||
String className = options.get(IndexTarget.CUSTOM_INDEX_OPTION_NAME);
|
||||
Class<Index> indexerClass = FBUtilities.classForName(className, "custom indexer");
|
||||
if(!Index.class.isAssignableFrom(indexerClass))
|
||||
throw new ConfigurationException(String.format("Specified Indexer class (%s) does not implement the Indexer interface", className));
|
||||
validateCustomIndexOptions(indexerClass, options);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateCustomIndexOptions(Class<? extends Index> indexerClass, Map<String, String> options) throws ConfigurationException
|
||||
{
|
||||
try
|
||||
{
|
||||
Map<String, String> filteredOptions =
|
||||
Maps.filterKeys(options,key -> !key.equals(IndexTarget.CUSTOM_INDEX_OPTION_NAME));
|
||||
|
||||
if (filteredOptions.isEmpty())
|
||||
return;
|
||||
|
||||
Map<?,?> unknownOptions = (Map) indexerClass.getMethod("validateOptions", Map.class).invoke(null, filteredOptions);
|
||||
if (!unknownOptions.isEmpty())
|
||||
throw new ConfigurationException(String.format("Properties specified %s are not understood by %s", unknownOptions.keySet(), indexerClass.getSimpleName()));
|
||||
}
|
||||
catch (NoSuchMethodException e)
|
||||
{
|
||||
logger.info("Indexer {} does not have a static validateOptions method. Validation ignored",
|
||||
indexerClass.getName());
|
||||
}
|
||||
catch (InvocationTargetException e)
|
||||
{
|
||||
if (e.getTargetException() instanceof ConfigurationException)
|
||||
throw (ConfigurationException) e.getTargetException();
|
||||
throw new ConfigurationException("Failed to validate custom indexer options: " + options);
|
||||
}
|
||||
catch (ConfigurationException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ConfigurationException("Failed to validate custom indexer options: " + options);
|
||||
}
|
||||
}
|
||||
|
||||
// to be removed in CASSANDRA-10124 with multi-target & row based indexes
|
||||
public ColumnDefinition indexedColumn(CFMetaData cfm)
|
||||
{
|
||||
return cfm.getColumnDefinition(columns.iterator().next());
|
||||
|
|
@ -132,11 +182,29 @@ public final class IndexMetadata
|
|||
return indexType == IndexType.COMPOSITES;
|
||||
}
|
||||
|
||||
public boolean isRowIndex()
|
||||
{
|
||||
return targetType == TargetType.ROW;
|
||||
}
|
||||
|
||||
public boolean isColumnIndex()
|
||||
{
|
||||
return targetType == TargetType.COLUMN;
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hashCode(name, indexType, targetType, options, columns);
|
||||
}
|
||||
|
||||
public boolean equalsWithoutName(IndexMetadata other)
|
||||
{
|
||||
return Objects.equal(indexType, other.indexType)
|
||||
&& Objects.equal(targetType, other.targetType)
|
||||
&& Objects.equal(columns, other.columns)
|
||||
&& Objects.equal(options, other.options);
|
||||
}
|
||||
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (obj == this)
|
||||
|
|
@ -147,11 +215,7 @@ public final class IndexMetadata
|
|||
|
||||
IndexMetadata other = (IndexMetadata)obj;
|
||||
|
||||
return Objects.equal(name, other.name)
|
||||
&& Objects.equal(indexType, other.indexType)
|
||||
&& Objects.equal(targetType, other.targetType)
|
||||
&& Objects.equal(options, other.options)
|
||||
&& Objects.equal(columns, other.columns);
|
||||
return Objects.equal(name, other.name) && equalsWithoutName(other);
|
||||
}
|
||||
|
||||
public String toString()
|
||||
|
|
|
|||
|
|
@ -21,6 +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;
|
||||
|
|
@ -39,17 +40,13 @@ import static com.google.common.collect.Iterables.filter;
|
|||
*/
|
||||
public class Indexes implements Iterable<IndexMetadata>
|
||||
{
|
||||
// lookup for index by target column
|
||||
private final ImmutableMap<ColumnIdentifier, IndexMetadata> indexes;
|
||||
private final ImmutableMap<String, IndexMetadata> indexes;
|
||||
private final ImmutableMultimap<ColumnIdentifier, IndexMetadata> indexesByColumn;
|
||||
|
||||
private Indexes(Builder builder)
|
||||
{
|
||||
ImmutableMap.Builder<ColumnIdentifier, IndexMetadata> internalBuilder = ImmutableMap.builder();
|
||||
builder.indexes.build()
|
||||
.values()
|
||||
.stream()
|
||||
.forEach(def -> internalBuilder.put(def.columns.iterator().next(), def));
|
||||
indexes = internalBuilder.build();
|
||||
indexes = builder.indexes.build();
|
||||
indexesByColumn = builder.indexesByColumn.build();
|
||||
}
|
||||
|
||||
public static Builder builder()
|
||||
|
|
@ -105,9 +102,9 @@ public class Indexes implements Iterable<IndexMetadata>
|
|||
* @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 Optional<IndexMetadata> get(ColumnDefinition column)
|
||||
public Collection<IndexMetadata> get(ColumnDefinition column)
|
||||
{
|
||||
return Optional.ofNullable(indexes.get(column.name));
|
||||
return indexesByColumn.get(column.name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -117,7 +114,7 @@ public class Indexes implements Iterable<IndexMetadata>
|
|||
*/
|
||||
public boolean hasIndexFor(ColumnDefinition column)
|
||||
{
|
||||
return indexes.get(column.name) != null;
|
||||
return !indexesByColumn.get(column.name).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -183,6 +180,7 @@ public class Indexes implements Iterable<IndexMetadata>
|
|||
public static final class Builder
|
||||
{
|
||||
final ImmutableMap.Builder<String, IndexMetadata> indexes = new ImmutableMap.Builder<>();
|
||||
final ImmutableMultimap.Builder<ColumnIdentifier, IndexMetadata> indexesByColumn = new ImmutableMultimap.Builder<>();
|
||||
|
||||
private Builder()
|
||||
{
|
||||
|
|
@ -196,6 +194,13 @@ public class Indexes implements Iterable<IndexMetadata>
|
|||
public Builder add(IndexMetadata index)
|
||||
{
|
||||
indexes.put(index.name, index);
|
||||
// All indexes are column indexes at the moment
|
||||
if (index.isColumnIndex())
|
||||
{
|
||||
for (ColumnIdentifier target : index.columns)
|
||||
indexesByColumn.put(target, index);
|
||||
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.config.*;
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.QueryProcessor;
|
||||
import org.apache.cassandra.cql3.UntypedResultSet;
|
||||
import org.apache.cassandra.cql3.functions.FunctionName;
|
||||
import org.apache.cassandra.cql3.functions.UDAggregate;
|
||||
import org.apache.cassandra.cql3.functions.UDFunction;
|
||||
|
|
@ -628,7 +630,7 @@ public final class LegacySchemaMigrator
|
|||
isStaticCompactTable,
|
||||
needsUpgrade);
|
||||
|
||||
indexes.add(IndexMetadata.legacyIndex(column, indexName, indexType, indexOptions));
|
||||
indexes.add(IndexMetadata.singleColumnIndex(column, indexName, indexType, indexOptions));
|
||||
}
|
||||
|
||||
return indexes.build();
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ import org.apache.cassandra.cql3.ColumnIdentifier;
|
|||
import org.apache.cassandra.cql3.QueryProcessor;
|
||||
import org.apache.cassandra.cql3.UntypedResultSet;
|
||||
import org.apache.cassandra.cql3.functions.*;
|
||||
import org.apache.cassandra.db.ClusteringComparator;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.marshal.*;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
|
|
@ -1427,7 +1426,7 @@ public final class SchemaKeyspace
|
|||
findColumnIdentifierWithName(targetColumnName, cfm.allColumns()).ifPresent(targetColumns::add);
|
||||
});
|
||||
}
|
||||
return IndexMetadata.legacyIndex(targetColumns.iterator().next(), name, type, options);
|
||||
return IndexMetadata.singleColumnIndex(targetColumns.iterator().next(), name, type, options);
|
||||
}
|
||||
|
||||
private static Optional<ColumnIdentifier> findColumnIdentifierWithName(String name,
|
||||
|
|
|
|||
|
|
@ -26,14 +26,12 @@ import org.apache.cassandra.concurrent.StageManager;
|
|||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.filter.*;
|
||||
import org.apache.cassandra.db.filter.ClusteringIndexFilter;
|
||||
import org.apache.cassandra.db.filter.DataLimits;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.exceptions.ReadTimeoutException;
|
||||
import org.apache.cassandra.net.AsyncOneResponse;
|
||||
import org.apache.cassandra.net.MessageIn;
|
||||
import org.apache.cassandra.net.MessageOut;
|
||||
import org.apache.cassandra.net.MessagingService;
|
||||
import org.apache.cassandra.net.*;
|
||||
import org.apache.cassandra.tracing.Tracing;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
|
|
@ -237,7 +235,7 @@ public class DataResolver extends ResponseResolver
|
|||
if (merged.isEmpty())
|
||||
return;
|
||||
|
||||
Rows.diff(merged, columns, versions, diffListener);
|
||||
Rows.diff(diffListener, merged, columns, versions);
|
||||
for (int i = 0; i < currentRows.length; i++)
|
||||
{
|
||||
if (currentRows[i] != null)
|
||||
|
|
|
|||
|
|
@ -31,11 +31,6 @@ import com.google.common.base.Predicate;
|
|||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.collect.*;
|
||||
import com.google.common.util.concurrent.Uninterruptibles;
|
||||
|
||||
import org.apache.cassandra.db.view.MaterializedViewManager;
|
||||
import org.apache.cassandra.db.view.MaterializedViewUtils;
|
||||
import org.apache.cassandra.db.HintedHandOffManager;
|
||||
import org.apache.cassandra.metrics.*;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -46,31 +41,31 @@ import org.apache.cassandra.config.CFMetaData;
|
|||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.config.Schema;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.filter.DataLimits;
|
||||
import org.apache.cassandra.db.filter.TombstoneOverwhelmingException;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexSearcher;
|
||||
import org.apache.cassandra.db.marshal.UUIDType;
|
||||
import org.apache.cassandra.dht.AbstractBounds;
|
||||
import org.apache.cassandra.dht.Bounds;
|
||||
import org.apache.cassandra.dht.RingPosition;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.rows.RowIterator;
|
||||
import org.apache.cassandra.db.view.MaterializedViewManager;
|
||||
import org.apache.cassandra.db.view.MaterializedViewUtils;
|
||||
import org.apache.cassandra.dht.*;
|
||||
import org.apache.cassandra.exceptions.*;
|
||||
import org.apache.cassandra.gms.FailureDetector;
|
||||
import org.apache.cassandra.gms.Gossiper;
|
||||
import org.apache.cassandra.hints.Hint;
|
||||
import org.apache.cassandra.hints.HintsService;
|
||||
import org.apache.cassandra.index.Index;
|
||||
import org.apache.cassandra.io.util.DataOutputBuffer;
|
||||
import org.apache.cassandra.locator.AbstractReplicationStrategy;
|
||||
import org.apache.cassandra.locator.IEndpointSnitch;
|
||||
import org.apache.cassandra.locator.LocalStrategy;
|
||||
import org.apache.cassandra.locator.TokenMetadata;
|
||||
import org.apache.cassandra.locator.*;
|
||||
import org.apache.cassandra.metrics.*;
|
||||
import org.apache.cassandra.net.*;
|
||||
import org.apache.cassandra.service.paxos.*;
|
||||
import org.apache.cassandra.service.paxos.Commit;
|
||||
import org.apache.cassandra.service.paxos.PrepareCallback;
|
||||
import org.apache.cassandra.service.paxos.ProposeCallback;
|
||||
import org.apache.cassandra.tracing.Tracing;
|
||||
import org.apache.cassandra.triggers.TriggerExecutor;
|
||||
import org.apache.cassandra.utils.*;
|
||||
import org.apache.cassandra.utils.AbstractIterator;
|
||||
import org.apache.cassandra.utils.*;
|
||||
|
||||
public class StorageProxy implements StorageProxyMBean
|
||||
{
|
||||
|
|
@ -1698,11 +1693,10 @@ public class StorageProxy implements StorageProxyMBean
|
|||
private static float estimateResultsPerRange(PartitionRangeReadCommand command, Keyspace keyspace)
|
||||
{
|
||||
ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(command.metadata().cfId);
|
||||
SecondaryIndexSearcher searcher = cfs.indexManager.getBestIndexSearcherFor(command);
|
||||
|
||||
float maxExpectedResults = searcher == null
|
||||
Index index = cfs.indexManager.getBestIndexFor(command);
|
||||
float maxExpectedResults = index == null
|
||||
? command.limits().estimateTotalResults(cfs)
|
||||
: searcher.highestSelectivityIndex(command.rowFilter()).estimateResultRows();
|
||||
: index.getEstimatedResultRows();
|
||||
|
||||
// adjust maxExpectedResults by the number of tokens this node has and the replication factor for this ks
|
||||
return (maxExpectedResults / DatabaseDescriptor.getNumTokens()) / keyspace.getReplicationStrategy().getReplicationFactor();
|
||||
|
|
@ -1962,7 +1956,9 @@ public class StorageProxy implements StorageProxyMBean
|
|||
}
|
||||
|
||||
Tracing.trace("Submitted {} concurrent range requests", concurrentQueries.size());
|
||||
return new CountingPartitionIterator(PartitionIterators.concat(concurrentQueries), command.limits(), command.nowInSec());
|
||||
// We want to count the results for the sake of updating the concurrency factor (see updateConcurrencyFactor) but we don't want to
|
||||
// enforce any particular limit at this point (this could break code than rely on postReconciliationProcessing), hence the DataLimits.NONE.
|
||||
return new CountingPartitionIterator(PartitionIterators.concat(concurrentQueries), DataLimits.NONE, command.nowInSec());
|
||||
}
|
||||
|
||||
public void close()
|
||||
|
|
@ -2003,7 +1999,8 @@ public class StorageProxy implements StorageProxyMBean
|
|||
Tracing.trace("Submitting range requests on {} ranges with a concurrency of {} ({} rows per range expected)", ranges.rangeCount(), concurrencyFactor, resultsPerRange);
|
||||
|
||||
// Note that in general, a RangeCommandIterator will honor the command limit for each range, but will not enforce it globally.
|
||||
return command.postReconciliationProcessing(command.limits().filter(new RangeCommandIterator(ranges, command, concurrencyFactor, keyspace, consistencyLevel), command.nowInSec()));
|
||||
|
||||
return command.limits().filter(command.postReconciliationProcessing(new RangeCommandIterator(ranges, command, concurrencyFactor, keyspace, consistencyLevel)), command.nowInSec());
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getSchemaVersions()
|
||||
|
|
|
|||
|
|
@ -17,10 +17,7 @@
|
|||
*/
|
||||
package org.apache.cassandra.streaming;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
|
|
@ -31,21 +28,17 @@ import org.apache.cassandra.concurrent.NamedThreadFactory;
|
|||
import org.apache.cassandra.config.Schema;
|
||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||
import org.apache.cassandra.db.Keyspace;
|
||||
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
|
||||
import org.apache.cassandra.db.compaction.OperationType;
|
||||
import org.apache.cassandra.db.Mutation;
|
||||
import org.apache.cassandra.db.compaction.OperationType;
|
||||
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.db.rows.RowIterators;
|
||||
import org.apache.cassandra.db.rows.Unfiltered;
|
||||
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
|
||||
import org.apache.cassandra.db.rows.UnfilteredRowIterators;
|
||||
import org.apache.cassandra.io.sstable.ISSTableScanner;
|
||||
import org.apache.cassandra.io.sstable.SSTableMultiWriter;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableWriter;
|
||||
import org.apache.cassandra.utils.JVMStabilityInspector;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
import org.apache.cassandra.utils.concurrent.Refs;
|
||||
|
||||
/**
|
||||
|
|
@ -176,7 +169,7 @@ public class StreamReceiveTask extends StreamTask
|
|||
|
||||
// add sstables and build secondary indexes
|
||||
cfs.addSSTables(readers);
|
||||
cfs.indexManager.maybeBuildSecondaryIndexes(readers, cfs.indexManager.allIndexesNames());
|
||||
cfs.indexManager.buildAllIndexesBlocking(readers);
|
||||
}
|
||||
}
|
||||
catch (Throwable t)
|
||||
|
|
|
|||
|
|
@ -38,12 +38,12 @@ import org.apache.cassandra.config.*;
|
|||
import org.apache.cassandra.cql3.QueryOptions;
|
||||
import org.apache.cassandra.cql3.statements.ParsedStatement;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.context.CounterContext;
|
||||
import org.apache.cassandra.db.filter.*;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.TimeUUIDType;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.dht.*;
|
||||
import org.apache.cassandra.dht.Range;
|
||||
import org.apache.cassandra.exceptions.*;
|
||||
|
|
@ -826,6 +826,9 @@ public class CassandraServer implements Cassandra.Iface
|
|||
Cell cell = cellFromColumn(metadata, name, column);
|
||||
PartitionUpdate update = PartitionUpdate.singleRowUpdate(metadata, key, BTreeRow.singleCellRow(name.clustering, cell));
|
||||
|
||||
// Indexed column values cannot be larger than 64K. See CASSANDRA-3057/4240 for more details
|
||||
Keyspace.open(metadata.ksName).getColumnFamilyStore(metadata.cfName).indexManager.validate(update);
|
||||
|
||||
mutation = new org.apache.cassandra.db.Mutation(update);
|
||||
}
|
||||
catch (MarshalException|UnknownColumnException e)
|
||||
|
|
@ -916,6 +919,8 @@ public class CassandraServer implements Cassandra.Iface
|
|||
int nowInSec = FBUtilities.nowInSeconds();
|
||||
|
||||
PartitionUpdate partitionUpdates = PartitionUpdate.fromIterator(LegacyLayout.toRowIterator(metadata, dk, toLegacyCells(metadata, updates, nowInSec).iterator(), nowInSec));
|
||||
// Indexed column values cannot be larger than 64K. See CASSANDRA-3057/4240 for more details
|
||||
Keyspace.open(metadata.ksName).getColumnFamilyStore(metadata.cfName).indexManager.validate(partitionUpdates);
|
||||
|
||||
FilteredPartition partitionExpected = null;
|
||||
if (!expected.isEmpty())
|
||||
|
|
@ -1121,6 +1126,9 @@ public class CassandraServer implements Cassandra.Iface
|
|||
DecoratedKey dk = metadata.decorateKey(key);
|
||||
PartitionUpdate update = PartitionUpdate.fromIterator(LegacyLayout.toUnfilteredRowIterator(metadata, dk, delInfo, cells.iterator()));
|
||||
|
||||
// Indexed column values cannot be larger than 64K. See CASSANDRA-3057/4240 for more details
|
||||
Keyspace.open(metadata.ksName).getColumnFamilyStore(metadata.cfName).indexManager.validate(update);
|
||||
|
||||
org.apache.cassandra.db.Mutation mutation;
|
||||
if (metadata.isCounter())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,21 +23,17 @@ import com.google.common.annotations.VisibleForTesting;
|
|||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import org.apache.cassandra.db.compaction.AbstractCompactionStrategy;
|
||||
import org.apache.cassandra.io.compress.ICompressor;
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.config.Schema;
|
||||
import org.apache.cassandra.config.*;
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.Operator;
|
||||
import org.apache.cassandra.db.CompactTables;
|
||||
import org.apache.cassandra.db.LegacyLayout;
|
||||
import org.apache.cassandra.db.WriteType;
|
||||
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.schema.CompressionParams;
|
||||
import org.apache.cassandra.io.compress.ICompressor;
|
||||
import org.apache.cassandra.locator.AbstractReplicationStrategy;
|
||||
import org.apache.cassandra.locator.LocalStrategy;
|
||||
import org.apache.cassandra.schema.*;
|
||||
|
|
@ -560,10 +556,10 @@ public class ThriftConversion
|
|||
Map<String, String> indexOptions = def.getIndex_options();
|
||||
IndexMetadata.IndexType indexType = IndexMetadata.IndexType.valueOf(def.index_type.name());
|
||||
|
||||
indexes.add(IndexMetadata.legacyIndex(column,
|
||||
indexName,
|
||||
indexType,
|
||||
indexOptions));
|
||||
indexes.add(IndexMetadata.singleColumnIndex(column,
|
||||
indexName,
|
||||
indexType,
|
||||
indexOptions));
|
||||
}
|
||||
}
|
||||
return indexes.build();
|
||||
|
|
@ -576,13 +572,22 @@ public class ThriftConversion
|
|||
|
||||
cd.setName(ByteBufferUtil.clone(column.name.bytes));
|
||||
cd.setValidation_class(column.type.toString());
|
||||
Optional<IndexMetadata> index = cfMetaData.getIndexes().get(column);
|
||||
index.ifPresent(def -> {
|
||||
cd.setIndex_type(org.apache.cassandra.thrift.IndexType.valueOf(def.indexType.name()));
|
||||
cd.setIndex_name(def.name);
|
||||
cd.setIndex_options(def.options == null || def.options.isEmpty() ? null : Maps.newHashMap(def.options));
|
||||
});
|
||||
|
||||
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)
|
||||
{
|
||||
IndexMetadata index = indexes.iterator().next();
|
||||
if (index.columns.size() == 1)
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
return cd;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,18 +18,24 @@
|
|||
package org.apache.cassandra.thrift;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.config.*;
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.config.Schema;
|
||||
import org.apache.cassandra.cql3.Attributes;
|
||||
import org.apache.cassandra.cql3.Operator;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.dht.IPartitioner;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.index.Index;
|
||||
import org.apache.cassandra.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.serializers.MarshalException;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
|
@ -449,8 +455,6 @@ public class ThriftValidation
|
|||
LegacyLayout.LegacyCellName cn = LegacyLayout.decodeCellName(metadata, scName, column.name);
|
||||
cn.column.validateCellValue(column.value);
|
||||
|
||||
// Indexed column values cannot be larger than 64K. See CASSANDRA-3057/4240 for more details
|
||||
Keyspace.open(metadata.ksName).getColumnFamilyStore(metadata.cfName).indexManager.validate(cn.column, column.value, null);
|
||||
}
|
||||
catch (UnknownColumnException e)
|
||||
{
|
||||
|
|
@ -609,7 +613,8 @@ public class ThriftValidation
|
|||
me.getMessage()));
|
||||
}
|
||||
|
||||
isIndexed |= (expression.op == IndexOperator.EQ) && idxManager.indexes(def);
|
||||
for(Index index : idxManager.listIndexes())
|
||||
isIndexed |= index.supportsExpression(def, Operator.valueOf(expression.op.name()));
|
||||
}
|
||||
|
||||
return isIndexed;
|
||||
|
|
|
|||
|
|
@ -27,15 +27,15 @@ import org.junit.BeforeClass;
|
|||
import org.apache.cassandra.config.*;
|
||||
import org.apache.cassandra.cql3.CQLTester;
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.cql3.statements.IndexTarget;
|
||||
import org.apache.cassandra.db.RowUpdateBuilder;
|
||||
import org.apache.cassandra.db.commitlog.CommitLog;
|
||||
import org.apache.cassandra.db.index.PerRowSecondaryIndexTest;
|
||||
import org.apache.cassandra.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.db.marshal.*;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.gms.Gossiper;
|
||||
import org.apache.cassandra.schema.*;
|
||||
import org.apache.cassandra.index.StubIndex;
|
||||
import org.apache.cassandra.io.util.FileUtils;
|
||||
import org.apache.cassandra.schema.*;
|
||||
import org.apache.cassandra.service.MigrationManager;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
|
@ -218,10 +218,6 @@ public class SchemaLoader
|
|||
schema.add(KeyspaceMetadata.create(ks_nocommit, KeyspaceParams.simpleTransient(1), Tables.of(
|
||||
standardCFMD(ks_nocommit, "Standard1"))));
|
||||
|
||||
// PerRowSecondaryIndexTest
|
||||
schema.add(KeyspaceMetadata.create(ks_prsi, KeyspaceParams.simple(1), Tables.of(
|
||||
perRowIndexedCFMD(ks_prsi, "Indexed1"))));
|
||||
|
||||
// CQLKeyspace
|
||||
schema.add(KeyspaceMetadata.create(ks_cql, KeyspaceParams.simple(1), Tables.of(
|
||||
|
||||
|
|
@ -291,8 +287,8 @@ public class SchemaLoader
|
|||
public static CFMetaData perRowIndexedCFMD(String ksName, String cfName)
|
||||
{
|
||||
final Map<String, String> indexOptions = Collections.singletonMap(
|
||||
SecondaryIndex.CUSTOM_INDEX_OPTION_NAME,
|
||||
PerRowSecondaryIndexTest.TestIndex.class.getName());
|
||||
IndexTarget.CUSTOM_INDEX_OPTION_NAME,
|
||||
StubIndex.class.getName());
|
||||
|
||||
CFMetaData cfm = CFMetaData.Builder.create(ksName, cfName)
|
||||
.addPartitionKey("key", AsciiType.instance)
|
||||
|
|
@ -303,10 +299,10 @@ public class SchemaLoader
|
|||
|
||||
cfm.indexes(
|
||||
cfm.getIndexes()
|
||||
.with(IndexMetadata.legacyIndex(indexedColumn,
|
||||
"indexe1",
|
||||
IndexMetadata.IndexType.CUSTOM,
|
||||
indexOptions)));
|
||||
.with(IndexMetadata.singleColumnIndex(indexedColumn,
|
||||
"indexe1",
|
||||
IndexMetadata.IndexType.CUSTOM,
|
||||
indexOptions)));
|
||||
return cfm;
|
||||
}
|
||||
|
||||
|
|
@ -414,10 +410,10 @@ public class SchemaLoader
|
|||
if (withIndex)
|
||||
cfm.indexes(
|
||||
cfm.getIndexes()
|
||||
.with(IndexMetadata.legacyIndex(cfm.getColumnDefinition(new ColumnIdentifier("birthdate", true)),
|
||||
"birthdate_key_index",
|
||||
IndexMetadata.IndexType.COMPOSITES,
|
||||
Collections.EMPTY_MAP)));
|
||||
.with(IndexMetadata.singleColumnIndex(cfm.getColumnDefinition(new ColumnIdentifier("birthdate", true)),
|
||||
"birthdate_key_index",
|
||||
IndexMetadata.IndexType.COMPOSITES,
|
||||
Collections.EMPTY_MAP)));
|
||||
|
||||
return cfm.compression(getCompressionParameters());
|
||||
}
|
||||
|
|
@ -434,10 +430,10 @@ public class SchemaLoader
|
|||
if (withIndex)
|
||||
cfm.indexes(
|
||||
cfm.getIndexes()
|
||||
.with(IndexMetadata.legacyIndex(cfm.getColumnDefinition(new ColumnIdentifier("birthdate", true)),
|
||||
"birthdate_composite_index",
|
||||
IndexMetadata.IndexType.KEYS,
|
||||
Collections.EMPTY_MAP)));
|
||||
.with(IndexMetadata.singleColumnIndex(cfm.getColumnDefinition(new ColumnIdentifier("birthdate", true)),
|
||||
"birthdate_composite_index",
|
||||
IndexMetadata.IndexType.KEYS,
|
||||
Collections.EMPTY_MAP)));
|
||||
|
||||
|
||||
return cfm.compression(getCompressionParameters());
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import java.util.function.Supplier;
|
|||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Iterators;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
|
|
@ -40,13 +39,15 @@ import org.apache.cassandra.config.ColumnDefinition;
|
|||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.compaction.AbstractCompactionTask;
|
||||
import org.apache.cassandra.db.compaction.CompactionManager;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.dht.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.dht.IPartitioner;
|
||||
import org.apache.cassandra.dht.RandomPartitioner.BigIntegerToken;
|
||||
import org.apache.cassandra.dht.Range;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.gms.ApplicationState;
|
||||
import org.apache.cassandra.gms.Gossiper;
|
||||
import org.apache.cassandra.gms.VersionedValue;
|
||||
|
|
@ -531,4 +532,10 @@ public class Util
|
|||
{
|
||||
thread.join(10000);
|
||||
}
|
||||
|
||||
// for use with Optional in tests, can be used as an argument to orElseThrow
|
||||
public static Supplier<AssertionError> throwAssert(final String message)
|
||||
{
|
||||
return () -> new AssertionError(message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,20 +18,24 @@
|
|||
package org.apache.cassandra.cql3.validation.entities;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
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.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.utils.FBUtilities;
|
||||
import org.apache.cassandra.index.StubIndex;
|
||||
|
||||
import static org.apache.cassandra.Util.throwAssert;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
|
|
@ -70,13 +74,23 @@ public class SecondaryIndexTest extends CQLTester
|
|||
private void testCreateAndDropIndex(String indexName, boolean addKeyspaceOnDrop) throws Throwable
|
||||
{
|
||||
execute("USE system");
|
||||
assertInvalidMessage("Index '" + removeQuotes(indexName.toLowerCase(Locale.US)) + "' could not be found", "DROP INDEX " + indexName + ";");
|
||||
assertInvalidMessage(String.format("Index '%s' could not be found",
|
||||
removeQuotes(indexName.toLowerCase(Locale.US))),
|
||||
"DROP INDEX " + indexName + ";");
|
||||
|
||||
createTable("CREATE TABLE %s (a int primary key, b int);");
|
||||
createIndex("CREATE INDEX " + indexName + " ON %s(b);");
|
||||
createIndex("CREATE INDEX IF NOT EXISTS " + indexName + " ON %s(b);");
|
||||
|
||||
assertInvalidMessage("Index already exists", "CREATE INDEX " + indexName + " ON %s(b)");
|
||||
assertInvalidMessage(String.format("Index %s already exists",
|
||||
removeQuotes(indexName.toLowerCase(Locale.US))),
|
||||
"CREATE INDEX " + indexName + " ON %s(b)");
|
||||
|
||||
String otherIndexName = "index_" + System.nanoTime();
|
||||
assertInvalidMessage(String.format("Index %s is a duplicate of existing index %s",
|
||||
removeQuotes(otherIndexName.toLowerCase(Locale.US)),
|
||||
removeQuotes(indexName.toLowerCase(Locale.US))),
|
||||
"CREATE INDEX " + otherIndexName + " ON %s(b)");
|
||||
|
||||
execute("INSERT INTO %s (a, b) values (?, ?);", 0, 0);
|
||||
execute("INSERT INTO %s (a, b) values (?, ?);", 1, 1);
|
||||
|
|
@ -84,7 +98,8 @@ public class SecondaryIndexTest extends CQLTester
|
|||
execute("INSERT INTO %s (a, b) values (?, ?);", 3, 1);
|
||||
|
||||
assertRows(execute("SELECT * FROM %s where b = ?", 1), row(1, 1), row(3, 1));
|
||||
assertInvalidMessage("Index '" + removeQuotes(indexName.toLowerCase(Locale.US)) + "' could not be found in any of the tables of keyspace 'system'",
|
||||
assertInvalidMessage(String.format("Index '%s' could not be found in any of the tables of keyspace 'system'",
|
||||
removeQuotes(indexName.toLowerCase(Locale.US))),
|
||||
"DROP INDEX " + indexName);
|
||||
|
||||
if (addKeyspaceOnDrop)
|
||||
|
|
@ -100,7 +115,9 @@ public class SecondaryIndexTest extends CQLTester
|
|||
assertInvalidMessage("No supported secondary index found for the non primary key columns restrictions",
|
||||
"SELECT * FROM %s where b = ?", 1);
|
||||
dropIndex("DROP INDEX IF EXISTS " + indexName);
|
||||
assertInvalidMessage("Index '" + removeQuotes(indexName.toLowerCase(Locale.US)) + "' could not be found", "DROP INDEX " + indexName);
|
||||
assertInvalidMessage(String.format("Index '%s' could not be found",
|
||||
removeQuotes(indexName.toLowerCase(Locale.US))),
|
||||
"DROP INDEX " + indexName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -189,12 +206,10 @@ public class SecondaryIndexTest extends CQLTester
|
|||
public void testUnknownCompressionOptions() throws Throwable
|
||||
{
|
||||
String tableName = createTableName();
|
||||
assertInvalidThrow(SyntaxException.class, String.format(
|
||||
"CREATE TABLE %s (key varchar PRIMARY KEY, password varchar, gender varchar) WITH compression_parameters:sstable_compressor = 'DeflateCompressor'", tableName));
|
||||
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));
|
||||
assertInvalidThrow(ConfigurationException.class, String.format("CREATE TABLE %s (key varchar PRIMARY KEY, password varchar, gender varchar) WITH compression = { 'sstable_compressor': 'DeflateCompressor' }",
|
||||
tableName));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -400,9 +415,6 @@ public class SecondaryIndexTest extends CQLTester
|
|||
assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND m CONTAINS KEY 'a'"), row(0, 0), row(0, 1));
|
||||
assertRows(execute("SELECT k, v FROM %s WHERE m CONTAINS KEY 'c'"), row(0, 2));
|
||||
assertEmpty(execute("SELECT k, v FROM %s WHERE m CONTAINS KEY 'd'"));
|
||||
|
||||
// we're not allowed to create a value index if we already have a key one
|
||||
assertInvalid("CREATE INDEX ON %s(m)");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -412,7 +424,7 @@ public class SecondaryIndexTest extends CQLTester
|
|||
@Test
|
||||
public void testIndexOnKeyWithReverseClustering() throws Throwable
|
||||
{
|
||||
createTable(" CREATE TABLE %s (k1 int, k2 int, v int, PRIMARY KEY ((k1, k2), v) ) WITH CLUSTERING ORDER BY (v DESC)");
|
||||
createTable("CREATE TABLE %s (k1 int, k2 int, v int, PRIMARY KEY ((k1, k2), v) ) WITH CLUSTERING ORDER BY (v DESC)");
|
||||
|
||||
createIndex("CREATE INDEX ON %s (k2)");
|
||||
|
||||
|
|
@ -591,4 +603,51 @@ public class SecondaryIndexTest extends CQLTester
|
|||
assertInvalid("CREATE INDEX ON %s (c)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleIndexesOnOneColumn() throws Throwable
|
||||
{
|
||||
String indexClassName = StubIndex.class.getName();
|
||||
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY ((a), b))");
|
||||
// uses different options otherwise the two indexes are considered duplicates
|
||||
createIndex(String.format("CREATE CUSTOM INDEX c_idx_1 ON %%s(c) USING '%s' WITH OPTIONS = {'foo':'a'}", indexClassName));
|
||||
createIndex(String.format("CREATE CUSTOM INDEX c_idx_2 ON %%s(c) USING '%s' WITH OPTIONS = {'foo':'b'}", indexClassName));
|
||||
|
||||
ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
|
||||
CFMetaData cfm = cfs.metadata;
|
||||
StubIndex index1 = (StubIndex)cfs.indexManager.getIndex(cfm.getIndexes()
|
||||
.get("c_idx_1")
|
||||
.orElseThrow(throwAssert("index not found")));
|
||||
StubIndex index2 = (StubIndex)cfs.indexManager.getIndex(cfm.getIndexes()
|
||||
.get("c_idx_2")
|
||||
.orElseThrow(throwAssert("index not found")));
|
||||
Object[] row1a = row(0, 0, 0);
|
||||
Object[] row1b = row(0, 0, 1);
|
||||
Object[] row2 = row(2, 2, 2);
|
||||
execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", row1a);
|
||||
execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", row1b);
|
||||
execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", row2);
|
||||
|
||||
assertEquals(2, index1.rowsInserted.size());
|
||||
assertColumnValue(0, "c", index1.rowsInserted.get(0), cfm);
|
||||
assertColumnValue(2, "c", index1.rowsInserted.get(1), cfm);
|
||||
|
||||
assertEquals(2, index2.rowsInserted.size());
|
||||
assertColumnValue(0, "c", index2.rowsInserted.get(0), cfm);
|
||||
assertColumnValue(2, "c", index2.rowsInserted.get(1), cfm);
|
||||
|
||||
assertEquals(1, index1.rowsUpdated.size());
|
||||
assertColumnValue(0, "c", index1.rowsUpdated.get(0).left, cfm);
|
||||
assertColumnValue(1, "c", index1.rowsUpdated.get(0).right, cfm);
|
||||
|
||||
assertEquals(1, index2.rowsUpdated.size());
|
||||
assertColumnValue(0, "c", index2.rowsUpdated.get(0).left, cfm);
|
||||
assertColumnValue(1, "c", index2.rowsUpdated.get(0).right, cfm);
|
||||
}
|
||||
|
||||
private static void assertColumnValue(int expected, String name, Row row, CFMetaData cfm)
|
||||
{
|
||||
ColumnDefinition col = cfm.getColumnDefinition(new ColumnIdentifier(name, true));
|
||||
AbstractType<?> type = col.type;
|
||||
assertEquals(expected, type.compose(row.getCell(col).value()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import java.util.List;
|
|||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.CQLTester;
|
||||
|
|
@ -30,8 +32,6 @@ import org.apache.cassandra.db.compaction.CompactionInterruptedException;
|
|||
import org.apache.cassandra.db.compaction.CompactionManager;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
public class CrcCheckChanceTest extends CQLTester
|
||||
{
|
||||
|
|
@ -49,7 +49,7 @@ public class CrcCheckChanceTest extends CQLTester
|
|||
|
||||
|
||||
ColumnFamilyStore cfs = Keyspace.open(CQLTester.KEYSPACE).getColumnFamilyStore(currentTable());
|
||||
ColumnFamilyStore indexCfs = cfs.indexManager.getIndexesBackedByCfs().iterator().next();
|
||||
ColumnFamilyStore indexCfs = cfs.indexManager.getAllIndexColumnFamilyStores().iterator().next();
|
||||
cfs.forceBlockingFlush();
|
||||
|
||||
Assert.assertEquals(0.99, cfs.metadata.params.compression.getCrcCheckChance());
|
||||
|
|
|
|||
|
|
@ -27,17 +27,15 @@ import java.util.List;
|
|||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
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.db.compaction.CompactionManager;
|
||||
import org.apache.cassandra.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
import org.apache.cassandra.dht.ByteOrderedPartitioner.BytesToken;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||
|
|
@ -115,12 +113,14 @@ public class CleanupTest
|
|||
fillCF(cfs, "birthdate", LOOPS);
|
||||
assertEquals(LOOPS, Util.getAll(Util.cmd(cfs).build()).size());
|
||||
|
||||
SecondaryIndex index = cfs.indexManager.getIndexForColumn(cfs.metadata.getColumnDefinition(COLUMN));
|
||||
ColumnDefinition cdef = cfs.metadata.getColumnDefinition(COLUMN);
|
||||
String indexName = cfs.metadata.getIndexes()
|
||||
.get(cdef)
|
||||
.iterator().next().name;
|
||||
long start = System.nanoTime();
|
||||
while (!index.isIndexBuilt(COLUMN) && System.nanoTime() - start < TimeUnit.SECONDS.toNanos(10))
|
||||
while (!cfs.getBuiltIndexes().contains(indexName) && System.nanoTime() - start < TimeUnit.SECONDS.toNanos(10))
|
||||
Thread.sleep(10);
|
||||
|
||||
ColumnDefinition cdef = cfs.metadata.getColumnDefinition(COLUMN);
|
||||
RowFilter cf = RowFilter.create();
|
||||
cf.add(cdef, Operator.EQ, VALUE);
|
||||
assertEquals(LOOPS, Util.getAll(Util.cmd(cfs).filterOn("birthdate", Operator.EQ, VALUE).build()).size());
|
||||
|
|
|
|||
|
|
@ -17,33 +17,39 @@
|
|||
*/
|
||||
package org.apache.cassandra.db;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
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.db.Directories.DataDirectory;
|
||||
import org.apache.cassandra.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.db.marshal.UTF8Type;
|
||||
import org.apache.cassandra.index.internal.CassandraIndex;
|
||||
import org.apache.cassandra.io.FSWriteError;
|
||||
import org.apache.cassandra.io.sstable.Component;
|
||||
import org.apache.cassandra.io.sstable.Descriptor;
|
||||
import org.apache.cassandra.io.util.FileUtils;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.io.FSWriteError;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class DirectoriesTest
|
||||
{
|
||||
|
|
@ -161,12 +167,13 @@ public class DirectoriesTest
|
|||
.addPartitionKey("thekey", UTF8Type.instance)
|
||||
.addClusteringColumn("col", UTF8Type.instance)
|
||||
.build();
|
||||
IndexMetadata indexDef = IndexMetadata.legacyIndex(PARENT_CFM.getColumnDefinition(ByteBufferUtil.bytes("col")),
|
||||
"idx",
|
||||
IndexMetadata.IndexType.KEYS,
|
||||
Collections.emptyMap());
|
||||
ColumnDefinition col = PARENT_CFM.getColumnDefinition(ByteBufferUtil.bytes("col"));
|
||||
IndexMetadata indexDef = IndexMetadata.singleColumnIndex(col,
|
||||
"idx",
|
||||
IndexMetadata.IndexType.KEYS,
|
||||
Collections.emptyMap());
|
||||
PARENT_CFM.indexes(PARENT_CFM.getIndexes().with(indexDef));
|
||||
CFMetaData INDEX_CFM = SecondaryIndex.newIndexMetadata(PARENT_CFM, indexDef);
|
||||
CFMetaData INDEX_CFM = CassandraIndex.indexCfsMetadata(PARENT_CFM, indexDef);
|
||||
Directories parentDirectories = new Directories(PARENT_CFM);
|
||||
Directories indexDirectories = new Directories(INDEX_CFM);
|
||||
// secondary index has its own directory
|
||||
|
|
|
|||
|
|
@ -22,37 +22,33 @@ import java.nio.ByteBuffer;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterators;
|
||||
|
||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.SchemaLoader;
|
||||
import org.apache.cassandra.config.*;
|
||||
import org.apache.cassandra.Util;
|
||||
import org.apache.cassandra.UpdateBuilder;
|
||||
import org.apache.cassandra.Util;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.cql3.statements.IndexTarget;
|
||||
import org.apache.cassandra.db.compaction.CompactionManager;
|
||||
import org.apache.cassandra.db.filter.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.index.PerColumnSecondaryIndex;
|
||||
import org.apache.cassandra.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexSearcher;
|
||||
import org.apache.cassandra.db.marshal.UTF8Type;
|
||||
import org.apache.cassandra.db.filter.ColumnFilter;
|
||||
import org.apache.cassandra.db.marshal.Int32Type;
|
||||
import org.apache.cassandra.db.marshal.UTF8Type;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.index.StubIndex;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||
import org.apache.cassandra.io.sstable.metadata.StatsMetadata;
|
||||
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 org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
|
@ -71,7 +67,7 @@ public class RangeTombstoneTest
|
|||
KeyspaceParams.simple(1),
|
||||
SchemaLoader.standardCFMD(KSNAME,
|
||||
CFNAME,
|
||||
0,
|
||||
1,
|
||||
UTF8Type.instance,
|
||||
Int32Type.instance,
|
||||
Int32Type.instance));
|
||||
|
|
@ -468,22 +464,26 @@ public class RangeTombstoneTest
|
|||
cfs.disableAutoCompaction();
|
||||
|
||||
ColumnDefinition cd = cfs.metadata.getColumnDefinition(indexedColumnName).copy();
|
||||
IndexMetadata indexDef = IndexMetadata.legacyIndex(cd,
|
||||
"test_index",
|
||||
IndexMetadata.IndexType.CUSTOM,
|
||||
ImmutableMap.of(SecondaryIndex.CUSTOM_INDEX_OPTION_NAME,
|
||||
TestIndex.class.getName()));
|
||||
IndexMetadata indexDef = IndexMetadata.singleColumnIndex(cd,
|
||||
"test_index",
|
||||
IndexMetadata.IndexType.CUSTOM,
|
||||
ImmutableMap.of(IndexTarget.CUSTOM_INDEX_OPTION_NAME,
|
||||
StubIndex.class.getName()));
|
||||
|
||||
if (!cfs.metadata.getIndexes().get("test_index").isPresent())
|
||||
cfs.metadata.indexes(cfs.metadata.getIndexes().with(indexDef));
|
||||
|
||||
Future<?> rebuild = cfs.indexManager.addIndexedColumn(indexDef);
|
||||
Future<?> rebuild = cfs.indexManager.addIndex(indexDef);
|
||||
// If rebuild there is, wait for the rebuild to finish so it doesn't race with the following insertions
|
||||
if (rebuild != null)
|
||||
rebuild.get();
|
||||
|
||||
TestIndex index = ((TestIndex)cfs.indexManager.getIndexForColumn(cd));
|
||||
index.resetCounts();
|
||||
StubIndex index = (StubIndex)cfs.indexManager.listIndexes()
|
||||
.stream()
|
||||
.filter(i -> "test_index".equals(i.getIndexName()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException(new AssertionError("Index not found")));
|
||||
index.reset();
|
||||
|
||||
UpdateBuilder builder = UpdateBuilder.create(cfs.metadata, key).withTimestamp(0);
|
||||
for (int i = 0; i < 10; i++)
|
||||
|
|
@ -494,14 +494,14 @@ public class RangeTombstoneTest
|
|||
new RowUpdateBuilder(cfs.metadata, 0, key).addRangeTombstone(0, 7).build().applyUnsafe();
|
||||
cfs.forceBlockingFlush();
|
||||
|
||||
assertEquals(10, index.inserts.size());
|
||||
assertEquals(10, index.rowsInserted.size());
|
||||
|
||||
CompactionManager.instance.performMaximal(cfs, false);
|
||||
|
||||
// compacted down to single sstable
|
||||
assertEquals(1, cfs.getLiveSSTables().size());
|
||||
|
||||
assertEquals(8, index.deletes.size());
|
||||
assertEquals(8, index.rowsDeleted.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -560,21 +560,26 @@ public class RangeTombstoneTest
|
|||
cfs.disableAutoCompaction();
|
||||
|
||||
ColumnDefinition cd = cfs.metadata.getColumnDefinition(indexedColumnName).copy();
|
||||
IndexMetadata indexDef = IndexMetadata.legacyIndex(cd,
|
||||
"test_index",
|
||||
IndexMetadata.IndexType.CUSTOM,
|
||||
ImmutableMap.of(SecondaryIndex.CUSTOM_INDEX_OPTION_NAME,
|
||||
TestIndex.class.getName()));
|
||||
IndexMetadata indexDef = IndexMetadata.singleColumnIndex(cd,
|
||||
"test_index",
|
||||
IndexMetadata.IndexType.CUSTOM,
|
||||
ImmutableMap.of(IndexTarget.CUSTOM_INDEX_OPTION_NAME,
|
||||
StubIndex.class.getName()));
|
||||
|
||||
if (!cfs.metadata.getIndexes().get("test_index").isPresent())
|
||||
cfs.metadata.indexes(cfs.metadata.getIndexes().with(indexDef));
|
||||
|
||||
Future<?> rebuild = cfs.indexManager.addIndexedColumn(indexDef);
|
||||
Future<?> rebuild = cfs.indexManager.addIndex(indexDef);
|
||||
// If rebuild there is, wait for the rebuild to finish so it doesn't race with the following insertions
|
||||
if (rebuild != null)
|
||||
rebuild.get();
|
||||
TestIndex index = ((TestIndex)cfs.indexManager.getIndexForColumn(cd));
|
||||
index.resetCounts();
|
||||
|
||||
StubIndex index = (StubIndex)cfs.indexManager.listIndexes()
|
||||
.stream()
|
||||
.filter(i -> "test_index".equals(i.getIndexName()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException(new AssertionError("Index not found")));
|
||||
index.reset();
|
||||
|
||||
UpdateBuilder.create(cfs.metadata, key).withTimestamp(0).newRow(1).add("val", 1).applyUnsafe();
|
||||
|
||||
|
|
@ -588,8 +593,8 @@ public class RangeTombstoneTest
|
|||
|
||||
// We should have 1 insert and 1 update to the indexed "1" column
|
||||
// CASSANDRA-6640 changed index update to just update, not insert then delete
|
||||
assertEquals(1, index.inserts.size());
|
||||
assertEquals(1, index.updates.size());
|
||||
assertEquals(1, index.rowsInserted.size());
|
||||
assertEquals(1, index.rowsUpdated.size());
|
||||
}
|
||||
|
||||
private static ByteBuffer bb(int i)
|
||||
|
|
@ -601,68 +606,4 @@ public class RangeTombstoneTest
|
|||
{
|
||||
return ByteBufferUtil.toInt(bb);
|
||||
}
|
||||
|
||||
public static class TestIndex extends PerColumnSecondaryIndex
|
||||
{
|
||||
public List<Cell> inserts = new ArrayList<>();
|
||||
public List<Cell> deletes = new ArrayList<>();
|
||||
public List<Cell> updates = new ArrayList<>();
|
||||
|
||||
public void resetCounts()
|
||||
{
|
||||
inserts.clear();
|
||||
deletes.clear();
|
||||
updates.clear();
|
||||
}
|
||||
|
||||
public void delete(ByteBuffer rowKey, Clustering clustering, Cell cell, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
deletes.add(cell);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteForCleanup(ByteBuffer rowKey, Clustering clustering, Cell cell, OpOrder.Group opGroup, int nowInSec) {}
|
||||
|
||||
public void insert(ByteBuffer rowKey, Clustering clustering, Cell cell, OpOrder.Group opGroup)
|
||||
{
|
||||
inserts.add(cell);
|
||||
}
|
||||
|
||||
public void update(ByteBuffer rowKey, Clustering clustering, Cell oldCell, Cell cell, OpOrder.Group opGroup, int nowInSec)
|
||||
{
|
||||
updates.add(cell);
|
||||
}
|
||||
|
||||
public void init(){}
|
||||
|
||||
public void reload(){}
|
||||
|
||||
public void validateOptions(CFMetaData cfm, IndexMetadata def) throws ConfigurationException{}
|
||||
|
||||
public String getIndexName(){ return "TestIndex";}
|
||||
|
||||
protected SecondaryIndexSearcher createSecondaryIndexSearcher(Set<ColumnDefinition> columns) { return null; };
|
||||
|
||||
public void forceBlockingFlush(){}
|
||||
|
||||
public ColumnFamilyStore getIndexCfs(){ return null; }
|
||||
|
||||
public void removeIndex(ByteBuffer columnName){}
|
||||
|
||||
public void invalidate(){}
|
||||
|
||||
public void validate(DecoratedKey key) {}
|
||||
public void validate(ByteBuffer value, CellPath path) {}
|
||||
public void validate(Clustering clustering) {}
|
||||
|
||||
public void truncateBlocking(long truncatedAt) { }
|
||||
|
||||
public boolean indexes(ColumnDefinition name) { return true; }
|
||||
|
||||
@Override
|
||||
public long estimateResultRows()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,7 @@
|
|||
*/
|
||||
package org.apache.cassandra.db;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
|
@ -31,10 +28,7 @@ import org.junit.BeforeClass;
|
|||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.apache.cassandra.OrderedJUnit4ClassRunner;
|
||||
import org.apache.cassandra.SchemaLoader;
|
||||
import org.apache.cassandra.UpdateBuilder;
|
||||
import org.apache.cassandra.Util;
|
||||
import org.apache.cassandra.*;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.Operator;
|
||||
import org.apache.cassandra.cql3.QueryProcessor;
|
||||
|
|
@ -44,7 +38,7 @@ import org.apache.cassandra.db.compaction.OperationType;
|
|||
import org.apache.cassandra.db.compaction.Scrubber;
|
||||
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
|
||||
import org.apache.cassandra.db.lifecycle.TransactionLog;
|
||||
import org.apache.cassandra.db.marshal.*;
|
||||
import org.apache.cassandra.db.marshal.LongType;
|
||||
import org.apache.cassandra.db.marshal.UUIDType;
|
||||
import org.apache.cassandra.db.partitions.Partition;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
|
|
@ -60,7 +54,10 @@ import org.apache.cassandra.io.util.FileUtils;
|
|||
import org.apache.cassandra.schema.KeyspaceParams;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
@RunWith(OrderedJUnit4ClassRunner.class)
|
||||
|
|
@ -607,7 +604,7 @@ public class ScrubTest
|
|||
assertOrdered(Util.cmd(cfs).filterOn(colName, Operator.EQ, 1L).build(), numRows / 2);
|
||||
|
||||
// scrub index
|
||||
Set<ColumnFamilyStore> indexCfss = cfs.indexManager.getIndexesBackedByCfs();
|
||||
Set<ColumnFamilyStore> indexCfss = cfs.indexManager.getAllIndexColumnFamilyStores();
|
||||
assertTrue(indexCfss.size() == 1);
|
||||
for(ColumnFamilyStore indexCfs : indexCfss)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -33,18 +33,20 @@ 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.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexSearcher;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.partitions.*;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.index.Index;
|
||||
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.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class SecondaryIndexTest
|
||||
|
|
@ -111,9 +113,9 @@ public class SecondaryIndexTest
|
|||
.columns("birthdate")
|
||||
.filterOn("birthdate", Operator.EQ, 1L)
|
||||
.build();
|
||||
List<SecondaryIndexSearcher> searchers = cfs.indexManager.getIndexSearchersFor(rc);
|
||||
assertEquals(searchers.size(), 1);
|
||||
try (ReadOrderGroup orderGroup = rc.startOrderGroup(); UnfilteredPartitionIterator pi = searchers.get(0).search(rc, orderGroup))
|
||||
|
||||
Index.Searcher searcher = cfs.indexManager.getBestIndexFor(rc).searcherFor(rc);
|
||||
try (ReadOrderGroup orderGroup = rc.startOrderGroup(); UnfilteredPartitionIterator pi = searcher.search(orderGroup))
|
||||
{
|
||||
assertTrue(pi.hasNext());
|
||||
pi.next().close();
|
||||
|
|
@ -198,7 +200,7 @@ public class SecondaryIndexTest
|
|||
|
||||
// verify that it's not being indexed under any other value either
|
||||
ReadCommand rc = Util.cmd(cfs).build();
|
||||
assertEquals(0, cfs.indexManager.getIndexSearchersFor(rc).size());
|
||||
assertNull(cfs.indexManager.getBestIndexFor(rc));
|
||||
|
||||
// resurrect w/ a newer timestamp
|
||||
new RowUpdateBuilder(cfs.metadata, 2, "k1").clustering("c").add("birthdate", 1L).build().apply();;
|
||||
|
|
@ -213,14 +215,16 @@ public class SecondaryIndexTest
|
|||
assertIndexedOne(cfs, col, 1L);
|
||||
|
||||
// delete the entire row (w/ newer timestamp this time)
|
||||
// todo - checking the # of index searchers for the command is probably not the best thing to test here
|
||||
RowUpdateBuilder.deleteRow(cfs.metadata, 3, "k1", "c").applyUnsafe();
|
||||
rc = Util.cmd(cfs).build();
|
||||
assertEquals(0, cfs.indexManager.getIndexSearchersFor(rc).size());
|
||||
assertNull(cfs.indexManager.getBestIndexFor(rc));
|
||||
|
||||
// make sure obsolete mutations don't generate an index entry
|
||||
// todo - checking the # of index searchers for the command is probably not the best thing to test here
|
||||
new RowUpdateBuilder(cfs.metadata, 3, "k1").clustering("c").add("birthdate", 1L).build().apply();;
|
||||
rc = Util.cmd(cfs).build();
|
||||
assertEquals(0, cfs.indexManager.getIndexSearchersFor(rc).size());
|
||||
assertNull(cfs.indexManager.getBestIndexFor(rc));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -292,7 +296,9 @@ public class SecondaryIndexTest
|
|||
keyspace.getColumnFamilyStore(WITH_KEYS_INDEX).forceBlockingFlush();
|
||||
|
||||
// now apply another update, but force the index update to be skipped
|
||||
keyspace.apply(new RowUpdateBuilder(cfs.metadata, 2, "k1").noRowMarker().add("birthdate", 2L).build(), true, false);
|
||||
keyspace.apply(new RowUpdateBuilder(cfs.metadata, 2, "k1").noRowMarker().add("birthdate", 2L).build(),
|
||||
true,
|
||||
false);
|
||||
|
||||
// Now searching the index for either the old or new value should return 0 rows
|
||||
// because the new value was not indexed and the old value should be ignored
|
||||
|
|
@ -303,7 +309,9 @@ public class SecondaryIndexTest
|
|||
|
||||
// now, reset back to the original value, still skipping the index update, to
|
||||
// make sure the value was expunged from the index when it was discovered to be inconsistent
|
||||
keyspace.apply(new RowUpdateBuilder(cfs.metadata, 3, "k1").noRowMarker().add("birthdate", 1L).build(), true, false);
|
||||
keyspace.apply(new RowUpdateBuilder(cfs.metadata, 3, "k1").noRowMarker().add("birthdate", 1L).build(),
|
||||
true,
|
||||
false);
|
||||
assertIndexedNone(cfs, col, 1L);
|
||||
}
|
||||
|
||||
|
|
@ -328,7 +336,9 @@ public class SecondaryIndexTest
|
|||
assertIndexedOne(cfs, col, 10l);
|
||||
|
||||
// now apply another update, but force the index update to be skipped
|
||||
keyspace.apply(new RowUpdateBuilder(cfs.metadata, 1, "k1").clustering("c").add("birthdate", 20l).build(), true, false);
|
||||
keyspace.apply(new RowUpdateBuilder(cfs.metadata, 1, "k1").clustering("c").add("birthdate", 20l).build(),
|
||||
true,
|
||||
false);
|
||||
|
||||
// Now searching the index for either the old or new value should return 0 rows
|
||||
// because the new value was not indexed and the old value should be ignored
|
||||
|
|
@ -422,34 +432,54 @@ public class SecondaryIndexTest
|
|||
ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(COMPOSITE_INDEX_TO_BE_ADDED);
|
||||
|
||||
// create a row and update the birthdate value, test that the index query fetches the new version
|
||||
DecoratedKey dk = Util.dk("k1");
|
||||
new RowUpdateBuilder(cfs.metadata, 0, "k1").clustering("c").add("birthdate", 1L).build().applyUnsafe();
|
||||
|
||||
ColumnDefinition old = cfs.metadata.getColumnDefinition(ByteBufferUtil.bytes("birthdate"));
|
||||
IndexMetadata indexDef = IndexMetadata.legacyIndex(old,
|
||||
"birthdate_index",
|
||||
IndexMetadata.IndexType.COMPOSITES,
|
||||
Collections.EMPTY_MAP);
|
||||
IndexMetadata indexDef = IndexMetadata.singleColumnIndex(old,
|
||||
"birthdate_index",
|
||||
IndexMetadata.IndexType.COMPOSITES,
|
||||
Collections.EMPTY_MAP);
|
||||
cfs.metadata.indexes(cfs.metadata.getIndexes().with(indexDef));
|
||||
Future<?> future = cfs.indexManager.addIndexedColumn(indexDef);
|
||||
Future<?> future = cfs.indexManager.addIndex(indexDef);
|
||||
future.get();
|
||||
// we had a bug (CASSANDRA-2244) where index would get created but not flushed -- check for that
|
||||
ColumnDefinition cDef = cfs.metadata.getColumnDefinition(ByteBufferUtil.bytes("birthdate"));
|
||||
assert cfs.indexManager.getIndexForColumn(cDef).getIndexCfs().getLiveSSTables().size() > 0;
|
||||
|
||||
// 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 (indexCfs.name.equals(indexName))
|
||||
flushed = indexCfs.getLiveSSTables().size() > 0;
|
||||
}
|
||||
assertTrue(flushed);
|
||||
assertIndexedOne(cfs, ByteBufferUtil.bytes("birthdate"), 1L);
|
||||
|
||||
// validate that drop clears it out & rebuild works (CASSANDRA-2320)
|
||||
SecondaryIndex indexedCfs = cfs.indexManager.getIndexForColumn(cDef);
|
||||
cfs.indexManager.removeIndexedColumn(ByteBufferUtil.bytes("birthdate"));
|
||||
assert !indexedCfs.isIndexBuilt(ByteBufferUtil.bytes("birthdate"));
|
||||
assertTrue(cfs.getBuiltIndexes().contains(indexName));
|
||||
cfs.indexManager.removeIndex(indexDef.name);
|
||||
assertFalse(cfs.getBuiltIndexes().contains(indexName));
|
||||
|
||||
// rebuild & re-query
|
||||
future = cfs.indexManager.addIndexedColumn(indexDef);
|
||||
future = cfs.indexManager.addIndex(indexDef);
|
||||
future.get();
|
||||
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
|
||||
{
|
||||
|
|
@ -460,6 +490,7 @@ public class SecondaryIndexTest
|
|||
for (int i = 0; i < 10; i++)
|
||||
new RowUpdateBuilder(cfs.metadata, 0, "k" + i).noRowMarker().add("birthdate", 1l).build().applyUnsafe();
|
||||
|
||||
assertIndexedCount(cfs, ByteBufferUtil.bytes("birthdate"), 1l, 10);
|
||||
cfs.forceBlockingFlush();
|
||||
assertIndexedCount(cfs, ByteBufferUtil.bytes("birthdate"), 1l, 10);
|
||||
}
|
||||
|
|
@ -477,13 +508,14 @@ public class SecondaryIndexTest
|
|||
ColumnDefinition cdef = cfs.metadata.getColumnDefinition(col);
|
||||
|
||||
ReadCommand rc = Util.cmd(cfs).filterOn(cdef.name.toString(), Operator.EQ, ((AbstractType) cdef.cellValueType()).decompose(val)).build();
|
||||
List<SecondaryIndexSearcher> searchers = cfs.indexManager.getIndexSearchersFor(rc);
|
||||
Index.Searcher searcher = cfs.indexManager.getBestIndexFor(rc).searcherFor(rc);
|
||||
if (count != 0)
|
||||
assertTrue(searchers.size() > 0);
|
||||
assertNotNull(searcher);
|
||||
|
||||
try (ReadOrderGroup orderGroup = rc.startOrderGroup();
|
||||
PartitionIterator iter = UnfilteredPartitionIterators.filter(searchers.get(0).search(rc, orderGroup),
|
||||
FBUtilities.nowInSeconds())) {
|
||||
PartitionIterator iter = UnfilteredPartitionIterators.filter(searcher.search(orderGroup),
|
||||
FBUtilities.nowInSeconds()))
|
||||
{
|
||||
assertEquals(count, Util.size(iter));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,304 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.index;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import org.apache.cassandra.SchemaLoader;
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.config.Schema;
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.QueryProcessor;
|
||||
import org.apache.cassandra.cql3.UntypedResultSet;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.*;
|
||||
import org.apache.cassandra.db.filter.RowFilter;
|
||||
import org.apache.cassandra.db.partitions.SingletonUnfilteredPartitionIterator;
|
||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
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 org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class PerRowSecondaryIndexTest
|
||||
{
|
||||
|
||||
// test that when index(key) is called on a PRSI index,
|
||||
// the data to be indexed can be read using the supplied
|
||||
// key. TestIndex.index(key) simply reads the data to be
|
||||
// indexed & stashes it in a static variable for inspection
|
||||
// in the test.
|
||||
|
||||
private static final String KEYSPACE1 = "PerRowSecondaryIndexTest";
|
||||
private static final String CF_INDEXED = "Indexed1";
|
||||
|
||||
@BeforeClass
|
||||
public static void defineSchema() throws ConfigurationException
|
||||
{
|
||||
SchemaLoader.prepareServer();
|
||||
SchemaLoader.createKeyspace(KEYSPACE1,
|
||||
KeyspaceParams.simple(1),
|
||||
SchemaLoader.perRowIndexedCFMD(KEYSPACE1, CF_INDEXED));
|
||||
}
|
||||
|
||||
@Before
|
||||
public void clearTestStub()
|
||||
{
|
||||
PerRowSecondaryIndexTest.TestIndex.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexInsertAndUpdate()
|
||||
{
|
||||
int nowInSec = FBUtilities.nowInSeconds();
|
||||
|
||||
// create a row then test that the configured index instance was able to read the row
|
||||
CFMetaData cfm = Schema.instance.getCFMetaData(KEYSPACE1, CF_INDEXED);
|
||||
ColumnDefinition cdef = cfm.getColumnDefinition(new ColumnIdentifier("indexed", true));
|
||||
|
||||
RowUpdateBuilder builder = new RowUpdateBuilder(cfm, FBUtilities.timestampMicros(), "k1");
|
||||
builder.add("indexed", ByteBufferUtil.bytes("foo"));
|
||||
builder.build().apply();
|
||||
|
||||
|
||||
UnfilteredRowIterator indexedRow = PerRowSecondaryIndexTest.TestIndex.LAST_INDEXED_PARTITION;
|
||||
assertNotNull(indexedRow);
|
||||
assertEquals(ByteBufferUtil.bytes("foo"), UnfilteredRowIterators.filter(indexedRow, nowInSec).next().getCell(cdef).value());
|
||||
|
||||
// update the row and verify what was indexed
|
||||
builder = new RowUpdateBuilder(cfm, FBUtilities.timestampMicros() + 1 , "k1");
|
||||
builder.add("indexed", ByteBufferUtil.bytes("bar"));
|
||||
builder.build().apply();
|
||||
|
||||
indexedRow = PerRowSecondaryIndexTest.TestIndex.LAST_INDEXED_PARTITION;
|
||||
assertNotNull(indexedRow);
|
||||
assertEquals(ByteBufferUtil.bytes("bar"), UnfilteredRowIterators.filter(indexedRow, nowInSec).next().getCell(cdef).value());
|
||||
assertTrue(Arrays.equals("k1".getBytes(), PerRowSecondaryIndexTest.TestIndex.LAST_INDEXED_KEY.array()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testColumnDelete()
|
||||
{
|
||||
// issue a column delete and test that the configured index instance was notified to update
|
||||
CFMetaData cfm = Schema.instance.getCFMetaData(KEYSPACE1, CF_INDEXED);
|
||||
|
||||
new RowUpdateBuilder(cfm, FBUtilities.timestampMicros(), "k2")
|
||||
.noRowMarker()
|
||||
.delete("indexed")
|
||||
.build()
|
||||
.apply();
|
||||
|
||||
UnfilteredRowIterator indexedRow = PerRowSecondaryIndexTest.TestIndex.LAST_INDEXED_PARTITION;
|
||||
assertNotNull(indexedRow);
|
||||
|
||||
//We filter tombstones now...
|
||||
Assert.assertFalse(UnfilteredRowIterators.filter(indexedRow, FBUtilities.nowInSeconds()).hasNext());
|
||||
assertTrue(Arrays.equals("k2".getBytes(), PerRowSecondaryIndexTest.TestIndex.LAST_INDEXED_KEY.array()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRowDelete()
|
||||
{
|
||||
// issue a row level delete and test that the configured index instance was notified to update
|
||||
CFMetaData cfm = Schema.instance.getCFMetaData(KEYSPACE1, CF_INDEXED);
|
||||
RowUpdateBuilder.deleteRow(cfm, FBUtilities.timestampMicros(), "k3").apply();
|
||||
|
||||
UnfilteredRowIterator indexedRow = PerRowSecondaryIndexTest.TestIndex.LAST_INDEXED_PARTITION;
|
||||
assertNotNull(indexedRow);
|
||||
assertNotNull(indexedRow.partitionLevelDeletion());
|
||||
Assert.assertFalse(UnfilteredRowIterators.filter(indexedRow, FBUtilities.nowInSeconds()).hasNext());
|
||||
assertTrue(Arrays.equals("k3".getBytes(), PerRowSecondaryIndexTest.TestIndex.LAST_INDEXED_KEY.array()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidSearch()
|
||||
{
|
||||
|
||||
CFMetaData cfm = Schema.instance.getCFMetaData(KEYSPACE1, CF_INDEXED);
|
||||
|
||||
RowUpdateBuilder builder = new RowUpdateBuilder(cfm, FBUtilities.timestampMicros(), "k1");
|
||||
builder.add("indexed", ByteBufferUtil.bytes("foo"));
|
||||
builder.build().apply();
|
||||
|
||||
|
||||
// test we can search:
|
||||
UntypedResultSet result = QueryProcessor.executeInternal(String.format("SELECT * FROM \"%s\".\"Indexed1\" WHERE indexed = 'foo'", KEYSPACE1));
|
||||
assertEquals(1, result.size());
|
||||
|
||||
// test we can't search if the searcher doesn't validate the expression:
|
||||
try
|
||||
{
|
||||
QueryProcessor.executeInternal(String.format("SELECT * FROM \"%s\".\"Indexed1\" WHERE indexed = 'invalid'", KEYSPACE1));
|
||||
fail("Query should have been invalid!");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
assertTrue(e instanceof InvalidRequestException || (e.getCause() != null && (e.getCause() instanceof InvalidRequestException)));
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestIndex extends PerRowSecondaryIndex
|
||||
{
|
||||
public static UnfilteredRowIterator LAST_INDEXED_PARTITION;
|
||||
public static ByteBuffer LAST_INDEXED_KEY;
|
||||
|
||||
public static void reset()
|
||||
{
|
||||
LAST_INDEXED_KEY = null;
|
||||
LAST_INDEXED_PARTITION = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void index(ByteBuffer rowKey, UnfilteredRowIterator cf)
|
||||
{
|
||||
LAST_INDEXED_PARTITION = cf;
|
||||
LAST_INDEXED_KEY = rowKey;
|
||||
}
|
||||
|
||||
public void index(ByteBuffer rowKey, PartitionUpdate atoms)
|
||||
{
|
||||
LAST_INDEXED_PARTITION = atoms.unfilteredIterator();
|
||||
LAST_INDEXED_KEY = rowKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(ByteBuffer key, OpOrder.Group opGroup)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateOptions(CFMetaData cfm, IndexMetadata def) throws ConfigurationException{}
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIndexName()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SecondaryIndexSearcher createSecondaryIndexSearcher(Set<ColumnDefinition> columns)
|
||||
{
|
||||
return new SecondaryIndexSearcher(baseCfs.indexManager, columns)
|
||||
{
|
||||
@Override
|
||||
public UnfilteredPartitionIterator search(ReadCommand filter, ReadOrderGroup orderGroup)
|
||||
{
|
||||
return new SingletonUnfilteredPartitionIterator(LAST_INDEXED_PARTITION, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RowFilter.Expression primaryClause(ReadCommand command)
|
||||
{
|
||||
RowFilter.Expression expression = command.rowFilter().iterator().next();
|
||||
|
||||
if (expression.getIndexValue().equals(ByteBufferUtil.bytes("invalid")))
|
||||
throw new InvalidRequestException("Invalid search!");
|
||||
|
||||
return expression;
|
||||
}
|
||||
|
||||
protected UnfilteredPartitionIterator queryDataFromIndex(AbstractSimplePerColumnSecondaryIndex index, DecoratedKey indexKey, RowIterator indexHits, ReadCommand command, ReadOrderGroup orderGroup)
|
||||
{
|
||||
// As we override 'search()' directly for the test, we don't care about this.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceBlockingFlush()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnFamilyStore getIndexCfs()
|
||||
{
|
||||
return baseCfs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean indexes(ColumnDefinition name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(DecoratedKey partitionKey) throws InvalidRequestException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Clustering clustering) throws InvalidRequestException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ByteBuffer cellValue, CellPath path) throws InvalidRequestException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeIndex(ByteBuffer columnName)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void truncateBlocking(long truncatedAt)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public long estimateResultRows() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.index;
|
||||
|
||||
import java.util.*;
|
||||
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;
|
||||
import org.apache.cassandra.db.partitions.PartitionIterator;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.index.transactions.IndexTransaction;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
|
||||
public class StubIndex implements Index
|
||||
{
|
||||
public List<Row> rowsInserted = new ArrayList<>();
|
||||
public List<Row> rowsDeleted = new ArrayList<>();
|
||||
public List<Pair<Row,Row>> rowsUpdated = new ArrayList<>();
|
||||
private IndexMetadata indexMetadata;
|
||||
private ColumnFamilyStore baseCfs;
|
||||
|
||||
public void reset()
|
||||
{
|
||||
rowsInserted.clear();
|
||||
rowsDeleted.clear();
|
||||
}
|
||||
|
||||
public StubIndex(ColumnFamilyStore baseCfs, IndexMetadata metadata)
|
||||
{
|
||||
this.baseCfs = baseCfs;
|
||||
this.indexMetadata = metadata;
|
||||
}
|
||||
|
||||
public boolean indexes(PartitionColumns columns)
|
||||
{
|
||||
for (ColumnDefinition col : columns)
|
||||
for (ColumnIdentifier indexed : indexMetadata.columns)
|
||||
if (indexed.equals(col.name))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean supportsExpression(ColumnDefinition column, Operator operator)
|
||||
{
|
||||
return operator == Operator.EQ;
|
||||
}
|
||||
|
||||
public RowFilter getPostIndexQueryFilter(RowFilter filter)
|
||||
{
|
||||
return filter;
|
||||
}
|
||||
|
||||
public Indexer indexerFor(DecoratedKey key,
|
||||
int nowInSec,
|
||||
OpOrder.Group opGroup,
|
||||
IndexTransaction.Type transactionType)
|
||||
{
|
||||
return new Indexer()
|
||||
{
|
||||
public void begin()
|
||||
{
|
||||
}
|
||||
|
||||
public void partitionDelete(DeletionTime deletionTime)
|
||||
{
|
||||
}
|
||||
|
||||
public void rangeTombstone(RangeTombstone tombstone)
|
||||
{
|
||||
}
|
||||
|
||||
public void insertRow(Row row)
|
||||
{
|
||||
rowsInserted.add(row);
|
||||
}
|
||||
|
||||
public void removeRow(Row row)
|
||||
{
|
||||
rowsDeleted.add(row);
|
||||
}
|
||||
|
||||
public void updateRow(Row oldRowData, Row newRowData)
|
||||
{
|
||||
rowsUpdated.add(Pair.create(oldRowData, newRowData));
|
||||
}
|
||||
|
||||
public void finish()
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Callable<?> getInitializationTask()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public IndexMetadata getIndexMetadata()
|
||||
{
|
||||
return indexMetadata;
|
||||
}
|
||||
|
||||
public String getIndexName()
|
||||
{
|
||||
return indexMetadata != null ? indexMetadata.name : "default_test_index_name";
|
||||
}
|
||||
|
||||
public void register(IndexRegistry registry){
|
||||
registry.registerIndex(this);
|
||||
}
|
||||
|
||||
public Optional<ColumnFamilyStore> getBackingTable()
|
||||
{
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Collection<ColumnDefinition> getIndexedColumns()
|
||||
{
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
public Callable<?> getBlockingFlushTask()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public Callable<?> getTruncateTask(long truncatedAt)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public Callable<?> getInvalidateTask()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public Callable<?> getMetadataReloadTask(IndexMetadata indexMetadata)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public long getEstimatedResultRows()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void validate(PartitionUpdate update) throws InvalidRequestException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Searcher searcherFor(ReadCommand command)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public BiFunction<PartitionIterator, ReadCommand, PartitionIterator> postProcessorFor(ReadCommand readCommand)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,718 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.index.internal;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.*;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.cql3.CQLTester;
|
||||
import org.apache.cassandra.cql3.UntypedResultSet;
|
||||
import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
|
||||
import org.apache.cassandra.cql3.statements.SelectStatement;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.filter.ClusteringIndexFilter;
|
||||
import org.apache.cassandra.db.filter.ClusteringIndexSliceFilter;
|
||||
import org.apache.cassandra.db.filter.ColumnFilter;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.db.rows.Unfiltered;
|
||||
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
import static org.apache.cassandra.Util.throwAssert;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* Smoke tests of built-in secondary index implementations
|
||||
*/
|
||||
public class CassandraIndexTest extends CQLTester
|
||||
{
|
||||
|
||||
@Test
|
||||
public void indexOnRegularColumn() throws Throwable
|
||||
{
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, c int, v int, PRIMARY KEY (k, c));")
|
||||
.target("v")
|
||||
.indexName("v_index")
|
||||
.withFirstRow(row(0, 0, 0))
|
||||
.withSecondRow(row(1, 1, 1))
|
||||
.missingIndexMessage(StatementRestrictions.NO_INDEX_FOUND_MESSAGE)
|
||||
.firstQueryExpression("v=0")
|
||||
.secondQueryExpression("v=1")
|
||||
.updateExpression("SET v=2")
|
||||
.postUpdateQueryExpression("v=2")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnFirstClusteringColumn() throws Throwable
|
||||
{
|
||||
// No update allowed on primary key columns, so this script has no update expression
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, c int, v int, PRIMARY KEY (k, c));")
|
||||
.target("c")
|
||||
.indexName("c_index")
|
||||
.withFirstRow(row(0, 0, 0))
|
||||
.withSecondRow(row(1, 1, 1))
|
||||
.missingIndexMessage(SelectStatement.REQUIRES_ALLOW_FILTERING_MESSAGE)
|
||||
.firstQueryExpression("c=0")
|
||||
.secondQueryExpression("c=1")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnSecondClusteringColumn() throws Throwable
|
||||
{
|
||||
// No update allowed on primary key columns, so this script has no update expression
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, c1 int, c2 int, v int, PRIMARY KEY (k, c1, c2));")
|
||||
.target("c2")
|
||||
.indexName("c2_index")
|
||||
.withFirstRow(row(0, 0, 0, 0))
|
||||
.withSecondRow(row(1, 1, 1, 1))
|
||||
.missingIndexMessage(String.format("PRIMARY KEY column \"%s\" cannot be restricted " +
|
||||
"as preceding column \"%s\" is not restricted",
|
||||
"c2", "c1"))
|
||||
.firstQueryExpression("c2=0")
|
||||
.secondQueryExpression("c2=1")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnFirstPartitionKeyColumn() throws Throwable
|
||||
{
|
||||
// No update allowed on primary key columns, so this script has no update expression
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k1 int, k2 int, c1 int, c2 int, v int, PRIMARY KEY ((k1, k2), c1, c2));")
|
||||
.target("k1")
|
||||
.indexName("k1_index")
|
||||
.withFirstRow(row(0, 0, 0, 0, 0))
|
||||
.withSecondRow(row(1, 1, 1, 1, 1))
|
||||
.missingIndexMessage("Partition key parts: k2 must be restricted as other parts are")
|
||||
.firstQueryExpression("k1=0")
|
||||
.secondQueryExpression("k1=1")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnSecondPartitionKeyColumn() throws Throwable
|
||||
{
|
||||
// No update allowed on primary key columns, so this script has no update expression
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k1 int, k2 int, c1 int, c2 int, v int, PRIMARY KEY ((k1, k2), c1, c2));")
|
||||
.target("k2")
|
||||
.indexName("k2_index")
|
||||
.withFirstRow(row(0, 0, 0, 0, 0))
|
||||
.withSecondRow(row(1, 1, 1, 1, 1))
|
||||
.missingIndexMessage("Partition key parts: k1 must be restricted as other parts are")
|
||||
.firstQueryExpression("k2=0")
|
||||
.secondQueryExpression("k2=1")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnNonFrozenListWithReplaceOperation() throws Throwable
|
||||
{
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, c int, l list<int>, PRIMARY KEY (k, c));")
|
||||
.target("l")
|
||||
.indexName("l_index1")
|
||||
.withFirstRow(row(0, 0, Lists.newArrayList(10, 20, 30)))
|
||||
.withSecondRow(row(1, 1, Lists.newArrayList(11, 21, 31)))
|
||||
.missingIndexMessage(StatementRestrictions.NO_INDEX_FOUND_MESSAGE)
|
||||
.firstQueryExpression("l CONTAINS 10")
|
||||
.secondQueryExpression("l CONTAINS 11")
|
||||
.updateExpression("SET l = [40, 50, 60]")
|
||||
.postUpdateQueryExpression("l CONTAINS 40")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnNonFrozenListWithInPlaceOperation() throws Throwable
|
||||
{
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, c int, l list<int>, PRIMARY KEY (k, c));")
|
||||
.target("l")
|
||||
.indexName("l_index2")
|
||||
.withFirstRow(row(0, 0, Lists.newArrayList(10, 20, 30)))
|
||||
.withSecondRow(row(1, 1, Lists.newArrayList(11, 21, 31)))
|
||||
.missingIndexMessage(StatementRestrictions.NO_INDEX_FOUND_MESSAGE)
|
||||
.firstQueryExpression("l CONTAINS 10")
|
||||
.secondQueryExpression("l CONTAINS 11")
|
||||
.updateExpression("SET l = l - [10]")
|
||||
.postUpdateQueryExpression("l CONTAINS 20")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnNonFrozenSetWithReplaceOperation() throws Throwable
|
||||
{
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, c int, s set<int>, PRIMARY KEY (k, c));")
|
||||
.target("s")
|
||||
.indexName("s_index1")
|
||||
.withFirstRow(row(0, 0, Sets.newHashSet(10, 20, 30)))
|
||||
.withSecondRow(row(1, 1, Sets.newHashSet(11, 21, 31)))
|
||||
.missingIndexMessage(StatementRestrictions.NO_INDEX_FOUND_MESSAGE)
|
||||
.firstQueryExpression("s CONTAINS 10")
|
||||
.secondQueryExpression("s CONTAINS 11")
|
||||
.updateExpression("SET s = {40, 50, 60}")
|
||||
.postUpdateQueryExpression("s CONTAINS 40")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnNonFrozenSetWithInPlaceOperation() throws Throwable
|
||||
{
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, c int, s set<int>, PRIMARY KEY (k, c));")
|
||||
.target("s")
|
||||
.indexName("s_index2")
|
||||
.withFirstRow(row(0, 0, Sets.newHashSet(10, 20, 30)))
|
||||
.withSecondRow(row(1, 1, Sets.newHashSet(11, 21, 31)))
|
||||
.missingIndexMessage(StatementRestrictions.NO_INDEX_FOUND_MESSAGE)
|
||||
.firstQueryExpression("s CONTAINS 10")
|
||||
.secondQueryExpression("s CONTAINS 11")
|
||||
.updateExpression("SET s = s - {10}")
|
||||
.postUpdateQueryExpression("s CONTAINS 20")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnNonFrozenMapValuesWithReplaceOperation() throws Throwable
|
||||
{
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, c int, m map<text,int>, PRIMARY KEY (k, c));")
|
||||
.target("m")
|
||||
.indexName("m_index1")
|
||||
.withFirstRow(row(0, 0, ImmutableMap.of("a", 10, "b", 20, "c", 30)))
|
||||
.withSecondRow(row(1, 1, ImmutableMap.of("d", 11, "e", 21, "f", 31)))
|
||||
.missingIndexMessage(StatementRestrictions.NO_INDEX_FOUND_MESSAGE)
|
||||
.firstQueryExpression("m CONTAINS 10")
|
||||
.secondQueryExpression("m CONTAINS 11")
|
||||
.updateExpression("SET m = {'x':40, 'y':50, 'z':60}")
|
||||
.postUpdateQueryExpression("m CONTAINS 40")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnNonFrozenMapValuesWithInPlaceOperation() throws Throwable
|
||||
{
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, c int, m map<text,int>, PRIMARY KEY (k, c));")
|
||||
.target("m")
|
||||
.indexName("m_index2")
|
||||
.withFirstRow(row(0, 0, ImmutableMap.of("a", 10, "b", 20, "c", 30)))
|
||||
.withSecondRow(row(1, 1, ImmutableMap.of("d", 11, "e", 21, "f", 31)))
|
||||
.missingIndexMessage(StatementRestrictions.NO_INDEX_FOUND_MESSAGE)
|
||||
.firstQueryExpression("m CONTAINS 10")
|
||||
.secondQueryExpression("m CONTAINS 11")
|
||||
.updateExpression("SET m['a'] = 40")
|
||||
.postUpdateQueryExpression("m CONTAINS 40")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnNonFrozenMapKeysWithReplaceOperation() throws Throwable
|
||||
{
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, c int, m map<text,int>, PRIMARY KEY (k, c));")
|
||||
.target("keys(m)")
|
||||
.indexName("m_index3")
|
||||
.withFirstRow(row(0, 0, ImmutableMap.of("a", 10, "b", 20, "c", 30)))
|
||||
.withSecondRow(row(1, 1, ImmutableMap.of("d", 11, "e", 21, "f", 31)))
|
||||
.missingIndexMessage(StatementRestrictions.NO_INDEX_FOUND_MESSAGE)
|
||||
.firstQueryExpression("m CONTAINS KEY 'a'")
|
||||
.secondQueryExpression("m CONTAINS KEY 'd'")
|
||||
.updateExpression("SET m = {'x':40, 'y':50, 'z':60}")
|
||||
.postUpdateQueryExpression("m CONTAINS KEY 'x'")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnNonFrozenMapKeysWithInPlaceOperation() throws Throwable
|
||||
{
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, c int, m map<text,int>, PRIMARY KEY (k, c));")
|
||||
.target("keys(m)")
|
||||
.indexName("m_index4")
|
||||
.withFirstRow(row(0, 0, ImmutableMap.of("a", 10, "b", 20, "c", 30)))
|
||||
.withSecondRow(row(1, 1, ImmutableMap.of("d", 11, "e", 21, "f", 31)))
|
||||
.missingIndexMessage(StatementRestrictions.NO_INDEX_FOUND_MESSAGE)
|
||||
.firstQueryExpression("m CONTAINS KEY 'a'")
|
||||
.secondQueryExpression("m CONTAINS KEY 'd'")
|
||||
.updateExpression("SET m['a'] = NULL")
|
||||
.postUpdateQueryExpression("m CONTAINS KEY 'b'")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnNonFrozenMapEntriesWithReplaceOperation() throws Throwable
|
||||
{
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, c int, m map<text,int>, PRIMARY KEY (k, c));")
|
||||
.target("entries(m)")
|
||||
.indexName("m_index5")
|
||||
.withFirstRow(row(0, 0, ImmutableMap.of("a", 10, "b", 20, "c", 30)))
|
||||
.withSecondRow(row(1, 1, ImmutableMap.of("d", 11, "e", 21, "f", 31)))
|
||||
.missingIndexMessage(StatementRestrictions.NO_INDEX_FOUND_MESSAGE)
|
||||
.firstQueryExpression("m['a'] = 10")
|
||||
.secondQueryExpression("m['d'] = 11")
|
||||
.updateExpression("SET m = {'x':40, 'y':50, 'z':60}")
|
||||
.postUpdateQueryExpression("m['x'] = 40")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnNonFrozenMapEntriesWithInPlaceOperation() throws Throwable
|
||||
{
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, c int, m map<text,int>, PRIMARY KEY (k, c));")
|
||||
.target("entries(m)")
|
||||
.indexName("m_index6")
|
||||
.withFirstRow(row(0, 0, ImmutableMap.of("a", 10, "b", 20, "c", 30)))
|
||||
.withSecondRow(row(1, 1, ImmutableMap.of("d", 11, "e", 21, "f", 31)))
|
||||
.missingIndexMessage(StatementRestrictions.NO_INDEX_FOUND_MESSAGE)
|
||||
.firstQueryExpression("m['a'] = 10")
|
||||
.secondQueryExpression("m['d'] = 11")
|
||||
.updateExpression("SET m['a'] = 40")
|
||||
.postUpdateQueryExpression("m['a'] = 40")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnFrozenList() throws Throwable
|
||||
{
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, c int, l frozen<list<int>>, PRIMARY KEY (k, c));")
|
||||
.target("full(l)")
|
||||
.indexName("fl_index")
|
||||
.withFirstRow(row(0, 0, Lists.newArrayList(10, 20, 30)))
|
||||
.withSecondRow(row(1, 1, Lists.newArrayList(11, 21, 31)))
|
||||
.missingIndexMessage(StatementRestrictions.NO_INDEX_FOUND_MESSAGE)
|
||||
.firstQueryExpression("l = [10, 20, 30]")
|
||||
.secondQueryExpression("l = [11, 21, 31]")
|
||||
.updateExpression("SET l = [40, 50, 60]")
|
||||
.postUpdateQueryExpression("l = [40, 50, 60]")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnFrozenSet() throws Throwable
|
||||
{
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, c int, s frozen<set<int>>, PRIMARY KEY (k, c));")
|
||||
.target("full(s)")
|
||||
.indexName("fs_index")
|
||||
.withFirstRow(row(0, 0, Sets.newHashSet(10, 20, 30)))
|
||||
.withSecondRow(row(1, 1, Sets.newHashSet(11, 21, 31)))
|
||||
.missingIndexMessage(StatementRestrictions.NO_INDEX_FOUND_MESSAGE)
|
||||
.firstQueryExpression("s = {10, 20, 30}")
|
||||
.secondQueryExpression("s = {11, 21, 31}")
|
||||
.updateExpression("SET s = {40, 50, 60}")
|
||||
.postUpdateQueryExpression("s = {40, 50, 60}")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnFrozenMap() throws Throwable
|
||||
{
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, c int, m frozen<map<text,int>>, PRIMARY KEY (k, c));")
|
||||
.target("full(m)")
|
||||
.indexName("fm_index")
|
||||
.withFirstRow(row(0, 0, ImmutableMap.of("a", 10, "b", 20, "c", 30)))
|
||||
.withSecondRow(row(1, 1, ImmutableMap.of("d", 11, "e", 21, "f", 31)))
|
||||
.missingIndexMessage(StatementRestrictions.NO_INDEX_FOUND_MESSAGE)
|
||||
.firstQueryExpression("m = {'a':10, 'b':20, 'c':30}")
|
||||
.secondQueryExpression("m = {'d':11, 'e':21, 'f':31}")
|
||||
.updateExpression("SET m = {'x':40, 'y':50, 'z':60}")
|
||||
.postUpdateQueryExpression("m = {'x':40, 'y':50, 'z':60}")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOnRegularColumnWithCompactStorage() throws Throwable
|
||||
{
|
||||
new TestScript().tableDefinition("CREATE TABLE %s (k int, v int, PRIMARY KEY (k)) WITH COMPACT STORAGE;")
|
||||
.target("v")
|
||||
.indexName("cv_index")
|
||||
.withFirstRow(row(0, 0))
|
||||
.withSecondRow(row(1,1))
|
||||
.missingIndexMessage(StatementRestrictions.NO_INDEX_FOUND_MESSAGE)
|
||||
.firstQueryExpression("v=0")
|
||||
.secondQueryExpression("v=1")
|
||||
.updateExpression("SET v=2")
|
||||
.postUpdateQueryExpression("v=2")
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createIndexesOnMultipleMapDimensions() throws Throwable
|
||||
{
|
||||
Object[] row1 = row(0, 0, ImmutableMap.of("a", 10, "b", 20, "c", 30));
|
||||
Object[] row2 = row(1, 1, ImmutableMap.of("d", 11, "e", 21, "f", 32));
|
||||
createTable("CREATE TABLE %s (k int, c int, m map<text, int>, PRIMARY KEY(k, c))");
|
||||
createIndex("CREATE INDEX mkey_index on %s(keys(m))");
|
||||
createIndex("CREATE INDEX mval_index on %s(m)");
|
||||
|
||||
execute("INSERT INTO %s (k, c, m) VALUES (?, ?, ?)", row1);
|
||||
execute("INSERT INTO %s (k, c, m) VALUES (?, ?, ?)", row2);
|
||||
|
||||
assertRows(execute("SELECT * FROM %s WHERE m CONTAINS KEY 'a'"), row1);
|
||||
assertRows(execute("SELECT * FROM %s WHERE m CONTAINS 20"), row1);
|
||||
assertRows(execute("SELECT * FROM %s WHERE m CONTAINS KEY 'f'"), row2);
|
||||
assertRows(execute("SELECT * FROM %s WHERE m CONTAINS 32"), row2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void insertWithTombstoneRemovesEntryFromIndex() throws Throwable
|
||||
{
|
||||
int key = 0;
|
||||
int indexedValue = 99;
|
||||
createTable("CREATE TABLE %s (k int, v int, PRIMARY KEY(k))");
|
||||
createIndex("CREATE INDEX v_index on %s(v)");
|
||||
execute("INSERT INTO %s (k, v) VALUES (?, ?)", key, indexedValue);
|
||||
|
||||
assertRows(execute("SELECT * FROM %s WHERE v = ?", indexedValue), row(key, indexedValue));
|
||||
execute("DELETE v FROM %s WHERE k=?", key);
|
||||
assertEmpty(execute("SELECT * FROM %s WHERE v = ?", indexedValue));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateTTLOnIndexedClusteringValue() throws Throwable
|
||||
{
|
||||
int basePk = 1;
|
||||
int indexedVal = 2;
|
||||
int initialTtl = 3600;
|
||||
createTable("CREATE TABLE %s (k int, c int, v int, PRIMARY KEY(k,c))");
|
||||
createIndex("CREATE INDEX c_index on %s(c)");
|
||||
execute("INSERT INTO %s (k, c, v) VALUES (?, ?, 0) USING TTL ?", basePk, indexedVal, initialTtl);
|
||||
ColumnFamilyStore baseCfs = getCurrentColumnFamilyStore();
|
||||
ColumnFamilyStore indexCfs = baseCfs.indexManager.listIndexes()
|
||||
.iterator()
|
||||
.next()
|
||||
.getBackingTable()
|
||||
.orElseThrow(throwAssert("No index found"));
|
||||
assertIndexRowTtl(indexCfs, indexedVal, initialTtl);
|
||||
|
||||
int updatedTtl = 9999;
|
||||
execute("INSERT INTO %s (k, c ,v) VALUES (?, ?, 0) USING TTL ?", basePk, indexedVal, updatedTtl);
|
||||
|
||||
assertIndexRowTtl(indexCfs, indexedVal, updatedTtl);
|
||||
}
|
||||
|
||||
// this is slightly annoying, but we cannot read rows from the methods in Util as
|
||||
// ReadCommand#executeInternal uses metadata retrieved via the cfId, which the index
|
||||
// CFS inherits from the base CFS. This has the 'wrong' partitioner (the index table
|
||||
// uses LocalPartition, the base table a real one, so we cannot read from the index
|
||||
// table with executeInternal
|
||||
private void assertIndexRowTtl(ColumnFamilyStore indexCfs, int indexedValue, int ttl) throws Throwable
|
||||
{
|
||||
DecoratedKey indexKey = indexCfs.decorateKey(ByteBufferUtil.bytes(indexedValue));
|
||||
ClusteringIndexFilter filter = new ClusteringIndexSliceFilter(Slices.with(indexCfs.metadata.comparator,
|
||||
Slice.ALL),
|
||||
false);
|
||||
SinglePartitionReadCommand command = SinglePartitionReadCommand.create(indexCfs.metadata,
|
||||
FBUtilities.nowInSeconds(),
|
||||
indexKey,
|
||||
ColumnFilter.all(indexCfs.metadata),
|
||||
filter);
|
||||
try (ReadOrderGroup orderGroup = ReadOrderGroup.forCommand(command);
|
||||
UnfilteredRowIterator iter = command.queryMemtableAndDisk(indexCfs, orderGroup.indexReadOpOrderGroup()))
|
||||
{
|
||||
while( iter.hasNext())
|
||||
{
|
||||
Unfiltered unfiltered = iter.next();
|
||||
assert (unfiltered.isRow());
|
||||
Row indexRow = (Row) unfiltered;
|
||||
assertEquals(ttl, indexRow.primaryKeyLivenessInfo().ttl());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TestScript
|
||||
{
|
||||
String tableDefinition;
|
||||
String indexName;
|
||||
String indexTarget;
|
||||
String queryExpression1;
|
||||
String queryExpression2;
|
||||
String updateExpression;
|
||||
String postUpdateQueryExpression;
|
||||
String missingIndexMessage;
|
||||
|
||||
Object[] firstRow;
|
||||
Object[] secondRow;
|
||||
|
||||
TestScript indexName(String indexName)
|
||||
{
|
||||
this.indexName = indexName;
|
||||
return this;
|
||||
}
|
||||
|
||||
TestScript target(String indexTarget)
|
||||
{
|
||||
this.indexTarget = indexTarget;
|
||||
return this;
|
||||
}
|
||||
|
||||
TestScript tableDefinition(String tableDefinition)
|
||||
{
|
||||
this.tableDefinition = tableDefinition;
|
||||
return this;
|
||||
}
|
||||
|
||||
TestScript withFirstRow(Object[] row)
|
||||
{
|
||||
this.firstRow = row;
|
||||
return this;
|
||||
}
|
||||
|
||||
TestScript withSecondRow(Object[] row)
|
||||
{
|
||||
this.secondRow = row;
|
||||
return this;
|
||||
}
|
||||
|
||||
TestScript firstQueryExpression(String queryExpression)
|
||||
{
|
||||
queryExpression1 = queryExpression;
|
||||
return this;
|
||||
}
|
||||
|
||||
TestScript secondQueryExpression(String queryExpression)
|
||||
{
|
||||
queryExpression2 = queryExpression;
|
||||
return this;
|
||||
}
|
||||
|
||||
TestScript updateExpression(String updateExpression)
|
||||
{
|
||||
this.updateExpression = updateExpression;
|
||||
return this;
|
||||
}
|
||||
|
||||
TestScript postUpdateQueryExpression(String queryExpression)
|
||||
{
|
||||
this.postUpdateQueryExpression = queryExpression;
|
||||
return this;
|
||||
}
|
||||
|
||||
TestScript missingIndexMessage(String missingIndexMessage)
|
||||
{
|
||||
this.missingIndexMessage = missingIndexMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
void run() throws Throwable
|
||||
{
|
||||
// check minimum required setup
|
||||
assertNotNull(indexName);
|
||||
assertNotNull(indexTarget);
|
||||
assertNotNull(queryExpression1);
|
||||
assertNotNull(queryExpression2);
|
||||
assertNotNull(firstRow);
|
||||
assertNotNull(secondRow);
|
||||
assertNotNull(tableDefinition);
|
||||
if (updateExpression != null)
|
||||
assertNotNull(postUpdateQueryExpression);
|
||||
|
||||
// first, create the table as we need the CFMetaData to build the other cql statements
|
||||
createTable(tableDefinition);
|
||||
|
||||
// now setup the cql statements the test will run through. Some are dependent on
|
||||
// the table definition, others are not.
|
||||
String createIndexCql = String.format("CREATE INDEX %s ON %%s(%s)", indexName, indexTarget);
|
||||
String dropIndexCql = String.format("DROP INDEX %s.%s", KEYSPACE, indexName);
|
||||
|
||||
String selectFirstRowCql = String.format("SELECT * FROM %%s WHERE %s", queryExpression1);
|
||||
String selectSecondRowCql = String.format("SELECT * FROM %%s WHERE %s", queryExpression2);
|
||||
String insertCql = getInsertCql();
|
||||
String deleteRowCql = getDeleteRowCql();
|
||||
String deletePartitionCql = getDeletePartitionCql();
|
||||
|
||||
// everything setup, run through the smoke test
|
||||
execute(insertCql, firstRow);
|
||||
// before creating the index, check we cannot query on the indexed column
|
||||
assertInvalidThrowMessage(missingIndexMessage, InvalidRequestException.class, selectFirstRowCql);
|
||||
|
||||
// create the index, wait for it to be built then validate the indexed value
|
||||
createIndex(createIndexCql);
|
||||
waitForIndexBuild();
|
||||
assertRows(execute(selectFirstRowCql), firstRow);
|
||||
assertEmpty(execute(selectSecondRowCql));
|
||||
|
||||
// flush and check again
|
||||
flush();
|
||||
assertRows(execute(selectFirstRowCql), firstRow);
|
||||
assertEmpty(execute(selectSecondRowCql));
|
||||
|
||||
// force major compaction and query again
|
||||
compact();
|
||||
assertRows(execute(selectFirstRowCql), firstRow);
|
||||
assertEmpty(execute(selectSecondRowCql));
|
||||
|
||||
// drop the index and assert we can no longer query using it
|
||||
execute(dropIndexCql);
|
||||
assertInvalidThrowMessage(missingIndexMessage, InvalidRequestException.class, selectFirstRowCql);
|
||||
flush();
|
||||
compact();
|
||||
|
||||
// insert second row, re-create the index and query for both indexed values
|
||||
execute(insertCql, secondRow);
|
||||
createIndex(createIndexCql);
|
||||
waitForIndexBuild();
|
||||
assertRows(execute(selectFirstRowCql), firstRow);
|
||||
assertRows(execute(selectSecondRowCql), secondRow);
|
||||
|
||||
// modify the indexed value in the first row, assert we can query by the new value & not the original one
|
||||
// note: this is not possible if the indexed column is part of the primary key, so we skip it in that case
|
||||
if (includesUpdate())
|
||||
{
|
||||
execute(getUpdateCql(), getPrimaryKeyValues(firstRow));
|
||||
assertEmpty(execute(selectFirstRowCql));
|
||||
// update the select statement to query using the updated value
|
||||
selectFirstRowCql = String.format("SELECT * FROM %%s WHERE %s", postUpdateQueryExpression);
|
||||
// we can't check the entire row b/c we've modified something.
|
||||
// so we just check the primary key columns, as they cannot have changed
|
||||
assertPrimaryKeyColumnsOnly(execute(selectFirstRowCql), firstRow);
|
||||
}
|
||||
|
||||
// delete row, check that it cannot be found via index query
|
||||
execute(deleteRowCql, getPrimaryKeyValues(firstRow));
|
||||
assertEmpty(execute(selectFirstRowCql));
|
||||
|
||||
// delete partition, check that its rows cannot be retrieved via index query
|
||||
execute(deletePartitionCql, getPartitionKeyValues(secondRow));
|
||||
assertEmpty(execute(selectSecondRowCql));
|
||||
|
||||
// flush & compact, then verify that deleted values stay gone
|
||||
flush();
|
||||
compact();
|
||||
assertEmpty(execute(selectFirstRowCql));
|
||||
assertEmpty(execute(selectSecondRowCql));
|
||||
|
||||
// add back both rows, reset the select for the first row to query on the original value & verify
|
||||
execute(insertCql, firstRow);
|
||||
selectFirstRowCql = String.format("SELECT * FROM %%s WHERE %s", queryExpression1);
|
||||
assertRows(execute(selectFirstRowCql), firstRow);
|
||||
execute(insertCql, secondRow);
|
||||
assertRows(execute(selectSecondRowCql), secondRow);
|
||||
|
||||
// flush and compact, verify again & we're done
|
||||
flush();
|
||||
compact();
|
||||
assertRows(execute(selectFirstRowCql), firstRow);
|
||||
assertRows(execute(selectSecondRowCql), secondRow);
|
||||
}
|
||||
|
||||
private void assertPrimaryKeyColumnsOnly(UntypedResultSet resultSet, Object[] row)
|
||||
{
|
||||
assertFalse(resultSet.isEmpty());
|
||||
CFMetaData cfm = getCurrentColumnFamilyStore().metadata;
|
||||
int columnCount = cfm.partitionKeyColumns().size();
|
||||
if (cfm.isCompound())
|
||||
columnCount += cfm.clusteringColumns().size();
|
||||
Object[] expected = copyValuesFromRow(row, columnCount);
|
||||
assertArrayEquals(expected, copyValuesFromRow(getRows(resultSet)[0], columnCount));
|
||||
}
|
||||
|
||||
private String getInsertCql()
|
||||
{
|
||||
CFMetaData cfm = getCurrentColumnFamilyStore().metadata;
|
||||
String columns = Joiner.on(", ")
|
||||
.join(Iterators.transform(cfm.allColumnsInSelectOrder(),
|
||||
(column) -> column.name.toString()));
|
||||
String markers = Joiner.on(", ").join(Iterators.transform(cfm.allColumnsInSelectOrder(),
|
||||
(column) -> {
|
||||
return "?";
|
||||
}));
|
||||
return String.format("INSERT INTO %%s (%s) VALUES (%s)", columns, markers);
|
||||
}
|
||||
|
||||
private String getUpdateCql()
|
||||
{
|
||||
String whereClause = getPrimaryKeyColumns().map(column -> column.name.toString() + "=?")
|
||||
.collect(Collectors.joining(" AND "));
|
||||
return String.format("UPDATE %%s %s WHERE %s", updateExpression, whereClause);
|
||||
}
|
||||
|
||||
private String getDeleteRowCql()
|
||||
{
|
||||
return getPrimaryKeyColumns().map(column -> column.name.toString() + "=?")
|
||||
.collect(Collectors.joining(" AND ", "DELETE FROM %s WHERE ", ""));
|
||||
}
|
||||
|
||||
private String getDeletePartitionCql()
|
||||
{
|
||||
CFMetaData cfm = getCurrentColumnFamilyStore().metadata;
|
||||
return StreamSupport.stream(cfm.partitionKeyColumns().spliterator(), false)
|
||||
.map(column -> column.name.toString() + "=?")
|
||||
.collect(Collectors.joining(" AND ", "DELETE FROM %s WHERE ", ""));
|
||||
}
|
||||
|
||||
private Stream<ColumnDefinition> getPrimaryKeyColumns()
|
||||
{
|
||||
CFMetaData cfm = getCurrentColumnFamilyStore().metadata;
|
||||
if (cfm.isCompactTable())
|
||||
return cfm.partitionKeyColumns().stream();
|
||||
else
|
||||
return Stream.concat(cfm.partitionKeyColumns().stream(), cfm.clusteringColumns().stream());
|
||||
}
|
||||
|
||||
private Object[] getPrimaryKeyValues(Object[] row)
|
||||
{
|
||||
CFMetaData cfm = getCurrentColumnFamilyStore().metadata;
|
||||
if (cfm.isCompactTable())
|
||||
return getPartitionKeyValues(row);
|
||||
|
||||
return copyValuesFromRow(row, cfm.partitionKeyColumns().size() + cfm.clusteringColumns().size());
|
||||
}
|
||||
|
||||
private Object[] getPartitionKeyValues(Object[] row)
|
||||
{
|
||||
CFMetaData cfm = getCurrentColumnFamilyStore().metadata;
|
||||
return copyValuesFromRow(row, cfm.partitionKeyColumns().size());
|
||||
}
|
||||
|
||||
private Object[] copyValuesFromRow(Object[] row, int length)
|
||||
{
|
||||
Object[] values = new Object[length];
|
||||
System.arraycopy(row, 0, values, 0, length);
|
||||
return values;
|
||||
}
|
||||
|
||||
private boolean includesUpdate()
|
||||
{
|
||||
return updateExpression != null;
|
||||
}
|
||||
|
||||
// Spin waiting for named index to be built
|
||||
private void waitForIndexBuild() throws Throwable
|
||||
{
|
||||
ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
|
||||
String fullIndexName = String.format("%s.%s", currentTable(), indexName);
|
||||
long maxWaitMillis = 10000;
|
||||
long startTime = System.currentTimeMillis();
|
||||
while (! cfs.indexManager.getBuiltIndexNames().contains(fullIndexName))
|
||||
{
|
||||
Thread.sleep(100);
|
||||
long wait = System.currentTimeMillis() - startTime;
|
||||
if (wait > maxWaitMillis)
|
||||
fail(String.format("Timed out waiting for index %s to build (%s)ms", fullIndexName, wait));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,15 +19,8 @@ package org.apache.cassandra.io.sstable;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import org.junit.Assert;
|
||||
|
|
@ -40,19 +33,17 @@ import org.apache.cassandra.SchemaLoader;
|
|||
import org.apache.cassandra.Util;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.Operator;
|
||||
import org.apache.cassandra.db.BufferDecoratedKey;
|
||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||
import org.apache.cassandra.db.DecoratedKey;
|
||||
import org.apache.cassandra.db.Keyspace;
|
||||
import org.apache.cassandra.db.ReadCommand;
|
||||
import org.apache.cassandra.db.RowUpdateBuilder;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.compaction.CompactionManager;
|
||||
import org.apache.cassandra.db.compaction.OperationType;
|
||||
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexSearcher;
|
||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.dht.*;
|
||||
import org.apache.cassandra.dht.IPartitioner;
|
||||
import org.apache.cassandra.dht.LocalPartitioner.LocalToken;
|
||||
import org.apache.cassandra.dht.Range;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.index.Index;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||
import org.apache.cassandra.io.util.FileDataInput;
|
||||
import org.apache.cassandra.io.util.MmappedSegmentedFile;
|
||||
|
|
@ -64,8 +55,8 @@ import org.apache.cassandra.utils.ByteBufferUtil;
|
|||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.apache.cassandra.utils.ByteBufferUtil.bytes;
|
||||
|
||||
@RunWith(OrderedJUnit4ClassRunner.class)
|
||||
public class SSTableReaderTest
|
||||
|
|
@ -388,19 +379,23 @@ public class SSTableReaderTest
|
|||
|
||||
store.forceBlockingFlush();
|
||||
|
||||
ColumnFamilyStore indexCfs = store.indexManager.getIndexForColumn(store.metadata.getColumnDefinition(bytes("birthdate"))).getIndexCfs();
|
||||
assert indexCfs.isIndex();
|
||||
SSTableReader sstable = indexCfs.getLiveSSTables().iterator().next();
|
||||
assert sstable.first.getToken() instanceof LocalToken;
|
||||
|
||||
try(SegmentedFile.Builder ibuilder = SegmentedFile.getBuilder(DatabaseDescriptor.getIndexAccessMode(), false);
|
||||
SegmentedFile.Builder dbuilder = SegmentedFile.getBuilder(DatabaseDescriptor.getDiskAccessMode(), sstable.compression))
|
||||
for(ColumnFamilyStore indexCfs : store.indexManager.getAllIndexColumnFamilyStores())
|
||||
{
|
||||
sstable.saveSummary(ibuilder, dbuilder);
|
||||
assert indexCfs.isIndex();
|
||||
SSTableReader sstable = indexCfs.getLiveSSTables().iterator().next();
|
||||
assert sstable.first.getToken() instanceof LocalToken;
|
||||
|
||||
try (SegmentedFile.Builder ibuilder = SegmentedFile.getBuilder(DatabaseDescriptor.getIndexAccessMode(),
|
||||
false);
|
||||
SegmentedFile.Builder dbuilder = SegmentedFile.getBuilder(DatabaseDescriptor.getDiskAccessMode(),
|
||||
sstable.compression))
|
||||
{
|
||||
sstable.saveSummary(ibuilder, dbuilder);
|
||||
}
|
||||
SSTableReader reopened = SSTableReader.open(sstable.descriptor);
|
||||
assert reopened.first.getToken() instanceof LocalToken;
|
||||
reopened.selfRef().release();
|
||||
}
|
||||
SSTableReader reopened = SSTableReader.open(sstable.descriptor);
|
||||
assert reopened.first.getToken() instanceof LocalToken;
|
||||
reopened.selfRef().release();
|
||||
}
|
||||
|
||||
/** see CASSANDRA-5407 */
|
||||
|
|
@ -545,16 +540,18 @@ public class SSTableReaderTest
|
|||
for (ColumnFamilyStore cfs : indexedCFS.concatWithIndexes())
|
||||
clearAndLoad(cfs);
|
||||
|
||||
ByteBuffer bBB = bytes("birthdate");
|
||||
|
||||
// query using index to see if sstable for secondary index opens
|
||||
ReadCommand rc = Util.cmd(indexedCFS).fromKeyIncl("k1").toKeyIncl("k3")
|
||||
.columns("birthdate")
|
||||
.filterOn("birthdate", Operator.EQ, 1L)
|
||||
.build();
|
||||
List<SecondaryIndexSearcher> searchers = indexedCFS.indexManager.getIndexSearchersFor(rc);
|
||||
assertEquals(searchers.size(), 1);
|
||||
assertEquals(1, Util.getAll(rc).size());
|
||||
Index.Searcher searcher = indexedCFS.indexManager.getBestIndexFor(rc).searcherFor(rc);
|
||||
assertNotNull(searcher);
|
||||
try (ReadOrderGroup orderGroup = ReadOrderGroup.forCommand(rc))
|
||||
{
|
||||
assertEquals(1, Util.size(UnfilteredPartitionIterators.filter(searcher.search(orderGroup), rc.nowInSec())));
|
||||
}
|
||||
}
|
||||
|
||||
private List<Range<Token>> makeRanges(Token left, Token right)
|
||||
|
|
|
|||
|
|
@ -40,11 +40,11 @@ import org.apache.cassandra.cql3.UntypedResultSet;
|
|||
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||
import org.apache.cassandra.db.Directories;
|
||||
import org.apache.cassandra.db.Keyspace;
|
||||
import org.apache.cassandra.db.index.SecondaryIndex;
|
||||
import org.apache.cassandra.db.lifecycle.TransactionLog;
|
||||
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;
|
||||
|
|
@ -511,9 +511,18 @@ public class DefsTest
|
|||
|
||||
cfs.forceBlockingFlush();
|
||||
ColumnDefinition indexedColumn = cfs.metadata.getColumnDefinition(ByteBufferUtil.bytes("birthdate"));
|
||||
SecondaryIndex index = cfs.indexManager.getIndexForColumn(indexedColumn);
|
||||
ColumnFamilyStore indexedCfs = index.getIndexCfs();
|
||||
Descriptor desc = indexedCfs.getLiveSSTables().iterator().next().descriptor;
|
||||
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"));
|
||||
Descriptor desc = indexCfs.getLiveSSTables().iterator().next().descriptor;
|
||||
|
||||
// drop the index
|
||||
CFMetaData meta = cfs.metadata.copy();
|
||||
|
|
@ -536,7 +545,7 @@ public class DefsTest
|
|||
MigrationManager.announceColumnFamilyUpdate(meta, false);
|
||||
|
||||
// check
|
||||
assertTrue(cfs.indexManager.getIndexes().isEmpty());
|
||||
assertTrue(cfs.indexManager.listIndexes().isEmpty());
|
||||
TransactionLog.waitForDeletions();
|
||||
assertFalse(new File(desc.filenameFor(Component.DATA)).exists());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ import com.google.common.collect.ImmutableList;
|
|||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.SchemaLoader;
|
||||
import org.apache.cassandra.config.*;
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.config.Schema;
|
||||
import org.apache.cassandra.cql3.CQLTester;
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.functions.*;
|
||||
|
|
|
|||
Loading…
Reference in New Issue