Return-Path: X-Original-To: apmail-cassandra-commits-archive@www.apache.org Delivered-To: apmail-cassandra-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id D28F0E2DB for ; Fri, 8 Feb 2013 17:46:00 +0000 (UTC) Received: (qmail 27600 invoked by uid 500); 8 Feb 2013 17:46:00 -0000 Delivered-To: apmail-cassandra-commits-archive@cassandra.apache.org Received: (qmail 27516 invoked by uid 500); 8 Feb 2013 17:46:00 -0000 Mailing-List: contact commits-help@cassandra.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cassandra.apache.org Delivered-To: mailing list commits@cassandra.apache.org Received: (qmail 27318 invoked by uid 99); 8 Feb 2013 17:45:59 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 08 Feb 2013 17:45:59 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 9D9423A73C; Fri, 8 Feb 2013 17:45:59 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: slebresne@apache.org To: commits@cassandra.apache.org X-Mailer: ASF-Git Admin Mailer Subject: [2/5] CQL3 refactor to allow conversion function Message-Id: <20130208174559.9D9423A73C@tyr.zones.apache.org> Date: Fri, 8 Feb 2013 17:45:59 +0000 (UTC) http://git-wip-us.apache.org/repos/asf/cassandra/blob/31e669ab/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java index 8f74b72..4491394 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@ -66,7 +66,7 @@ public class SelectStatement implements CQLStatement private final int boundTerms; public final CFDefinition cfDef; public final Parameters parameters; - private final List> selectedNames = new ArrayList>(); // empty => wildcard + private final Selection selection; private final Restriction[] keyRestrictions; private final Restriction[] columnRestrictions; @@ -95,10 +95,11 @@ public class SelectStatement implements CQLStatement } }; - public SelectStatement(CFDefinition cfDef, int boundTerms, Parameters parameters) + public SelectStatement(CFDefinition cfDef, int boundTerms, Parameters parameters, Selection selection) { this.cfDef = cfDef; this.boundTerms = boundTerms; + this.selection = selection; this.keyRestrictions = new Restriction[cfDef.keys.size()]; this.columnRestrictions = new Restriction[cfDef.columns.size()]; this.parameters = parameters; @@ -342,13 +343,13 @@ public class SelectStatement implements CQLStatement if (builder.remainingCount() == 1) { for (Term t : r.eqValues) - keys.add(builder.copy().add(t, Relation.Type.EQ, variables).build()); + keys.add(builder.copy().add(t.bindAndGet(variables)).build()); } else { if (r.eqValues.size() > 1) throw new InvalidRequestException("IN is only supported on the last column of the partition key"); - builder.add(r.eqValues.get(0), Relation.Type.EQ, variables); + builder.add(r.eqValues.get(0).bindAndGet(variables)); } } return keys; @@ -371,29 +372,8 @@ public class SelectStatement implements CQLStatement if (t == null) return p.getMinimumToken(); - if (!t.isToken) - { - try - { - String text = t.getText(); - p.getTokenFactory().validate(text); - return p.getTokenFactory().fromString(text); - } - catch (ConfigurationException e) - { - throw new InvalidRequestException(e.getMessage()); - } - } - - assert t.isToken; - ColumnNameBuilder builder = cfDef.getKeyNameBuilder(); - // We know all keyRestriction must be set - for (CFDefinition.Name name : cfDef.keys.values()) - { - Restriction r = keyRestrictions[name.position]; - builder.add(r.isEquality() ? r.eqValues.get(0) : r.bound(b), Relation.Type.EQ, variables); - } - return p.getToken(builder.build()); + ByteBuffer value = t.bindAndGet(variables); + return p.getTokenFactory().fromByteArray(value); } private boolean includeKeyBound(Bound b) @@ -429,11 +409,6 @@ public class SelectStatement implements CQLStatement return false; } - private boolean isWildcard() - { - return selectedNames.isEmpty(); - } - private SortedSet getRequestedColumns(List variables) throws InvalidRequestException { assert !isColumnRange(); @@ -452,14 +427,14 @@ public class SelectStatement implements CQLStatement { Term v = iter.next(); ColumnNameBuilder b = iter.hasNext() ? builder.copy() : builder; - ByteBuffer cname = b.add(v, Relation.Type.EQ, variables).build(); + ByteBuffer cname = b.add(v.bindAndGet(variables)).build(); columns.add(cname); } return columns; } else { - builder.add(r.eqValues.get(0), Relation.Type.EQ, variables); + builder.add(r.eqValues.get(0).bindAndGet(variables)); } } @@ -485,8 +460,8 @@ public class SelectStatement implements CQLStatement columns.add(builder.copy().add(ByteBufferUtil.EMPTY_BYTE_BUFFER).build()); // selected columns - for (Pair p : getExpandedSelection()) - columns.add(builder.copy().add(p.right.id().key).build()); + for (ColumnIdentifier id : selection.regularColumnsToFetch()) + columns.add(builder.copy().add(id.key).build()); } else { @@ -536,13 +511,13 @@ public class SelectStatement implements CQLStatement if (r.isEquality()) { assert r.eqValues.size() == 1; - builder.add(r.eqValues.get(0), Relation.Type.EQ, variables); + builder.add(r.eqValues.get(0).bindAndGet(variables)); } else { Term t = r.bound(b); assert t != null; - return builder.add(t, r.getRelation(eocBound, b), variables).build(); + return builder.add(t.bindAndGet(variables), r.getRelation(eocBound, b)).build(); } } // Means no relation at all or everything was an equal @@ -569,7 +544,7 @@ public class SelectStatement implements CQLStatement { for (Term t : restriction.eqValues) { - ByteBuffer value = t.getByteBuffer(name.type, variables); + ByteBuffer value = t.bindAndGet(variables); if (value.remaining() > 0xFFFF) throw new InvalidRequestException("Index expression values may not be larger than 64K"); expressions.add(new IndexExpression(name.name.key, IndexOperator.EQ, value)); @@ -581,7 +556,7 @@ public class SelectStatement implements CQLStatement { if (restriction.bound(b) != null) { - ByteBuffer value = restriction.bound(b).getByteBuffer(name.type, variables); + ByteBuffer value = restriction.bound(b).bindAndGet(variables); if (value.remaining() > 0xFFFF) throw new InvalidRequestException("Index expression values may not be larger than 64K"); expressions.add(new IndexExpression(name.name.key, restriction.getIndexOperator(b), value)); @@ -592,91 +567,6 @@ public class SelectStatement implements CQLStatement return expressions; } - private List> getExpandedSelection() - { - if (selectedNames.isEmpty()) - { - List> selection = new ArrayList>(); - for (CFDefinition.Name name : cfDef) - selection.add(Pair.create(name, name.name)); - return selection; - } - else - { - return selectedNames; - } - } - - private ByteBuffer value(IColumn c) - { - return (c instanceof CounterColumn) - ? ByteBufferUtil.bytes(CounterContext.instance().total(c.value())) - : c.value(); - } - - private void addReturnValue(ResultSet cqlRows, Selector s, IColumn c) - { - if (c == null || c.isMarkedForDelete()) - { - cqlRows.addColumnValue(null); - return; - } - - if (s.hasFunction()) - { - switch (s.function()) - { - case WRITE_TIME: - cqlRows.addColumnValue(ByteBufferUtil.bytes(c.timestamp())); - return; - case TTL: - if (c instanceof ExpiringColumn) - { - int ttl = ((ExpiringColumn)c).getLocalDeletionTime() - (int) (System.currentTimeMillis() / 1000); - cqlRows.addColumnValue(ByteBufferUtil.bytes(ttl)); - } - else - { - cqlRows.addColumnValue(null); - } - return; - } - } - - addReturnValue(cqlRows, s, value(c)); - } - - private void addReturnValue(ResultSet cqlRows, Selector s, ByteBuffer value) - { - if (value != null && s.hasFunction()) - { - switch (s.function()) - { - case DATE_OF: - value = DateType.instance.decompose(new Date(UUIDGen.unixTimestamp(UUIDGen.getUUID(value)))); - break; - case UNIXTIMESTAMP_OF: - value = ByteBufferUtil.bytes(UUIDGen.unixTimestamp(UUIDGen.getUUID(value))); - break; - case WRITE_TIME: - case TTL: - throw new AssertionError("Cannot return the timestamp or ttl of a value"); - } - } - cqlRows.addColumnValue(value); - } - - private ResultSet createResult(List> selection) - { - List names = new ArrayList(selection.size()); - for (Pair p : selection) - { - names.add(p.right.hasFunction() - ? new ColumnSpecification(p.left.ksName, p.left.cfName, new ColumnIdentifier(p.right.toString(), true), p.right.function().resultType) - : p.left); - } - return new ResultSet(names); - } private Iterable columnsInOrder(final ColumnFamily cf, final List variables) throws InvalidRequestException { @@ -688,7 +578,7 @@ public class SelectStatement implements CQLStatement ColumnNameBuilder builder = cfDef.getColumnNameBuilder(); for (int i = 0; i < columnRestrictions.length - 1; i++) - builder.add(columnRestrictions[i].eqValues.get(0), Relation.Type.EQ, variables); + builder.add(columnRestrictions[i].eqValues.get(0).bindAndGet(variables)); final List requested = new ArrayList(last.eqValues.size()); Iterator iter = last.eqValues.iterator(); @@ -696,7 +586,7 @@ public class SelectStatement implements CQLStatement { Term t = iter.next(); ColumnNameBuilder b = iter.hasNext() ? builder.copy() : builder; - requested.add(b.add(t, Relation.Type.EQ, variables).build()); + requested.add(b.add(t.bindAndGet(variables)).build()); } return new Iterable() @@ -720,9 +610,7 @@ public class SelectStatement implements CQLStatement private ResultSet process(List rows, List variables) throws InvalidRequestException { - List> selection = getExpandedSelection(); - ResultSet cqlRows = createResult(selection); - + Selection.ResultSetBuilder result = selection.resultSetBuilder(); for (org.apache.cassandra.db.Row row : rows) { // Not columns match the query, skip @@ -755,30 +643,29 @@ public class SelectStatement implements CQLStatement else if (sliceRestriction != null) { // For dynamic CF, the column could be out of the requested bounds, filter here - if (!sliceRestriction.isInclusive(Bound.START) && c.name().equals(sliceRestriction.bound(Bound.START).getByteBuffer(cfDef.cfm.comparator, variables))) + if (!sliceRestriction.isInclusive(Bound.START) && c.name().equals(sliceRestriction.bound(Bound.START).bindAndGet(variables))) continue; - if (!sliceRestriction.isInclusive(Bound.END) && c.name().equals(sliceRestriction.bound(Bound.END).getByteBuffer(cfDef.cfm.comparator, variables))) + if (!sliceRestriction.isInclusive(Bound.END) && c.name().equals(sliceRestriction.bound(Bound.END).bindAndGet(variables))) continue; } + result.newRow(); // Respect selection order - for (Pair p : selection) + for (CFDefinition.Name name : selection.getColumnsList()) { - CFDefinition.Name name = p.left; - Selector selector = p.right; switch (name.kind) { case KEY_ALIAS: - addReturnValue(cqlRows, selector, keyComponents[name.position]); + result.add(keyComponents[name.position]); break; case COLUMN_ALIAS: ByteBuffer val = cfDef.isComposite ? (name.position < components.length ? components[name.position] : null) : c.name(); - addReturnValue(cqlRows, selector, val); + result.add(val); break; case VALUE_ALIAS: - addReturnValue(cqlRows, selector, c); + result.add(c); break; case COLUMN_METADATA: // This should not happen for compact CF @@ -805,7 +692,7 @@ public class SelectStatement implements CQLStatement } for (ColumnGroupMap group : builder.groups()) - handleGroup(selection, row.key.key, keyComponents, group, cqlRows); + handleGroup(selection, result, row.key.key, keyComponents, group); } else { @@ -813,24 +700,20 @@ public class SelectStatement implements CQLStatement continue; // Static case: One cqlRow for all columns - // Respect selection order - for (Pair p : selection) + result.newRow(); + for (CFDefinition.Name name : selection.getColumnsList()) { - CFDefinition.Name name = p.left; - Selector selector = p.right; if (name.kind == CFDefinition.Name.Kind.KEY_ALIAS) - { - addReturnValue(cqlRows, selector, keyComponents[name.position]); - continue; - } - - IColumn c = row.cf.getColumn(name.name.key); - addReturnValue(cqlRows, selector, c); + result.add(keyComponents[name.position]); + else + result.add(row.cf.getColumn(name.name.key)); } } } - orderResults(selection, cqlRows); + ResultSet cqlRows = result.build(); + + orderResults(cqlRows); // Internal calls always return columns in the comparator order, even when reverse was set if (isReversed) @@ -844,7 +727,7 @@ public class SelectStatement implements CQLStatement /** * Orders results when multiple keys are selected (using IN) */ - private void orderResults(List> selection, ResultSet cqlRows) + private void orderResults(ResultSet cqlRows) { // There is nothing to do if // a. there are no results, @@ -859,7 +742,7 @@ public class SelectStatement implements CQLStatement if (parameters.orderings.size() == 1) { CFDefinition.Name ordering = cfDef.get(parameters.orderings.keySet().iterator().next()); - Collections.sort(cqlRows.rows, new SingleColumnComparator(getColumnPositionInSelect(selection, ordering), ordering.type)); + Collections.sort(cqlRows.rows, new SingleColumnComparator(getColumnPositionInResultSet(cqlRows, ordering), ordering.type)); return; } @@ -874,18 +757,18 @@ public class SelectStatement implements CQLStatement { CFDefinition.Name orderingColumn = cfDef.get(identifier); types.add(orderingColumn.type); - positions[idx++] = getColumnPositionInSelect(selection, orderingColumn); + positions[idx++] = getColumnPositionInResultSet(cqlRows, orderingColumn); } Collections.sort(cqlRows.rows, new CompositeComparator(types, positions)); } // determine position of column in the select clause - private int getColumnPositionInSelect(List> selection, CFDefinition.Name columnName) + private int getColumnPositionInResultSet(ResultSet rs, CFDefinition.Name columnName) { - for (int i = 0; i < selection.size(); i++) + for (int i = 0; i < rs.metadata.names.size(); i++) { - if (selection.get(i).left.equals(columnName)) + if (rs.metadata.names.get(i).name.equals(columnName.name)) return i; } @@ -914,20 +797,19 @@ public class SelectStatement implements CQLStatement return true; } - private void handleGroup(List> selection, ByteBuffer key, ByteBuffer[] keyComponents, ColumnGroupMap columns, ResultSet cqlRows) + private void handleGroup(Selection selection, Selection.ResultSetBuilder result, ByteBuffer key, ByteBuffer[] keyComponents, ColumnGroupMap columns) throws InvalidRequestException { // Respect requested order - for (Pair p : selection) + result.newRow(); + for (CFDefinition.Name name : selection.getColumnsList()) { - CFDefinition.Name name = p.left; - Selector selector = p.right; switch (name.kind) { case KEY_ALIAS: - addReturnValue(cqlRows, selector, keyComponents[name.position]); + result.add(keyComponents[name.position]); break; case COLUMN_ALIAS: - addReturnValue(cqlRows, selector, columns.getKeyComponent(name.position)); + result.add(columns.getKeyComponent(name.position)); break; case VALUE_ALIAS: // This should not happen for SPARSE @@ -939,16 +821,13 @@ public class SelectStatement implements CQLStatement ByteBuffer value = collection == null ? null : ((CollectionType)name.type).serialize(collection); - addReturnValue(cqlRows, selector, value); + result.add(value); } else { - IColumn c = columns.getSimple(name.name.key); - addReturnValue(cqlRows, selector, c); + result.add(columns.getSimple(name.name.key)); } break; - default: - throw new AssertionError(); } } } @@ -971,10 +850,10 @@ public class SelectStatement implements CQLStatement public static class RawStatement extends CFStatement { private final Parameters parameters; - private final List selectClause; + private final List selectClause; private final List whereClause; - public RawStatement(CFName cfName, Parameters parameters, List selectClause, List whereClause) + public RawStatement(CFName cfName, Parameters parameters, List selectClause, List whereClause) { super(cfName); this.parameters = parameters; @@ -990,48 +869,19 @@ public class SelectStatement implements CQLStatement throw new InvalidRequestException("LIMIT must be strictly positive"); CFDefinition cfDef = cfm.getCfDef(); - SelectStatement stmt = new SelectStatement(cfDef, getBoundsTerms(), parameters); - CFDefinition.Name[] names = new CFDefinition.Name[getBoundsTerms()]; + + ColumnSpecification[] names = new ColumnSpecification[getBoundsTerms()]; IPartitioner partitioner = StorageService.getPartitioner(); // Select clause - if (parameters.isCount) - { - if (!selectClause.isEmpty()) - throw new InvalidRequestException("Only COUNT(*) and COUNT(1) operations are currently supported."); - } - else - { - for (Selector t : selectClause) - { - CFDefinition.Name name = cfDef.get(t.id()); - if (name == null) - throw new InvalidRequestException(String.format("Undefined name %s in selection clause", t.id())); - if (t.hasFunction()) - { - if (name.type.isCollection()) - throw new InvalidRequestException(String.format("Function %s is not supported on collections", t.function())); - switch (t.function()) - { - case WRITE_TIME: - case TTL: - if (name.kind != CFDefinition.Name.Kind.COLUMN_METADATA && name.kind != CFDefinition.Name.Kind.VALUE_ALIAS) - throw new InvalidRequestException(String.format("Cannot use function %s on PRIMARY KEY part %s", t.function(), name)); - break; - case DATE_OF: - case UNIXTIMESTAMP_OF: - if (!(name.type instanceof TimeUUIDType)) - throw new InvalidRequestException(String.format("Function %s is only allowed on timeuuid columns", t.function())); - break; - } - } + if (parameters.isCount && !selectClause.isEmpty()) + throw new InvalidRequestException("Only COUNT(*) and COUNT(1) operations are currently supported."); - if (t.hasKey()) - throw new InvalidRequestException("Selecting a list/map element by index/key is not yet supported"); + Selection selection = selectClause.isEmpty() + ? Selection.wildcard(cfDef) + : Selection.fromSelectors(cfDef, selectClause); - stmt.selectedNames.add(Pair.create(name, t)); - } - } + SelectStatement stmt = new SelectStatement(cfDef, getBoundsTerms(), parameters, selection); /* * WHERE clause. For a given entity, rules are: @@ -1047,41 +897,18 @@ public class SelectStatement implements CQLStatement if (name == null) throw new InvalidRequestException(String.format("Undefined name %s in where clause ('%s')", rel.getEntity(), rel)); - if (rel.operator() == Relation.Type.IN) - { - for (Term value : rel.getInValues()) - { - if (!rel.onToken || value.isToken) - value.validateType(name.toString(), name.type); - else - value.validateType("token of " + name.toString(), StorageService.getPartitioner().getTokenValidator()); - if (value.isBindMarker()) - names[value.bindIndex] = name; - } - } - else - { - Term value = rel.getValue(); - if (!rel.onToken || value.isToken) - value.validateType(name.toString(), name.type); - else - value.validateType("token of " + name.toString(), StorageService.getPartitioner().getTokenValidator()); - if (value.isBindMarker()) - names[value.bindIndex] = name; - } - switch (name.kind) { case KEY_ALIAS: - stmt.keyRestrictions[name.position] = updateRestriction(name, stmt.keyRestrictions[name.position], rel); + stmt.keyRestrictions[name.position] = updateRestriction(name, stmt.keyRestrictions[name.position], rel, names); break; case COLUMN_ALIAS: - stmt.columnRestrictions[name.position] = updateRestriction(name, stmt.columnRestrictions[name.position], rel); + stmt.columnRestrictions[name.position] = updateRestriction(name, stmt.columnRestrictions[name.position], rel, names); break; case VALUE_ALIAS: throw new InvalidRequestException(String.format("Restricting the value of a compact CF (%s) is not supported", name.name)); case COLUMN_METADATA: - stmt.metadataRestrictions.put(name, updateRestriction(name, stmt.metadataRestrictions.get(name), rel)); + stmt.metadataRestrictions.put(name, updateRestriction(name, stmt.metadataRestrictions.get(name), rel, names)); break; } } @@ -1135,6 +962,7 @@ public class SelectStatement implements CQLStatement { CFDefinition.Name cname = iter.next(); Restriction restriction = stmt.keyRestrictions[i]; + if (restriction == null) { if (stmt.onToken) @@ -1235,16 +1063,16 @@ public class SelectStatement implements CQLStatement // If we order an IN query, we'll have to do a manual sort post-query. Currently, this sorting requires that we // have queried the column on which we sort (TODO: we should update it to add the column on which we sort to the one // queried automatically, and then removing it from the resultSet afterwards if needed) - if (stmt.keyIsInRelation && !stmt.selectedNames.isEmpty()) // empty means wildcard was used + if (stmt.keyIsInRelation && !selectClause.isEmpty()) // empty means wildcard was used { for (ColumnIdentifier column : stmt.parameters.orderings.keySet()) { CFDefinition.Name name = cfDef.get(column); boolean hasColumn = false; - for (Pair selectPair : stmt.selectedNames) + for (RawSelector selector : selectClause) { - if (selectPair.left.equals(name)) + if (name.name.equals(selector)) { hasColumn = true; break; @@ -1310,30 +1138,54 @@ public class SelectStatement implements CQLStatement return new ParsedStatement.Prepared(stmt, Arrays.asList(names)); } - Restriction updateRestriction(CFDefinition.Name name, Restriction restriction, Relation newRel) throws InvalidRequestException + Restriction updateRestriction(CFDefinition.Name name, Restriction restriction, Relation newRel, ColumnSpecification[] boundNames) throws InvalidRequestException { - if (newRel.onToken && name.kind != CFDefinition.Name.Kind.KEY_ALIAS) - throw new InvalidRequestException(String.format("The token() function is only supported on the partition key, found on %s", name)); + ColumnSpecification receiver = name; + if (newRel.onToken) + { + if (name.kind != CFDefinition.Name.Kind.KEY_ALIAS) + throw new InvalidRequestException(String.format("The token() function is only supported on the partition key, found on %s", name)); + + receiver = new ColumnSpecification(name.ksName, + name.cfName, + new ColumnIdentifier("partition key token", true), + StorageService.instance.getPartitioner().getTokenValidator()); + } switch (newRel.operator()) { case EQ: - if (restriction != null) - throw new InvalidRequestException(String.format("%s cannot be restricted by more than one relation if it includes an Equal", name)); - restriction = new Restriction(newRel.getValue(), newRel.onToken); + { + if (restriction != null) + throw new InvalidRequestException(String.format("%s cannot be restricted by more than one relation if it includes an Equal", name)); + Term t = newRel.getValue().prepare(receiver); + t.collectMarkerSpecification(boundNames); + restriction = new Restriction(t, newRel.onToken); + } break; case IN: if (restriction != null) throw new InvalidRequestException(String.format("%s cannot be restricted by more than one reation if it includes a IN", name)); - restriction = new Restriction(newRel.getInValues()); + List inValues = new ArrayList(newRel.getInValues().size()); + for (Term.Raw raw : newRel.getInValues()) + { + Term t = raw.prepare(receiver); + t.collectMarkerSpecification(boundNames); + inValues.add(t); + } + restriction = new Restriction(inValues); break; case GT: case GTE: case LT: case LTE: - if (restriction == null) - restriction = new Restriction(newRel.onToken); - restriction.setBound(name.name, newRel.operator(), newRel.getValue()); + { + if (restriction == null) + restriction = new Restriction(newRel.onToken); + Term t = newRel.getValue().prepare(receiver); + t.collectMarkerSpecification(boundNames); + restriction.setBound(name.name, newRel.operator(), t); + } break; } return restriction; @@ -1364,17 +1216,23 @@ public class SelectStatement implements CQLStatement final boolean onToken; - Restriction(List values) + + Restriction(List values, boolean onToken) { this.eqValues = values; this.bounds = null; this.boundInclusive = null; - this.onToken = false; + this.onToken = onToken; + } + + Restriction(List values) + { + this(values, false); } Restriction(Term value, boolean onToken) { - this(Collections.singletonList(value)); + this(Collections.singletonList(value), onToken); } Restriction(boolean onToken) http://git-wip-us.apache.org/repos/asf/cassandra/blob/31e669ab/src/java/org/apache/cassandra/cql3/statements/Selection.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/Selection.java b/src/java/org/apache/cassandra/cql3/statements/Selection.java new file mode 100644 index 0000000..a402453 --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/statements/Selection.java @@ -0,0 +1,401 @@ +/* + * 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.cql3.statements; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import org.apache.cassandra.cql3.*; +import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.cql3.functions.Functions; +import org.apache.cassandra.db.CounterColumn; +import org.apache.cassandra.db.ExpiringColumn; +import org.apache.cassandra.db.IColumn; +import org.apache.cassandra.db.context.CounterContext; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.Int32Type; +import org.apache.cassandra.db.marshal.LongType; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.utils.ByteBufferUtil; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class Selection +{ + private static final Logger logger = LoggerFactory.getLogger(Selection.class); + + private final List columnsList; + private final List metadata; + private final boolean collectTimestamps; + private final boolean collectTTLs; + + protected Selection(List columnsList, List metadata, boolean collectTimestamps, boolean collectTTLs) + { + this.columnsList = columnsList; + this.metadata = metadata; + this.collectTimestamps = collectTimestamps; + this.collectTTLs = collectTTLs; + } + + public static Selection wildcard(CFDefinition cfDef) + { + List all = new ArrayList(); + for (CFDefinition.Name name : cfDef) + all.add(name); + return new SimpleSelection(all); + } + + private static boolean isUsingFunction(List rawSelectors) + { + for (RawSelector rawSelector : rawSelectors) + { + if (!(rawSelector instanceof ColumnIdentifier)) + return true; + } + return false; + } + + private static int addAndGetIndex(CFDefinition.Name name, List l) + { + int idx = l.indexOf(name); + if (idx < 0) + { + idx = l.size(); + l.add(name); + } + return idx; + } + + private static Selector makeSelector(CFDefinition cfDef, RawSelector raw, List names, List metadata) throws InvalidRequestException + { + if (raw instanceof ColumnIdentifier) + { + CFDefinition.Name name = cfDef.get((ColumnIdentifier)raw); + if (name == null) + throw new InvalidRequestException(String.format("Undefined name %s in selection clause", raw)); + if (metadata != null) + metadata.add(name); + return new SimpleSelector(addAndGetIndex(name, names), name.type); + } + else if (raw instanceof RawSelector.WritetimeOrTTL) + { + RawSelector.WritetimeOrTTL tot = (RawSelector.WritetimeOrTTL)raw; + CFDefinition.Name name = cfDef.get(tot.id); + if (name == null) + throw new InvalidRequestException(String.format("Undefined name %s in selection clause", tot.id)); + if (name.kind != CFDefinition.Name.Kind.COLUMN_METADATA && name.kind != CFDefinition.Name.Kind.VALUE_ALIAS) + throw new InvalidRequestException(String.format("Cannot use selection function %s on PRIMARY KEY part %s", tot.isWritetime ? "writeTime" : "ttl", name)); + if (name.type.isCollection()) + throw new InvalidRequestException(String.format("Cannot use selection function %s on collections", tot.isWritetime ? "writeTime" : "ttl")); + + if (metadata != null) + metadata.add(makeWritetimeOrTTLSpec(cfDef, tot)); + return new WritetimeOrTTLSelector(addAndGetIndex(name, names), tot.isWritetime); + } + else + { + RawSelector.WithFunction withFun = (RawSelector.WithFunction)raw; + List args = new ArrayList(withFun.args.size()); + for (RawSelector rawArg : withFun.args) + args.add(makeSelector(cfDef, rawArg, names, null)); + + AbstractType returnType = Functions.getReturnType(withFun.functionName, cfDef.cfm.ksName, cfDef.cfm.cfName); + ColumnSpecification spec = makeFunctionSpec(cfDef, withFun, returnType); + Function fun = Functions.get(withFun.functionName, args, spec); + if (metadata != null) + metadata.add(spec); + return new FunctionSelector(fun, args); + } + } + + private static ColumnSpecification makeWritetimeOrTTLSpec(CFDefinition cfDef, RawSelector.WritetimeOrTTL tot) + { + return new ColumnSpecification(cfDef.cfm.ksName, + cfDef.cfm.cfName, + new ColumnIdentifier(tot.toString(), true), + tot.isWritetime ? LongType.instance : Int32Type.instance); + } + + private static ColumnSpecification makeFunctionSpec(CFDefinition cfDef, RawSelector.WithFunction fun, AbstractType returnType) throws InvalidRequestException + { + if (returnType == null) + throw new InvalidRequestException(String.format("Unknown function %s called in selection clause", fun.functionName)); + + return new ColumnSpecification(cfDef.cfm.ksName, + cfDef.cfm.cfName, + new ColumnIdentifier(fun.toString(), true), + returnType); + } + + public static Selection fromSelectors(CFDefinition cfDef, List rawSelectors) throws InvalidRequestException + { + boolean usesFunction = isUsingFunction(rawSelectors); + + if (usesFunction) + { + List names = new ArrayList(); + List metadata = new ArrayList(rawSelectors.size()); + List selectors = new ArrayList(rawSelectors.size()); + boolean collectTimestamps = false; + boolean collectTTLs = false; + for (RawSelector rawSelector : rawSelectors) + { + Selector selector = makeSelector(cfDef, rawSelector, names, metadata); + selectors.add(selector); + if (selector instanceof WritetimeOrTTLSelector) + { + collectTimestamps |= ((WritetimeOrTTLSelector)selector).isWritetime; + collectTTLs |= !((WritetimeOrTTLSelector)selector).isWritetime; + } + } + return new SelectionWithFunctions(names, metadata, selectors, collectTimestamps, collectTTLs); + } + else + { + List names = new ArrayList(rawSelectors.size()); + for (RawSelector rawSelector : rawSelectors) + { + assert rawSelector instanceof ColumnIdentifier; + CFDefinition.Name name = cfDef.get((ColumnIdentifier)rawSelector); + if (name == null) + throw new InvalidRequestException(String.format("Undefined name %s in selection clause", rawSelector)); + names.add(name); + } + return new SimpleSelection(names); + } + } + + protected abstract List handleRow(ResultSetBuilder rs) throws InvalidRequestException; + + /** + * @return the list of CQL3 "regular" (the "COLUMN_METADATA" ones) column names to fetch. + */ + public List regularColumnsToFetch() + { + List toFetch = new ArrayList(); + for (CFDefinition.Name name : columnsList) + { + if (name.kind == CFDefinition.Name.Kind.COLUMN_METADATA) + toFetch.add(name.name); + } + return toFetch; + } + + /** + * @return the list of CQL3 columns value this SelectionClause needs. + */ + public List getColumnsList() + { + return columnsList; + } + + public ResultSetBuilder resultSetBuilder() + { + return new ResultSetBuilder(); + } + + private static ByteBuffer value(IColumn c) + { + return (c instanceof CounterColumn) + ? ByteBufferUtil.bytes(CounterContext.instance().total(c.value())) + : c.value(); + } + + public class ResultSetBuilder + { + private final ResultSet resultSet; + + /* + * We'll build CQL3 row one by one. + * The currentRow is the values for the (CQL3) columns we've fetched. + * We also collect timestamps and ttls for the case where the writetime and + * ttl functions are used. Note that we might collect timestamp and/or ttls + * we don't care about, but since the array below are allocated just once, + * it doesn't matter performance wise. + */ + List current; + final long[] timestamps; + final int[] ttls; + + private ResultSetBuilder() + { + this.resultSet = new ResultSet(metadata); + this.timestamps = collectTimestamps ? new long[columnsList.size()] : null; + this.ttls = collectTTLs ? new int[columnsList.size()] : null; + } + + public void add(ByteBuffer v) + { + current.add(v); + } + + public void add(IColumn c) + { + current.add(c == null || c.isMarkedForDelete() ? null : value(c)); + if (timestamps != null) + { + timestamps[current.size() - 1] = c.timestamp(); + } + if (ttls != null) + { + int ttl = -1; + if (c instanceof ExpiringColumn) + ttl = ((ExpiringColumn)c).getLocalDeletionTime() - (int) (System.currentTimeMillis() / 1000); + ttls[current.size() - 1] = ttl; + } + } + + public void newRow() throws InvalidRequestException + { + if (current != null) + resultSet.addRow(handleRow(this)); + current = new ArrayList(columnsList.size()); + } + + public ResultSet build() throws InvalidRequestException + { + if (current != null) + { + resultSet.addRow(handleRow(this)); + current = null; + } + return resultSet; + } + } + + // Special cased selection for when no function is used (this save some allocations). + private static class SimpleSelection extends Selection + { + public SimpleSelection(List columnsList) + { + /* + * In theory, even a simple selection could have multiple time the same column, so we + * could filter those duplicate out of columnsList. But since we're very unlikely to + * get much duplicate in practice, it's more efficient not to bother. + */ + super(columnsList, new ArrayList(columnsList), false, false); + } + + protected List handleRow(ResultSetBuilder rs) + { + return rs.current; + } + } + + private interface Selector extends AssignementTestable + { + public ByteBuffer compute(ResultSetBuilder rs) throws InvalidRequestException; + } + + private static class SimpleSelector implements Selector + { + private final int idx; + private final AbstractType type; + + public SimpleSelector(int idx, AbstractType type) + { + this.idx = idx; + this.type = type; + } + + public ByteBuffer compute(ResultSetBuilder rs) + { + return rs.current.get(idx); + } + + public boolean isAssignableTo(ColumnSpecification receiver) + { + return type.equals(receiver.type); + } + } + + private static class FunctionSelector implements Selector + { + private final Function fun; + private final List argSelectors; + + public FunctionSelector(Function fun, List argSelectors) + { + this.fun = fun; + this.argSelectors = argSelectors; + } + + public ByteBuffer compute(ResultSetBuilder rs) throws InvalidRequestException + { + List args = new ArrayList(argSelectors.size()); + for (Selector s : argSelectors) + args.add(s.compute(rs)); + + return fun.execute(args); + } + + public boolean isAssignableTo(ColumnSpecification receiver) + { + return fun.returnType().equals(receiver.type); + } + } + + private static class WritetimeOrTTLSelector implements Selector + { + private final int idx; + private final boolean isWritetime; + + public WritetimeOrTTLSelector(int idx, boolean isWritetime) + { + this.idx = idx; + this.isWritetime = isWritetime; + } + + public ByteBuffer compute(ResultSetBuilder rs) + { + if (isWritetime) + return ByteBufferUtil.bytes(rs.timestamps[idx]); + + int ttl = rs.ttls[idx]; + return ttl > 0 ? ByteBufferUtil.bytes(ttl) : null; + } + + public boolean isAssignableTo(ColumnSpecification receiver) + { + return receiver.type.equals(isWritetime ? LongType.instance : Int32Type.instance); + } + } + + private static class SelectionWithFunctions extends Selection + { + private final List selectors; + + public SelectionWithFunctions(List columnsList, List metadata, List selectors, boolean collectTimestamps, boolean collectTTLs) + { + super(columnsList, metadata, collectTimestamps, collectTTLs); + this.selectors = selectors; + } + + protected List handleRow(ResultSetBuilder rs) throws InvalidRequestException + { + List result = new ArrayList(); + for (Selector selector : selectors) + { + result.add(selector.compute(rs)); + } + return result; + } + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/31e669ab/src/java/org/apache/cassandra/cql3/statements/Selector.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/Selector.java b/src/java/org/apache/cassandra/cql3/statements/Selector.java deleted file mode 100644 index 4f1db00..0000000 --- a/src/java/org/apache/cassandra/cql3/statements/Selector.java +++ /dev/null @@ -1,181 +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.cql3.statements; - -import com.google.common.base.Objects; - -import org.apache.cassandra.cql3.ColumnIdentifier; -import org.apache.cassandra.cql3.Term; -import org.apache.cassandra.db.marshal.*; - -public abstract class Selector -{ - public enum Function - { - WRITE_TIME (LongType.instance), - TTL (Int32Type.instance), - DATE_OF (DateType.instance), - UNIXTIMESTAMP_OF(LongType.instance); - - public final AbstractType resultType; - - private Function(AbstractType resultType) - { - this.resultType = resultType; - } - - @Override - public String toString() - { - switch (this) - { - case WRITE_TIME: - return "writetime"; - case TTL: - return "ttl"; - case DATE_OF: - return "dateof"; - case UNIXTIMESTAMP_OF: - return "unixtimestampof"; - } - throw new AssertionError(); - } - } - - public abstract ColumnIdentifier id(); - - public boolean hasFunction() - { - return false; - } - - public Function function() - { - return null; - } - - public boolean hasKey() - { - return false; - } - - public Term key() - { - return null; - } - - public static class WithFunction extends Selector - { - private final Function function; - private final ColumnIdentifier id; - - public WithFunction(ColumnIdentifier id, Function function) - { - this.id = id; - this.function = function; - } - - public ColumnIdentifier id() - { - return id; - } - - @Override - public boolean hasFunction() - { - return true; - } - - @Override - public Function function() - { - return function; - } - - @Override - public final int hashCode() - { - return Objects.hashCode(function, id); - } - - @Override - public final boolean equals(Object o) - { - if(!(o instanceof WithFunction)) - return false; - Selector that = (WithFunction)o; - return id().equals(that.id()) && function() == that.function(); - } - - @Override - public String toString() - { - return function + "(" + id + ")"; - } - } - - public static class WithKey extends Selector - { - private final ColumnIdentifier id; - private final Term key; - - public WithKey(ColumnIdentifier id, Term key) - { - this.id = id; - this.key = key; - } - - public ColumnIdentifier id() - { - return id; - } - - @Override - public boolean hasKey() - { - return true; - } - - public Term key() - { - return key; - } - - @Override - public final int hashCode() - { - return Objects.hashCode(id, key); - } - - @Override - public final boolean equals(Object o) - { - if(!(o instanceof WithKey)) - return false; - WithKey that = (WithKey)o; - return id().equals(that.id()) && key.equals(that.key); - } - - @Override - public String toString() - { - return id + "[" + key + "]"; - } - } -} http://git-wip-us.apache.org/repos/asf/cassandra/blob/31e669ab/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java index aac4f04..683d24b 100644 --- a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java @@ -20,14 +20,8 @@ package org.apache.cassandra.cql3.statements; import java.nio.ByteBuffer; import java.util.*; -import com.google.common.collect.ArrayListMultimap; - import org.apache.cassandra.cql3.*; import org.apache.cassandra.config.CFMetaData; -import org.apache.cassandra.cql3.operations.ColumnOperation; -import org.apache.cassandra.cql3.operations.Operation; -import org.apache.cassandra.cql3.operations.SetOperation; -import org.apache.cassandra.cql3.operations.PreparedOperation; import org.apache.cassandra.db.*; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.exceptions.*; @@ -44,34 +38,39 @@ import static org.apache.cassandra.thrift.ThriftValidation.validateColumnFamily; public class UpdateStatement extends ModificationStatement { private CFDefinition cfDef; - private final List> columns; - private final List columnNames; - private final List columnOperations; + + // Provided for an UPDATE + private final List> operations; private final List whereClause; - private final ArrayListMultimap processedColumns = ArrayListMultimap.create(); + // Provided for an INSERT + private final List columnNames; + private final List columnValues; + + private final List processedColumns = new ArrayList(); private final Map> processedKeys = new HashMap>(); + private static final Operation setToEmptyOperation = new Constants.Setter(null, new Constants.Value(ByteBufferUtil.EMPTY_BYTE_BUFFER)); + /** * Creates a new UpdateStatement from a column family name, columns map, consistency * level, and key term. * * @param name column family being operated on - * @param columns a map of column name/values pairs + * @param operations a map of column operations to perform * @param whereClause the where clause * @param attrs additional attributes for statement (CL, timestamp, timeToLive) */ public UpdateStatement(CFName name, - List> columns, + List> operations, List whereClause, Attributes attrs) { super(name, attrs); - - this.columns = columns; + this.operations = operations; this.whereClause = whereClause; this.columnNames = null; - this.columnOperations = null; + this.columnValues = null; } /** @@ -81,20 +80,19 @@ public class UpdateStatement extends ModificationStatement * * @param name column family being operated on * @param columnNames list of column names - * @param columnOperations list of column 'set' operations (corresponds to names) + * @param columnValues list of column values (corresponds to names) * @param attrs additional attributes for statement (CL, timestamp, timeToLive) */ public UpdateStatement(CFName name, Attributes attrs, List columnNames, - List columnOperations) + List columnValues) { super(name, attrs); - this.columnNames = columnNames; - this.columnOperations = columnOperations; + this.columnValues = columnValues; + this.operations = null; this.whereClause = null; - this.columns = null; } protected void validateConsistency(ConsistencyLevel cl) throws InvalidRequestException @@ -116,29 +114,23 @@ public class UpdateStatement extends ModificationStatement // Lists SET operation incurs a read. Set toRead = null; - for (Map.Entry entry : processedColumns.entries()) + for (Operation op : processedColumns) { - CFDefinition.Name name = entry.getKey(); - Operation value = entry.getValue(); - - if (!(name.type instanceof ListType)) - continue; - - if (value.requiresRead(name.type)) + if (op.requiresRead()) { if (toRead == null) toRead = new TreeSet(UTF8Type.instance); - toRead.add(name.name.key); + toRead.add(op.columnName.key); } } Map rows = toRead != null ? readRows(keys, builder, toRead, (CompositeType)cfDef.cfm.comparator, local, cl) : null; Collection mutations = new LinkedList(); - UpdateParameters params = new UpdateParameters(variables, getTimestamp(now), getTimeToLive()); + UpdateParameters params = new UpdateParameters(variables, getTimestamp(now), getTimeToLive(), rows); for (ByteBuffer key: keys) - mutations.add(mutationForKey(cfDef, key, builder, params, rows == null ? null : rows.get(key), cl)); + mutations.add(mutationForKey(cfDef, key, builder, params, cl)); return mutations; } @@ -164,7 +156,7 @@ public class UpdateStatement extends ModificationStatement else { assert values.size() == 1; // We only allow IN for row keys so far - builder.add(values.get(0), Relation.Type.EQ, variables); + builder.add(values.get(0).bindAndGet(variables)); } } return firstEmpty; @@ -184,13 +176,15 @@ public class UpdateStatement extends ModificationStatement if (keyBuilder.remainingCount() == 1) { for (Term t : values) - keys.add(keyBuilder.copy().add(t, Relation.Type.EQ, variables).build()); + { + keys.add(keyBuilder.copy().add(t.bindAndGet(variables)).build()); + } } else { if (values.size() > 1) throw new InvalidRequestException("IN is only supported on the last column of the partition key"); - keyBuilder.add(values.get(0), Relation.Type.EQ, variables); + keyBuilder.add(values.get(0).bindAndGet(variables)); } } return keys; @@ -203,7 +197,7 @@ public class UpdateStatement extends ModificationStatement * * @throws InvalidRequestException on the wrong request */ - private IMutation mutationForKey(CFDefinition cfDef, ByteBuffer key, ColumnNameBuilder builder, UpdateParameters params, ColumnGroupMap group, ConsistencyLevel cl) + private IMutation mutationForKey(CFDefinition cfDef, ByteBuffer key, ColumnNameBuilder builder, UpdateParameters params, ConsistencyLevel cl) throws InvalidRequestException { validateKey(key); @@ -232,30 +226,28 @@ public class UpdateStatement extends ModificationStatement if (builder.componentCount() == 0) throw new InvalidRequestException(String.format("Missing PRIMARY KEY part %s", cfDef.columns.values().iterator().next())); - Operation operation; if (cfDef.value == null) { - // No value was defined, we set to the empty value - operation = ColumnOperation.SetToEmpty(); + // compact + no compact value implies there is no column outside the PK. So no operation could + // have passed through validation + assert processedColumns.isEmpty(); + setToEmptyOperation.execute(key, cf, builder.copy(), params); } else { - List operations = processedColumns.get(cfDef.value); - if (operations.isEmpty()) + // compact means we don't have a row marker, so don't accept to set only the PK (Note: we + // could accept it and use an empty value!?) + if (processedColumns.isEmpty()) throw new InvalidRequestException(String.format("Missing mandatory column %s", cfDef.value)); - assert operations.size() == 1; - operation = operations.get(0); + + for (Operation op : processedColumns) + op.execute(key, cf, builder.copy(), params); } - operation.execute(cf, builder.copy(), cfDef.value == null ? null : cfDef.value.type, params, null); } else { - for (Map.Entry entry : processedColumns.entries()) - { - CFDefinition.Name name = entry.getKey(); - Operation op = entry.getValue(); - op.execute(cf, builder.copy().add(name.name.key), name.type, params, group == null || !op.requiresRead(name.type) ? null : group.getCollection(name.name.key)); - } + for (Operation op : processedColumns) + op.execute(key, cf, builder.copy(), params); } return type == Type.COUNTER ? new CounterMutation(rm, cl) : rm; @@ -269,15 +261,15 @@ public class UpdateStatement extends ModificationStatement type = metadata.getDefaultValidator().isCommutative() ? Type.COUNTER : Type.LOGGED; - if (columns == null) + if (operations == null) { // Created from an INSERT if (type == Type.COUNTER) throw new InvalidRequestException("INSERT statement are not allowed on counter tables, use UPDATE instead"); - if (columnNames.size() != columnOperations.size()) - throw new InvalidRequestException("unmatched column names/values"); - if (columnNames.size() < 1) - throw new InvalidRequestException("no columns specified for INSERT"); + if (columnNames.size() != columnValues.size()) + throw new InvalidRequestException("Unmatched column names/values"); + if (columnNames.isEmpty()) + throw new InvalidRequestException("No columns provided to INSERT"); for (int i = 0; i < columnNames.size(); i++) { @@ -285,24 +277,27 @@ public class UpdateStatement extends ModificationStatement if (name == null) throw new InvalidRequestException(String.format("Unknown identifier %s", columnNames.get(i))); - Operation operation = columnOperations.get(i).validateAndAddBoundNames(name, boundNames); + // For UPDATES, the parser validates we don't set the same value twice but we must check it here for INSERT + for (int j = 0; j < i; j++) + if (name.name.equals(columnNames.get(j))) + throw new InvalidRequestException(String.format("Multiple definitions found for column %s", name)); + + Term.Raw value = columnValues.get(i); switch (name.kind) { case KEY_ALIAS: case COLUMN_ALIAS: - if (processedKeys.containsKey(name.name)) + Term t = value.prepare(name); + t.collectMarkerSpecification(boundNames); + if (processedKeys.put(name.name, Collections.singletonList(t)) != null) throw new InvalidRequestException(String.format("Multiple definitions found for PRIMARY KEY part %s", name)); - // We know collection are not accepted for key and column aliases - if (operation.getType() != Operation.Type.COLUMN && operation.getType() != Operation.Type.PREPARED) - throw new InvalidRequestException(String.format("Invalid definition for %s, not a collection type", name)); - processedKeys.put(name.name, operation.getValues()); break; case VALUE_ALIAS: case COLUMN_METADATA: - if (processedColumns.containsKey(name)) - throw new InvalidRequestException(String.format("Multiple definitions found for column %s", name)); - processedColumns.put(name, operation); + Operation operation = new Operation.SetValue(value).prepare(name); + operation.collectMarkerSpecification(boundNames); + processedColumns.add(operation); break; } } @@ -310,32 +305,14 @@ public class UpdateStatement extends ModificationStatement else { // Created from an UPDATE - for (Pair entry : columns) + for (Pair entry : operations) { CFDefinition.Name name = cfDef.get(entry.left); if (name == null) throw new InvalidRequestException(String.format("Unknown identifier %s", entry.left)); - Operation operation = entry.right.validateAndAddBoundNames(name, boundNames); - - switch (operation.getType()) - { - case COUNTER: - if (type != Type.COUNTER) - throw new InvalidRequestException("Invalid counter operation on non-counter table."); - break; - case LIST: - case SET: - case MAP: - case COLUMN: - if (type == Type.COUNTER) - throw new InvalidRequestException("Invalid non-counter operation on counter table."); - break; - case PREPARED: - if (type == Type.COUNTER && !((PreparedOperation)operation).isPotentialCounterOperation()) - throw new InvalidRequestException("Invalid non-counter operation on counter table."); - break; - } + Operation operation = entry.right.prepare(name); + operation.collectMarkerSpecification(boundNames); switch (name.kind) { @@ -344,10 +321,7 @@ public class UpdateStatement extends ModificationStatement throw new InvalidRequestException(String.format("PRIMARY KEY part %s found in SET part", entry.left)); case VALUE_ALIAS: case COLUMN_METADATA: - for (Operation otherOp : processedColumns.get(name)) - if (otherOp.getType() == Operation.Type.COLUMN) - throw new InvalidRequestException(String.format("Multiple definitions found for column %s", name)); - processedColumns.put(name, operation); + processedColumns.add(operation); break; } } @@ -376,27 +350,28 @@ public class UpdateStatement extends ModificationStatement { case KEY_ALIAS: case COLUMN_ALIAS: - List values; + List rawValues; if (rel.operator() == Relation.Type.EQ) - values = Collections.singletonList(rel.getValue()); + rawValues = Collections.singletonList(rel.getValue()); else if (name.kind == CFDefinition.Name.Kind.KEY_ALIAS && rel.operator() == Relation.Type.IN) - values = rel.getInValues(); + rawValues = rel.getInValues(); else - throw new InvalidRequestException(String.format("Invalid operator %s for key %s", rel.operator(), rel.getEntity())); + throw new InvalidRequestException(String.format("Invalid operator %s for PRIMARY KEY part %s", rel.operator(), name)); - if (processed.containsKey(name.name)) - throw new InvalidRequestException(String.format("Multiple definitions found for PRIMARY KEY part %s", name)); - for (Term value : values) + List values = new ArrayList(rawValues.size()); + for (Term.Raw raw : rawValues) { - value.validateType(name.toString(), name.type); - if (value.isBindMarker()) - names[value.bindIndex] = name; + Term t = raw.prepare(name); + t.collectMarkerSpecification(names); + values.add(t); } - processed.put(name.name, values); + + if (processed.put(name.name, values) != null) + throw new InvalidRequestException(String.format("Multiple definitions found for PRIMARY KEY part %s", name)); break; case VALUE_ALIAS: case COLUMN_METADATA: - throw new InvalidRequestException(String.format("PRIMARY KEY part %s found in SET part", rel.getEntity())); + throw new InvalidRequestException(String.format("Non PRIMARY KEY %s found in where clause", name)); } } } @@ -406,7 +381,7 @@ public class UpdateStatement extends ModificationStatement return String.format("UpdateStatement(name=%s, keys=%s, columns=%s, timestamp=%s, timeToLive=%s)", cfName, whereClause, - columns, + operations, isSetTimestamp() ? getTimestamp(-1) : "", getTimeToLive()); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/31e669ab/src/java/org/apache/cassandra/db/SystemTable.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/SystemTable.java b/src/java/org/apache/cassandra/db/SystemTable.java index 629defe..6a38757 100644 --- a/src/java/org/apache/cassandra/db/SystemTable.java +++ b/src/java/org/apache/cassandra/db/SystemTable.java @@ -29,6 +29,7 @@ import com.google.common.base.Function; import com.google.common.collect.HashMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.SetMultimap; +import com.google.common.collect.Sets; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -204,7 +205,7 @@ public class SystemTable return sstable.descriptor.generation; } }); - processInternal(String.format(req, COMPACTION_LOG, compactionId, cfs.table.name, cfs.columnFamily, StringUtils.join(generations.iterator(), ','))); + processInternal(String.format(req, COMPACTION_LOG, compactionId, cfs.table.name, cfs.columnFamily, StringUtils.join(Sets.newHashSet(generations), ','))); forceBlockingFlush(COMPACTION_LOG); return compactionId; } http://git-wip-us.apache.org/repos/asf/cassandra/blob/31e669ab/src/java/org/apache/cassandra/db/marshal/AbstractType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/AbstractType.java b/src/java/org/apache/cassandra/db/marshal/AbstractType.java index 5c4c03e..7b586d5 100644 --- a/src/java/org/apache/cassandra/db/marshal/AbstractType.java +++ b/src/java/org/apache/cassandra/db/marshal/AbstractType.java @@ -24,7 +24,6 @@ import java.util.Map; import java.util.Set; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Term; import org.apache.cassandra.exceptions.SyntaxException; import org.apache.cassandra.db.IColumn; import org.apache.cassandra.db.OnDiskAtom; @@ -150,12 +149,6 @@ public abstract class AbstractType implements Comparator /* validate that the byte array is a valid sequence for the type we are supposed to be comparing */ public abstract void validate(ByteBuffer bytes) throws MarshalException; - /* CQL3 types will actually override this, but we use a default for compatibility sake */ - public Set supportedCQL3Constants() - { - return null; - } - /* Most of our internal type should override that. */ public CQL3Type asCQL3Type() { http://git-wip-us.apache.org/repos/asf/cassandra/blob/31e669ab/src/java/org/apache/cassandra/db/marshal/AsciiType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/AsciiType.java b/src/java/org/apache/cassandra/db/marshal/AsciiType.java index c1762cc..990b3d4 100644 --- a/src/java/org/apache/cassandra/db/marshal/AsciiType.java +++ b/src/java/org/apache/cassandra/db/marshal/AsciiType.java @@ -18,19 +18,14 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; import org.apache.cassandra.cql.jdbc.JdbcAscii; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Term; public class AsciiType extends AbstractType { public static final AsciiType instance = new AsciiType(); - private final Set supportedCQL3Constants = EnumSet.of(Term.Type.STRING); - AsciiType() {} // singleton public String getString(ByteBuffer bytes) @@ -76,11 +71,6 @@ public class AsciiType extends AbstractType } } - public Set supportedCQL3Constants() - { - return supportedCQL3Constants; - } - public CQL3Type asCQL3Type() { return CQL3Type.Native.ASCII; http://git-wip-us.apache.org/repos/asf/cassandra/blob/31e669ab/src/java/org/apache/cassandra/db/marshal/BooleanType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/BooleanType.java b/src/java/org/apache/cassandra/db/marshal/BooleanType.java index 87b1183..0bd1a57 100644 --- a/src/java/org/apache/cassandra/db/marshal/BooleanType.java +++ b/src/java/org/apache/cassandra/db/marshal/BooleanType.java @@ -18,19 +18,15 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; import org.apache.cassandra.cql.jdbc.JdbcBoolean; +import org.apache.cassandra.cql3.Constants; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Term; public class BooleanType extends AbstractType { public static final BooleanType instance = new BooleanType(); - public final Set supportedCQL3Constants = EnumSet.of(Term.Type.BOOLEAN); - BooleanType() {} // singleton public Boolean compose(ByteBuffer bytes) @@ -84,11 +80,6 @@ public class BooleanType extends AbstractType throw new MarshalException(String.format("Expected 1 or 0 byte value (%d)", bytes.remaining())); } - public Set supportedCQL3Constants() - { - return supportedCQL3Constants; - } - public CQL3Type asCQL3Type() { return CQL3Type.Native.BOOLEAN; http://git-wip-us.apache.org/repos/asf/cassandra/blob/31e669ab/src/java/org/apache/cassandra/db/marshal/BytesType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/BytesType.java b/src/java/org/apache/cassandra/db/marshal/BytesType.java index a2ddbf8..1bb2bd2 100644 --- a/src/java/org/apache/cassandra/db/marshal/BytesType.java +++ b/src/java/org/apache/cassandra/db/marshal/BytesType.java @@ -18,12 +18,9 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; import org.apache.cassandra.cql.jdbc.JdbcBytes; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Term; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.Hex; @@ -31,8 +28,6 @@ public class BytesType extends AbstractType { public static final BytesType instance = new BytesType(); - private final Set supportedCQL3Constants = EnumSet.of(Term.Type.HEX); - BytesType() {} // singleton public ByteBuffer compose(ByteBuffer bytes) @@ -88,11 +83,6 @@ public class BytesType extends AbstractType return this == previous || previous == AsciiType.instance || previous == UTF8Type.instance; } - public Set supportedCQL3Constants() - { - return supportedCQL3Constants; - } - public CQL3Type asCQL3Type() { return CQL3Type.Native.BLOB; http://git-wip-us.apache.org/repos/asf/cassandra/blob/31e669ab/src/java/org/apache/cassandra/db/marshal/CollectionType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/CollectionType.java b/src/java/org/apache/cassandra/db/marshal/CollectionType.java index b448f1b..d330e4c 100644 --- a/src/java/org/apache/cassandra/db/marshal/CollectionType.java +++ b/src/java/org/apache/cassandra/db/marshal/CollectionType.java @@ -93,7 +93,7 @@ public abstract class CollectionType extends AbstractType } // Utilitary method - protected ByteBuffer pack(List buffers, int elements, int size) + protected static ByteBuffer pack(List buffers, int elements, int size) { ByteBuffer result = ByteBuffer.allocate(2 + size); result.putShort((short)elements); @@ -105,6 +105,13 @@ public abstract class CollectionType extends AbstractType return (ByteBuffer)result.flip(); } + public static ByteBuffer pack(List buffers, int elements) + { + int size = 0; + for (ByteBuffer bb : buffers) + size += 2 + bb.remaining(); + return pack(buffers, elements, size); + } public CQL3Type asCQL3Type() { http://git-wip-us.apache.org/repos/asf/cassandra/blob/31e669ab/src/java/org/apache/cassandra/db/marshal/CompositeType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/CompositeType.java b/src/java/org/apache/cassandra/db/marshal/CompositeType.java index fb80906..2d7f4fc 100644 --- a/src/java/org/apache/cassandra/db/marshal/CompositeType.java +++ b/src/java/org/apache/cassandra/db/marshal/CompositeType.java @@ -235,14 +235,12 @@ public class CompositeType extends AbstractCompositeType this.serializedSize = b.serializedSize; } - public Builder add(Term t, Relation.Type op, List variables) throws InvalidRequestException + public Builder add(ByteBuffer buffer, Relation.Type op) { if (components.size() >= composite.types.size()) throw new IllegalStateException("Composite column is already fully constructed"); int current = components.size(); - AbstractType currentType = composite.types.get(current); - ByteBuffer buffer = t.getByteBuffer(currentType, variables); components.add(buffer); /* @@ -272,13 +270,7 @@ public class CompositeType extends AbstractCompositeType public Builder add(ByteBuffer bb) { - int current = components.size(); - if (current >= composite.types.size()) - throw new IllegalStateException("Composite column is already fully constructed"); - - components.add(bb); - endOfComponents[current] = (byte) 0; - return this; + return add(bb, Relation.Type.EQ); } public int componentCount() http://git-wip-us.apache.org/repos/asf/cassandra/blob/31e669ab/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java b/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java index 8cd2457..1421476 100644 --- a/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java +++ b/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java @@ -18,11 +18,9 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; +import org.apache.cassandra.cql3.Constants; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Term; import org.apache.cassandra.db.*; import org.apache.cassandra.utils.ByteBufferUtil; @@ -30,8 +28,6 @@ public class CounterColumnType extends AbstractCommutativeType { public static final CounterColumnType instance = new CounterColumnType(); - private final Set supportedCQL3Constants = EnumSet.of(Term.Type.INTEGER); - CounterColumnType() {} // singleton public int compare(ByteBuffer o1, ByteBuffer o2) @@ -66,11 +62,6 @@ public class CounterColumnType extends AbstractCommutativeType throw new MarshalException(String.format("Expected 8 or 0 byte long (%d)", bytes.remaining())); } - public Set supportedCQL3Constants() - { - return supportedCQL3Constants; - } - public CQL3Type asCQL3Type() { return CQL3Type.Native.COUNTER; http://git-wip-us.apache.org/repos/asf/cassandra/blob/31e669ab/src/java/org/apache/cassandra/db/marshal/DateType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/DateType.java b/src/java/org/apache/cassandra/db/marshal/DateType.java index 0da1af5..ac27ba6 100644 --- a/src/java/org/apache/cassandra/db/marshal/DateType.java +++ b/src/java/org/apache/cassandra/db/marshal/DateType.java @@ -23,12 +23,10 @@ import java.nio.ByteBuffer; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.EnumSet; -import java.util.Set; import org.apache.cassandra.cql.jdbc.JdbcDate; +import org.apache.cassandra.cql3.Constants; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Term; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.commons.lang.time.DateUtils; @@ -39,8 +37,6 @@ public class DateType extends AbstractType static final String DEFAULT_FORMAT = iso8601Patterns[3]; static final SimpleDateFormat FORMATTER = new SimpleDateFormat(DEFAULT_FORMAT); - private final Set supportedCQL3Constants = EnumSet.of(Term.Type.STRING, Term.Type.INTEGER); - DateType() {} // singleton public Date compose(ByteBuffer bytes) @@ -130,11 +126,6 @@ public class DateType extends AbstractType throw new MarshalException(String.format("Expected 8 or 0 byte long for date (%d)", bytes.remaining())); } - public Set supportedCQL3Constants() - { - return supportedCQL3Constants; - } - public CQL3Type asCQL3Type() { return CQL3Type.Native.TIMESTAMP; http://git-wip-us.apache.org/repos/asf/cassandra/blob/31e669ab/src/java/org/apache/cassandra/db/marshal/DecimalType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/DecimalType.java b/src/java/org/apache/cassandra/db/marshal/DecimalType.java index 75e179e..825f208 100644 --- a/src/java/org/apache/cassandra/db/marshal/DecimalType.java +++ b/src/java/org/apache/cassandra/db/marshal/DecimalType.java @@ -19,20 +19,15 @@ package org.apache.cassandra.db.marshal; import java.math.BigDecimal; import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; import org.apache.cassandra.cql.jdbc.JdbcDecimal; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Term; import org.apache.cassandra.utils.ByteBufferUtil; public class DecimalType extends AbstractType { public static final DecimalType instance = new DecimalType(); - private final Set supportedCQL3Constants = EnumSet.of(Term.Type.INTEGER, Term.Type.FLOAT); - DecimalType() {} // singleton public int compare(ByteBuffer bb0, ByteBuffer bb1) @@ -92,11 +87,6 @@ public class DecimalType extends AbstractType // no useful check for invalid decimals. } - public Set supportedCQL3Constants() - { - return supportedCQL3Constants; - } - public CQL3Type asCQL3Type() { return CQL3Type.Native.DECIMAL; http://git-wip-us.apache.org/repos/asf/cassandra/blob/31e669ab/src/java/org/apache/cassandra/db/marshal/DoubleType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/DoubleType.java b/src/java/org/apache/cassandra/db/marshal/DoubleType.java index 23854bd..f2c7378 100644 --- a/src/java/org/apache/cassandra/db/marshal/DoubleType.java +++ b/src/java/org/apache/cassandra/db/marshal/DoubleType.java @@ -18,20 +18,15 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; import org.apache.cassandra.cql.jdbc.JdbcDouble; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Term; import org.apache.cassandra.utils.ByteBufferUtil; public class DoubleType extends AbstractType { public static final DoubleType instance = new DoubleType(); - private final Set supportedCQL3Constants = EnumSet.of(Term.Type.INTEGER, Term.Type.FLOAT); - DoubleType() {} // singleton public Double compose(ByteBuffer bytes) @@ -95,11 +90,6 @@ public class DoubleType extends AbstractType throw new MarshalException(String.format("Expected 8 or 0 byte value for a double (%d)", bytes.remaining())); } - public Set supportedCQL3Constants() - { - return supportedCQL3Constants; - } - public CQL3Type asCQL3Type() { return CQL3Type.Native.DOUBLE;