Merge branch 'cassandra-2.0' into cassandra-2.1

Conflicts:
	CHANGES.txt
	src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
This commit is contained in:
Jake Luciani 2014-05-07 15:17:58 -04:00
commit 0cb1db68fc
5 changed files with 239 additions and 149 deletions

View File

@ -76,6 +76,7 @@ Merged from 1.2:
* Optimize Cell liveness checks and clean up Cell (CASSANDRA-7119)
* Support consistent range movements (CASSANDRA-2434)
Merged from 2.0:
* Starting threads in OutboundTcpConnectionPool constructor causes race conditions (CASSANDRA-7177)
* Allow overriding cassandra-rackdc.properties file (CASSANDRA-7072)
* Set JMX RMI port to 7199 (CASSANDRA-7087)
* Use LOCAL_QUORUM for data reads at LOCAL_SERIAL (CASSANDRA-6939)

View File

@ -117,6 +117,8 @@ public class BatchStatement implements CQLStatement, MeasurableForPreparedCache
{
if (timestampSet && statement.isTimestampSet())
throw new InvalidRequestException("Timestamp must be set either on BATCH or individual statements");
statement.validate(state);
}
}

View File

@ -23,18 +23,23 @@ import java.util.*;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import org.github.jamm.MemoryMeter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.composites.CBuilder;
import org.apache.cassandra.db.composites.Composite;
import org.apache.cassandra.db.filter.ColumnSlice;
import org.apache.cassandra.db.filter.IDiskAtomFilter;
import org.apache.cassandra.db.filter.SliceQueryFilter;
import org.apache.cassandra.db.marshal.CompositeType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.marshal.ListType;
import org.apache.cassandra.db.marshal.BooleanType;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.service.CASConditions;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.service.StorageProxy;
@ -49,16 +54,21 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
{
private static final ColumnIdentifier CAS_RESULT_COLUMN = new ColumnIdentifier("[applied]", false);
private static final Logger logger = LoggerFactory.getLogger(ModificationStatement.class);
private static boolean loggedCounterTTL = false;
private static boolean loggedCounterTimestamp = false;
public static enum StatementType { INSERT, UPDATE, DELETE }
public final StatementType type;
private final int boundTerms;
public final CFMetaData cfm;
public final Attributes attrs;
private final Map<ColumnIdentifier, Restriction> processedKeys = new HashMap<ColumnIdentifier, Restriction>();
private final List<Operation> columnOperations = new ArrayList<Operation>();
private int boundTerms;
// Separating normal and static conditions makes things somewhat easier
private List<ColumnCondition> columnConditions;
private List<ColumnCondition> staticConditions;
@ -70,18 +80,17 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
private boolean setsStaticColumns;
private boolean setsRegularColumns;
private final Function<ColumnCondition, ColumnDefinition> getColumnForCondition = new Function<ColumnCondition, ColumnDefinition>()
private final Function<ColumnCondition, ColumnIdentifier> getColumnForCondition = new Function<ColumnCondition, ColumnIdentifier>()
{
public ColumnDefinition apply(ColumnCondition cond)
public ColumnIdentifier apply(ColumnCondition cond)
{
return cond.column;
return cond.column.name;
}
};
public ModificationStatement(StatementType type, int boundTerms, CFMetaData cfm, Attributes attrs)
public ModificationStatement(StatementType type, CFMetaData cfm, Attributes attrs)
{
this.type = type;
this.boundTerms = boundTerms;
this.cfm = cfm;
this.attrs = attrs;
}
@ -97,7 +106,7 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
}
public abstract boolean requireFullClusteringKey();
public abstract void addUpdateForKey(ColumnFamily updates, ByteBuffer key, Composite prefix, UpdateParameters params) throws InvalidRequestException;
public abstract void addUpdateForKey(ColumnFamily updates, ByteBuffer key, ColumnNameBuilder builder, UpdateParameters params) throws InvalidRequestException;
public int getBoundTerms()
{
@ -116,12 +125,12 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
public boolean isCounter()
{
return cfm.isCounter();
return cfm.getDefaultValidator().isCommutative();
}
public long getTimestamp(long now, QueryOptions options) throws InvalidRequestException
public long getTimestamp(long now, List<ByteBuffer> variables) throws InvalidRequestException
{
return attrs.getTimestamp(now, options);
return attrs.getTimestamp(now, variables);
}
public boolean isTimestampSet()
@ -129,9 +138,9 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
return attrs.isTimestampSet();
}
public int getTimeToLive(QueryOptions options) throws InvalidRequestException
public int getTimeToLive(List<ByteBuffer> variables) throws InvalidRequestException
{
return attrs.getTimeToLive(options);
return attrs.getTimeToLive(variables);
}
public void checkAccess(ClientState state) throws InvalidRequestException, UnauthorizedException
@ -146,18 +155,31 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
public void validate(ClientState state) throws InvalidRequestException
{
if (hasConditions() && attrs.isTimestampSet())
throw new InvalidRequestException("Cannot provide custom timestamp for conditional updates");
throw new InvalidRequestException("Cannot provide custom timestamp for conditional update");
if (isCounter() && attrs.isTimestampSet())
throw new InvalidRequestException("Cannot provide custom timestamp for counter updates");
if (isCounter())
{
if (attrs.isTimestampSet() && !loggedCounterTimestamp)
{
logger.warn("Detected use of 'USING TIMESTAMP' in a counter UPDATE. This is invalid " +
"because counters do not use timestamps, and the timestamp has been ignored. " +
"Such queries will be rejected in Cassandra 2.1+ - please fix your queries before then.");
loggedCounterTimestamp = true;
}
if (isCounter() && attrs.isTimeToLiveSet())
throw new InvalidRequestException("Cannot provide custom TTL for counter updates");
if (attrs.isTimeToLiveSet() && !loggedCounterTTL)
{
logger.warn("Detected use of 'USING TTL' in a counter UPDATE. This is invalid " +
"because counter tables do not support TTL, and the TTL value has been ignored. " +
"Such queries will be rejected in Cassandra 2.1+ - please fix your queries before then.");
loggedCounterTTL = true;
}
}
}
public void addOperation(Operation op)
{
if (op.column.isStatic())
if (op.isStatic(cfm))
setsStaticColumns = true;
else
setsRegularColumns = true;
@ -169,19 +191,19 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
return columnOperations;
}
public Iterable<ColumnDefinition> getColumnsWithConditions()
public Iterable<ColumnIdentifier> getColumnsWithConditions()
{
if (ifNotExists || ifExists)
return null;
return Iterables.concat(columnConditions == null ? Collections.<ColumnDefinition>emptyList() : Iterables.transform(columnConditions, getColumnForCondition),
staticConditions == null ? Collections.<ColumnDefinition>emptyList() : Iterables.transform(staticConditions, getColumnForCondition));
return Iterables.concat(columnConditions == null ? Collections.<ColumnIdentifier>emptyList() : Iterables.transform(columnConditions, getColumnForCondition),
staticConditions == null ? Collections.<ColumnIdentifier>emptyList() : Iterables.transform(staticConditions, getColumnForCondition));
}
public void addCondition(ColumnCondition cond) throws InvalidRequestException
{
List<ColumnCondition> conds = null;
if (cond.column.isStatic())
if (cond.column.kind == CFDefinition.Name.Kind.STATIC)
{
setsStaticColumns = true;
if (staticConditions == null)
@ -218,44 +240,45 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
return ifExists;
}
private void addKeyValues(ColumnDefinition def, Restriction values) throws InvalidRequestException
private void addKeyValues(CFDefinition.Name name, Restriction values) throws InvalidRequestException
{
if (def.kind == ColumnDefinition.Kind.CLUSTERING_COLUMN)
if (name.kind == CFDefinition.Name.Kind.COLUMN_ALIAS)
hasNoClusteringColumns = false;
if (processedKeys.put(def.name, values) != null)
throw new InvalidRequestException(String.format("Multiple definitions found for PRIMARY KEY part %s", def.name));
if (processedKeys.put(name.name, values) != null)
throw new InvalidRequestException(String.format("Multiple definitions found for PRIMARY KEY part %s", name.name));
}
public void addKeyValue(ColumnDefinition def, Term value) throws InvalidRequestException
public void addKeyValue(CFDefinition.Name name, Term value) throws InvalidRequestException
{
addKeyValues(def, new Restriction.EQ(value, false));
addKeyValues(name, new Restriction.EQ(value, false));
}
public void processWhereClause(List<Relation> whereClause, VariableSpecifications names) throws InvalidRequestException
{
CFDefinition cfDef = cfm.getCfDef();
for (Relation rel : whereClause)
{
ColumnDefinition def = cfm.getColumnDefinition(rel.getEntity());
if (def == null)
CFDefinition.Name name = cfDef.get(rel.getEntity());
if (name == null)
throw new InvalidRequestException(String.format("Unknown key identifier %s", rel.getEntity()));
switch (def.kind)
switch (name.kind)
{
case PARTITION_KEY:
case CLUSTERING_COLUMN:
case KEY_ALIAS:
case COLUMN_ALIAS:
Restriction restriction;
if (rel.operator() == Relation.Type.EQ)
{
Term t = rel.getValue().prepare(keyspace(), def);
Term t = rel.getValue().prepare(name);
t.collectMarkerSpecification(names);
restriction = new Restriction.EQ(t, false);
}
else if (def.kind == ColumnDefinition.Kind.PARTITION_KEY && rel.operator() == Relation.Type.IN)
else if (name.kind == CFDefinition.Name.Kind.KEY_ALIAS && rel.operator() == Relation.Type.IN)
{
if (rel.getValue() != null)
{
Term t = rel.getValue().prepare(keyspace(), def);
Term t = rel.getValue().prepare(name);
t.collectMarkerSpecification(names);
restriction = Restriction.IN.create(t);
}
@ -264,7 +287,7 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
List<Term> values = new ArrayList<Term>(rel.getInValues().size());
for (Term.Raw raw : rel.getInValues())
{
Term t = raw.prepare(keyspace(), def);
Term t = raw.prepare(name);
t.collectMarkerSpecification(names);
values.add(t);
}
@ -273,37 +296,40 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
}
else
{
throw new InvalidRequestException(String.format("Invalid operator %s for PRIMARY KEY part %s", rel.operator(), def.name));
throw new InvalidRequestException(String.format("Invalid operator %s for PRIMARY KEY part %s", rel.operator(), name));
}
addKeyValues(def, restriction);
addKeyValues(name, restriction);
break;
default:
throw new InvalidRequestException(String.format("Non PRIMARY KEY %s found in where clause", def.name));
case VALUE_ALIAS:
case COLUMN_METADATA:
case STATIC:
throw new InvalidRequestException(String.format("Non PRIMARY KEY %s found in where clause", name));
}
}
}
public List<ByteBuffer> buildPartitionKeyNames(QueryOptions options)
public List<ByteBuffer> buildPartitionKeyNames(List<ByteBuffer> variables)
throws InvalidRequestException
{
CBuilder keyBuilder = cfm.getKeyValidatorAsCType().builder();
CFDefinition cfDef = cfm.getCfDef();
ColumnNameBuilder keyBuilder = cfDef.getKeyNameBuilder();
List<ByteBuffer> keys = new ArrayList<ByteBuffer>();
for (ColumnDefinition def : cfm.partitionKeyColumns())
for (CFDefinition.Name name : cfDef.partitionKeys())
{
Restriction r = processedKeys.get(def.name);
Restriction r = processedKeys.get(name.name);
if (r == null)
throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s", def.name));
throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s", name));
List<ByteBuffer> values = r.values(options);
List<ByteBuffer> values = r.values(variables);
if (keyBuilder.remainingCount() == 1)
{
for (ByteBuffer val : values)
{
if (val == null)
throw new InvalidRequestException(String.format("Invalid null value for partition key part %s", def.name));
ByteBuffer key = keyBuilder.buildWith(val).toByteBuffer();
throw new InvalidRequestException(String.format("Invalid null value for partition key part %s", name));
ByteBuffer key = keyBuilder.copy().add(val).build();
ThriftValidation.validateKey(cfm, key);
keys.add(key);
}
@ -314,14 +340,14 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
throw new InvalidRequestException("IN is only supported on the last column of the partition key");
ByteBuffer val = values.get(0);
if (val == null)
throw new InvalidRequestException(String.format("Invalid null value for partition key part %s", def.name));
throw new InvalidRequestException(String.format("Invalid null value for partition key part %s", name));
keyBuilder.add(val);
}
}
return keys;
}
public Composite createClusteringPrefix(QueryOptions options)
public ColumnNameBuilder createClusteringPrefixBuilder(List<ByteBuffer> variables)
throws InvalidRequestException
{
// If the only updated/deleted columns are static, then we don't need clustering columns.
@ -338,83 +364,96 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
{
// If we set no non-static columns, then it's fine not to have clustering columns
if (hasNoClusteringColumns)
return cfm.comparator.staticPrefix();
return cfm.getStaticColumnNameBuilder();
// If we do have clustering columns however, then either it's an INSERT and the query is valid
// but we still need to build a proper prefix, or it's not an INSERT, and then we want to reject
// (see above)
if (type != StatementType.INSERT)
{
for (ColumnDefinition def : cfm.clusteringColumns())
if (processedKeys.get(def.name) != null)
throw new InvalidRequestException(String.format("Invalid restriction on clustering column %s since the %s statement modifies only static columns", def.name, type));
for (CFDefinition.Name name : cfm.getCfDef().clusteringColumns())
if (processedKeys.get(name.name) != null)
throw new InvalidRequestException(String.format("Invalid restriction on clustering column %s since the %s statement modifies only static columns", name.name, type));
// we should get there as it contradicts hasNoClusteringColumns == false
throw new AssertionError();
}
}
return createClusteringPrefixBuilderInternal(options);
return createClusteringPrefixBuilderInternal(variables);
}
private Composite createClusteringPrefixBuilderInternal(QueryOptions options)
private ColumnNameBuilder updatePrefixFor(ByteBuffer name, ColumnNameBuilder prefix)
{
return isStatic(name) ? cfm.getStaticColumnNameBuilder() : prefix;
}
public boolean isStatic(ByteBuffer name)
{
ColumnDefinition def = cfm.getColumnDefinition(name);
return def != null && def.type == ColumnDefinition.Type.STATIC;
}
private ColumnNameBuilder createClusteringPrefixBuilderInternal(List<ByteBuffer> variables)
throws InvalidRequestException
{
CBuilder builder = cfm.comparator.prefixBuilder();
ColumnDefinition firstEmptyKey = null;
for (ColumnDefinition def : cfm.clusteringColumns())
CFDefinition cfDef = cfm.getCfDef();
ColumnNameBuilder builder = cfDef.getColumnNameBuilder();
CFDefinition.Name firstEmptyKey = null;
for (CFDefinition.Name name : cfDef.clusteringColumns())
{
Restriction r = processedKeys.get(def.name);
Restriction r = processedKeys.get(name.name);
if (r == null)
{
firstEmptyKey = def;
if (requireFullClusteringKey() && !cfm.comparator.isDense() && cfm.comparator.isCompound())
throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s", def.name));
firstEmptyKey = name;
if (requireFullClusteringKey() && cfDef.isComposite && !cfDef.isCompact)
throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s", name));
}
else if (firstEmptyKey != null)
{
throw new InvalidRequestException(String.format("Missing PRIMARY KEY part %s since %s is set", firstEmptyKey.name, def.name));
throw new InvalidRequestException(String.format("Missing PRIMARY KEY part %s since %s is set", firstEmptyKey.name, name.name));
}
else
{
List<ByteBuffer> values = r.values(options);
List<ByteBuffer> values = r.values(variables);
assert values.size() == 1; // We only allow IN for row keys so far
ByteBuffer val = values.get(0);
if (val == null)
throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", def.name));
throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", name));
builder.add(val);
}
}
return builder.build();
return builder;
}
protected ColumnDefinition getFirstEmptyKey()
protected CFDefinition.Name getFirstEmptyKey()
{
for (ColumnDefinition def : cfm.clusteringColumns())
for (CFDefinition.Name name : cfm.getCfDef().clusteringColumns())
{
if (processedKeys.get(def.name) == null)
return def;
if (processedKeys.get(name.name) == null)
return name;
}
return null;
}
protected Map<ByteBuffer, CQL3Row> readRequiredRows(Collection<ByteBuffer> partitionKeys, Composite clusteringPrefix, boolean local, ConsistencyLevel cl)
protected Map<ByteBuffer, ColumnGroupMap> readRequiredRows(Collection<ByteBuffer> partitionKeys, ColumnNameBuilder clusteringPrefix, boolean local, ConsistencyLevel cl)
throws RequestExecutionException, RequestValidationException
{
// Lists SET operation incurs a read.
boolean requiresRead = false;
Set<ByteBuffer> toRead = null;
for (Operation op : columnOperations)
{
if (op.requiresRead())
{
requiresRead = true;
break;
if (toRead == null)
toRead = new TreeSet<ByteBuffer>(UTF8Type.instance);
toRead.add(op.columnName.key);
}
}
return requiresRead ? readRows(partitionKeys, clusteringPrefix, cfm, local, cl) : null;
return toRead == null ? null : readRows(partitionKeys, clusteringPrefix, toRead, (CompositeType)cfm.comparator, local, cl);
}
protected Map<ByteBuffer, CQL3Row> readRows(Collection<ByteBuffer> partitionKeys, Composite rowPrefix, CFMetaData cfm, boolean local, ConsistencyLevel cl)
private Map<ByteBuffer, ColumnGroupMap> readRows(Collection<ByteBuffer> partitionKeys, ColumnNameBuilder clusteringPrefix, Set<ByteBuffer> toRead, CompositeType composite, boolean local, ConsistencyLevel cl)
throws RequestExecutionException, RequestValidationException
{
try
@ -426,7 +465,16 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
throw new InvalidRequestException(String.format("Write operation require a read but consistency %s is not supported on reads", cl));
}
ColumnSlice[] slices = new ColumnSlice[]{ rowPrefix.slice() };
ColumnSlice[] slices = new ColumnSlice[toRead.size()];
int i = 0;
for (ByteBuffer name : toRead)
{
ColumnNameBuilder prefix = updatePrefixFor(name, clusteringPrefix);
ByteBuffer start = prefix.copy().add(name).build();
ByteBuffer finish = prefix.copy().add(name).buildAsEndOfRange();
slices[i++] = new ColumnSlice(start, finish);
}
List<ReadCommand> commands = new ArrayList<ReadCommand>(partitionKeys.size());
long now = System.currentTimeMillis();
for (ByteBuffer key : partitionKeys)
@ -440,19 +488,20 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
? SelectStatement.readLocally(keyspace(), commands)
: StorageProxy.read(commands, cl);
Map<ByteBuffer, CQL3Row> map = new HashMap<ByteBuffer, CQL3Row>();
Map<ByteBuffer, ColumnGroupMap> map = new HashMap<ByteBuffer, ColumnGroupMap>();
for (Row row : rows)
{
if (row.cf == null || row.cf.isEmpty())
if (row.cf == null || row.cf.getColumnCount() == 0)
continue;
Iterator<CQL3Row> iter = cfm.comparator.CQL3RowBuilder(cfm, now).group(row.cf.getSortedColumns().iterator());
if (iter.hasNext())
{
map.put(row.key.getKey(), iter.next());
// We can only update one CQ3Row per partition key at a time (we don't allow IN for clustering key)
assert !iter.hasNext();
}
ColumnGroupMap.Builder groupBuilder = new ColumnGroupMap.Builder(composite, true, now);
for (Column column : row.cf)
groupBuilder.add(column);
List<ColumnGroupMap> groups = groupBuilder.groups();
assert groups.isEmpty() || groups.size() == 1;
if (!groups.isEmpty())
map.put(row.key.key, groups.get(0));
}
return map;
}
@ -488,7 +537,7 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
else
cl.validateForWrite(cfm.ksName);
Collection<? extends IMutation> mutations = getMutations(options, false, options.getTimestamp(queryState));
Collection<? extends IMutation> mutations = getMutations(options.getValues(), false, cl, queryState.getTimestamp());
if (!mutations.isEmpty())
StorageProxy.mutateWithTriggers(mutations, cl, false);
@ -498,18 +547,18 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
public ResultMessage executeWithCondition(QueryState queryState, QueryOptions options)
throws RequestExecutionException, RequestValidationException
{
List<ByteBuffer> keys = buildPartitionKeyNames(options);
List<ByteBuffer> variables = options.getValues();
List<ByteBuffer> keys = buildPartitionKeyNames(variables);
// We don't support IN for CAS operation so far
if (keys.size() > 1)
throw new InvalidRequestException("IN on the partition key is not supported with conditional updates");
ByteBuffer key = keys.get(0);
long now = options.getTimestamp(queryState);
CQL3CasConditions conditions = new CQL3CasConditions(cfm, now);
Composite prefix = createClusteringPrefix(options);
ColumnFamily updates = ArrayBackedSortedColumns.factory.create(cfm);
addUpdatesAndConditions(key, prefix, updates, conditions, options, getTimestamp(now, options));
CQL3CasConditions conditions = new CQL3CasConditions(cfm, queryState.getTimestamp());
ColumnNameBuilder prefix = createClusteringPrefixBuilder(variables);
ColumnFamily updates = UnsortedColumns.factory.create(cfm);
addUpdatesAndConditions(key, prefix, updates, conditions, variables, getTimestamp(queryState.getTimestamp(), variables));
ColumnFamily result = StorageProxy.cas(keyspace(),
columnFamily(),
@ -521,16 +570,16 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
return new ResultMessage.Rows(buildCasResultSet(key, result));
}
public void addUpdatesAndConditions(ByteBuffer key, Composite clusteringPrefix, ColumnFamily updates, CQL3CasConditions conditions, QueryOptions options, long now)
public void addUpdatesAndConditions(ByteBuffer key, ColumnNameBuilder clusteringPrefix, ColumnFamily updates, CQL3CasConditions conditions, List<ByteBuffer> variables, long now)
throws InvalidRequestException
{
UpdateParameters updParams = new UpdateParameters(cfm, options, now, getTimeToLive(options), null);
UpdateParameters updParams = new UpdateParameters(cfm, variables, now, getTimeToLive(variables), null);
addUpdateForKey(updates, key, clusteringPrefix, updParams);
if (ifNotExists)
{
// If we use ifNotExists, if the statement applies to any non static columns, then the condition is on the row of the non-static
// columns and the prefix should be the clusteringPrefix. But if only static columns are set, then the ifNotExists apply to the existence
// columns and the prefix should be the rowPrefix. But if only static columns are set, then the ifNotExists apply to the existence
// of any static columns and we should use the prefix for the "static part" of the partition.
conditions.addNotExist(clusteringPrefix);
}
@ -541,9 +590,9 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
else
{
if (columnConditions != null)
conditions.addConditions(clusteringPrefix, columnConditions, options);
conditions.addConditions(clusteringPrefix, columnConditions, variables);
if (staticConditions != null)
conditions.addConditions(cfm.comparator.staticPrefix(), staticConditions, options);
conditions.addConditions(cfm.getStaticColumnNameBuilder(), staticConditions, variables);
}
}
@ -552,7 +601,7 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
return buildCasResultSet(keyspace(), key, columnFamily(), cf, getColumnsWithConditions(), false);
}
public static ResultSet buildCasResultSet(String ksName, ByteBuffer key, String cfName, ColumnFamily cf, Iterable<ColumnDefinition> columnsWithConditions, boolean isBatch)
public static ResultSet buildCasResultSet(String ksName, ByteBuffer key, String cfName, ColumnFamily cf, Iterable<ColumnIdentifier> columnsWithConditions, boolean isBatch)
throws InvalidRequestException
{
boolean success = cf == null;
@ -588,33 +637,34 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
return new ResultSet(new ResultSet.Metadata(specs), rows);
}
private static ResultSet buildCasFailureResultSet(ByteBuffer key, ColumnFamily cf, Iterable<ColumnDefinition> columnsWithConditions, boolean isBatch)
private static ResultSet buildCasFailureResultSet(ByteBuffer key, ColumnFamily cf, Iterable<ColumnIdentifier> columnsWithConditions, boolean isBatch)
throws InvalidRequestException
{
CFMetaData cfm = cf.metadata();
CFDefinition cfDef = cf.metadata().getCfDef();
Selection selection;
if (columnsWithConditions == null)
{
selection = Selection.wildcard(cfm);
selection = Selection.wildcard(cfDef);
}
else
{
List<ColumnDefinition> defs = new ArrayList<>();
List<CFDefinition.Name> names = new ArrayList<CFDefinition.Name>();
// Adding the partition key for batches to disambiguate if the conditions span multipe rows (we don't add them outside
// of batches for compatibility sakes).
if (isBatch)
{
defs.addAll(cfm.partitionKeyColumns());
defs.addAll(cfm.clusteringColumns());
names.addAll(cfDef.partitionKeys());
names.addAll(cfDef.clusteringColumns());
}
for (ColumnDefinition def : columnsWithConditions)
defs.add(def);
selection = Selection.forColumns(defs);
for (ColumnIdentifier id : columnsWithConditions)
names.add(cfDef.get(id));
selection = Selection.forColumns(names);
}
long now = System.currentTimeMillis();
Selection.ResultSetBuilder builder = selection.resultSetBuilder(now);
SelectStatement.forSelection(cfm, selection).processColumnFamily(key, cf, QueryOptions.DEFAULT, now, builder);
SelectStatement.forSelection(cfDef, selection).processColumnFamily(key, cf, Collections.<ByteBuffer>emptyList(), now, builder);
return builder.build();
}
@ -624,19 +674,15 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
if (hasConditions())
throw new UnsupportedOperationException();
for (IMutation mutation : getMutations(QueryOptions.DEFAULT, true, queryState.getTimestamp()))
{
// We don't use counters internally.
assert mutation instanceof Mutation;
((Mutation) mutation).apply();
}
for (IMutation mutation : getMutations(Collections.<ByteBuffer>emptyList(), true, null, queryState.getTimestamp()))
mutation.apply();
return null;
}
/**
* Convert statement into a list of mutations to apply on the server
*
* @param options value for prepared statement markers
* @param variables value for prepared statement markers
* @param local if true, any requests (for collections) performed by getMutation should be done locally only.
* @param cl the consistency to use for the potential reads involved in generating the mutations (for lists set/delete operations)
* @param now the current timestamp in microseconds to use if no timestamp is user provided.
@ -644,36 +690,37 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
* @return list of the mutations
* @throws InvalidRequestException on invalid requests
*/
private Collection<? extends IMutation> getMutations(QueryOptions options, boolean local, long now)
private Collection<? extends IMutation> getMutations(List<ByteBuffer> variables, boolean local, ConsistencyLevel cl, long now)
throws RequestExecutionException, RequestValidationException
{
List<ByteBuffer> keys = buildPartitionKeyNames(options);
Composite clusteringPrefix = createClusteringPrefix(options);
List<ByteBuffer> keys = buildPartitionKeyNames(variables);
ColumnNameBuilder clusteringPrefix = createClusteringPrefixBuilder(variables);
UpdateParameters params = makeUpdateParameters(keys, clusteringPrefix, options, local, now);
UpdateParameters params = makeUpdateParameters(keys, clusteringPrefix, variables, local, cl, now);
Collection<IMutation> mutations = new ArrayList<IMutation>();
for (ByteBuffer key: keys)
{
ThriftValidation.validateKey(cfm, key);
ColumnFamily cf = ArrayBackedSortedColumns.factory.create(cfm);
ColumnFamily cf = UnsortedColumns.factory.create(cfm);
addUpdateForKey(cf, key, clusteringPrefix, params);
Mutation mut = new Mutation(cfm.ksName, key, cf);
mutations.add(isCounter() ? new CounterMutation(mut, options.getConsistency()) : mut);
RowMutation rm = new RowMutation(cfm.ksName, key, cf);
mutations.add(isCounter() ? new CounterMutation(rm, cl) : rm);
}
return mutations;
}
public UpdateParameters makeUpdateParameters(Collection<ByteBuffer> keys,
Composite prefix,
QueryOptions options,
ColumnNameBuilder prefix,
List<ByteBuffer> variables,
boolean local,
ConsistencyLevel cl,
long now)
throws RequestExecutionException, RequestValidationException
{
// Some lists operation requires reading
Map<ByteBuffer, CQL3Row> rows = readRequiredRows(keys, prefix, local, options.getConsistency());
return new UpdateParameters(cfm, options, getTimestamp(now, options), getTimeToLive(options), rows);
Map<ByteBuffer, ColumnGroupMap> rows = readRequiredRows(keys, prefix, local, cl);
return new UpdateParameters(cfm, variables, getTimestamp(now, variables), getTimeToLive(variables), rows);
}
public static abstract class Parsed extends CFStatement
@ -702,11 +749,16 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
public ModificationStatement prepare(VariableSpecifications boundNames) throws InvalidRequestException
{
CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace(), columnFamily());
CFDefinition cfDef = metadata.getCfDef();
// The collected count in the beginning of preparation.
// Will start at non-zero for statements nested inside a BatchStatement (the second and the further ones).
int collected = boundNames.getCollectedCount();
Attributes preparedAttributes = attrs.prepare(keyspace(), columnFamily());
preparedAttributes.collectMarkerSpecification(boundNames);
ModificationStatement stmt = prepareInternal(metadata, boundNames, preparedAttributes);
ModificationStatement stmt = prepareInternal(cfDef, boundNames, preparedAttributes);
if (ifNotExists || ifExists || !conditions.isEmpty())
{
@ -714,7 +766,7 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
throw new InvalidRequestException("Conditional updates are not supported on counter tables");
if (attrs.timestamp != null)
throw new InvalidRequestException("Cannot provide custom timestamp for conditional updates");
throw new InvalidRequestException("Cannot provide custom timestamp for conditional update");
if (ifNotExists)
{
@ -734,28 +786,32 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF
{
for (Pair<ColumnIdentifier, ColumnCondition.Raw> entry : conditions)
{
ColumnDefinition def = metadata.getColumnDefinition(entry.left);
if (def == null)
CFDefinition.Name name = cfDef.get(entry.left);
if (name == null)
throw new InvalidRequestException(String.format("Unknown identifier %s", entry.left));
ColumnCondition condition = entry.right.prepare(keyspace(), def);
ColumnCondition condition = entry.right.prepare(name);
condition.collectMarkerSpecification(boundNames);
switch (def.kind)
switch (name.kind)
{
case PARTITION_KEY:
case CLUSTERING_COLUMN:
case KEY_ALIAS:
case COLUMN_ALIAS:
throw new InvalidRequestException(String.format("PRIMARY KEY part %s found in SET part", entry.left));
default:
case VALUE_ALIAS:
case COLUMN_METADATA:
case STATIC:
stmt.addCondition(condition);
break;
}
}
}
}
stmt.boundTerms = boundNames.getCollectedCount() - collected;
return stmt;
}
protected abstract ModificationStatement prepareInternal(CFMetaData cfm, VariableSpecifications boundNames, Attributes attrs) throws InvalidRequestException;
protected abstract ModificationStatement prepareInternal(CFDefinition cfDef, VariableSpecifications boundNames, Attributes attrs) throws InvalidRequestException;
}
}

View File

@ -512,11 +512,11 @@ public final class MessagingService implements MessagingServiceMBean
cp = new OutboundTcpConnectionPool(to);
OutboundTcpConnectionPool existingPool = connectionManagers.putIfAbsent(to, cp);
if (existingPool != null)
{
cp.close();
cp = existingPool;
}
else
cp.start();
}
cp.waitForStarted();
return cp;
}

View File

@ -22,6 +22,8 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.SocketChannel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.cassandra.concurrent.Stage;
import org.apache.cassandra.config.Config;
@ -36,6 +38,7 @@ public class OutboundTcpConnectionPool
{
// pointer for the real Address.
private final InetAddress id;
private final CountDownLatch started;
public final OutboundTcpConnection cmdCon;
public final OutboundTcpConnection ackCon;
// pointer to the reseted Address.
@ -46,13 +49,10 @@ public class OutboundTcpConnectionPool
{
id = remoteEp;
resetedEndpoint = SystemKeyspace.getPreferredIP(remoteEp);
started = new CountDownLatch(1);
cmdCon = new OutboundTcpConnection(this);
cmdCon.start();
ackCon = new OutboundTcpConnection(this);
ackCon.start();
metrics = new ConnectionMetrics(id, this);
}
/**
@ -167,14 +167,45 @@ public class OutboundTcpConnectionPool
}
return true;
}
public void start()
{
cmdCon.start();
ackCon.start();
public void close()
metrics = new ConnectionMetrics(id, this);
started.countDown();
}
public void waitForStarted()
{
if (started.getCount() == 0)
return;
boolean error = false;
try
{
if (!started.await(1, TimeUnit.MINUTES))
error = true;
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
error = true;
}
if (error)
throw new IllegalStateException(String.format("Connections to %s are not started!", id.getHostAddress()));
}
public void close()
{
// these null guards are simply for tests
if (ackCon != null)
ackCon.closeSocket(true);
if (cmdCon != null)
cmdCon.closeSocket(true);
metrics.release();
}
}