Allow accessing column timestamp and ttl in CQL3 patch by slebresne; reviewed by xedin for CASSANDRA-4217
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/f77cd113 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/f77cd113 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/f77cd113 Branch: refs/heads/cassandra-1.1 Commit: f77cd11373a85c4136e76f004c5bf8c45e875f09 Parents: a4f06c2 Author: Sylvain Lebresne <sylv...@datastax.com> Authored: Thu May 24 16:42:01 2012 +0200 Committer: Sylvain Lebresne <sylv...@datastax.com> Committed: Thu May 24 16:42:01 2012 +0200 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../apache/cassandra/cql3/ColumnIdentifier.java | 19 ++- src/java/org/apache/cassandra/cql3/Cql.g | 21 ++- .../cassandra/cql3/statements/SelectStatement.java | 127 +++++++++++---- .../apache/cassandra/cql3/statements/Selector.java | 98 +++++++++++ 5 files changed, 225 insertions(+), 41 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/f77cd113/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 55a34ef..487f388 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -61,6 +61,7 @@ * rename stress to cassandra-stress for saner packaging (CASSANDRA-4256) * Fix exception on colum metadata with non-string comparator (CASSANDRA-4269) * Check for unknown/invalid compression options (CASSANDRA-4266) + * (cql3) Adds simple access to column timestamp and ttl (CASSANDRA-4217) Merged from 1.0: * Fix super columns bug where cache is not updated (CASSANDRA-4190) * fix maxTimestamp to include row tombstones (CASSANDRA-4116) http://git-wip-us.apache.org/repos/asf/cassandra/blob/f77cd113/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java b/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java index 337e619..557e420 100644 --- a/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java +++ b/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java @@ -25,10 +25,12 @@ import java.nio.ByteBuffer; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.utils.ByteBufferUtil; +import org.apache.cassandra.cql3.statements.Selector; + /** * Represents an identifer for a CQL column definition. */ -public class ColumnIdentifier implements Comparable<ColumnIdentifier> +public class ColumnIdentifier implements Comparable<ColumnIdentifier>, Selector { public final ByteBuffer key; private final String text; @@ -70,4 +72,19 @@ public class ColumnIdentifier implements Comparable<ColumnIdentifier> { return key.compareTo(other.key); } + + public ColumnIdentifier id() + { + return this; + } + + public boolean hasFunction() + { + return false; + } + + public Selector.Function function() + { + return null; + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/f77cd113/src/java/org/apache/cassandra/cql3/Cql.g ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Cql.g b/src/java/org/apache/cassandra/cql3/Cql.g index 6e6240f..5123cbb 100644 --- a/src/java/org/apache/cassandra/cql3/Cql.g +++ b/src/java/org/apache/cassandra/cql3/Cql.g @@ -179,14 +179,21 @@ selectStatement returns [SelectStatement.RawStatement expr] } ; -selectClause returns [List<ColumnIdentifier> expr] - : ids=cidentList { $expr = ids; } - | '\*' { $expr = Collections.<ColumnIdentifier>emptyList();} +selectClause returns [List<Selector> expr] + : t1=selector { $expr = new ArrayList<Selector>(); $expr.add(t1); } (',' tN=selector { $expr.add(tN); })* + | '\*' { $expr = Collections.<Selector>emptyList();} ; -selectCountClause returns [List<ColumnIdentifier> expr] - : c=selectClause { $expr = c; } - | i=INTEGER { if (!i.getText().equals("1")) addRecognitionError("Only COUNT(1) is supported, got COUNT(" + i.getText() + ")"); $expr = Collections.<ColumnIdentifier>emptyList();} +selector returns [Selector s] + : c=cident { $s = c; } + | K_WRITETIME '(' c=cident ')' { $s = new Selector.WithFunction(c, Selector.Function.WRITE_TIME); } + | K_TTL '(' c=cident ')' { $s = new Selector.WithFunction(c, Selector.Function.TTL); } + ; + +selectCountClause returns [List<Selector> expr] + : ids=cidentList { $expr = new ArrayList<Selector>(ids); } + | '\*' { $expr = Collections.<Selector>emptyList();} + | i=INTEGER { if (!i.getText().equals("1")) addRecognitionError("Only COUNT(1) is supported, got COUNT(" + i.getText() + ")"); $expr = Collections.<Selector>emptyList();} ; whereClause returns [List<Relation> clause] @@ -547,6 +554,7 @@ unreserved_keyword returns [String str] | K_STORAGE | K_TYPE | K_VALUES + | K_WRITETIME ) { $str = $k.text; } | t=native_type { $str = t; } ; @@ -622,6 +630,7 @@ K_VARCHAR: V A R C H A R; K_VARINT: V A R I N T; K_TIMEUUID: T I M E U U I D; K_TOKEN: T O K E N; +K_WRITETIME: W R I T E T I M E; // Case-insensitive alpha characters fragment A: ('a'|'A'); http://git-wip-us.apache.org/repos/asf/cassandra/blob/f77cd113/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 35cb943..d7089f9 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@ -33,6 +33,7 @@ import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.db.IColumn; import org.apache.cassandra.db.CounterColumn; import org.apache.cassandra.db.ColumnFamily; +import org.apache.cassandra.db.ExpiringColumn; import org.apache.cassandra.db.RangeSliceCommand; import org.apache.cassandra.db.ReadCommand; import org.apache.cassandra.db.Row; @@ -44,6 +45,8 @@ import org.apache.cassandra.db.context.CounterContext; import org.apache.cassandra.db.filter.QueryPath; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.CompositeType; +import org.apache.cassandra.db.marshal.Int32Type; +import org.apache.cassandra.db.marshal.LongType; import org.apache.cassandra.db.marshal.ReversedType; import org.apache.cassandra.db.marshal.TypeParser; import org.apache.cassandra.dht.*; @@ -81,7 +84,7 @@ public class SelectStatement implements CQLStatement private final int boundTerms; public final CFDefinition cfDef; public final Parameters parameters; - private final List<Pair<CFDefinition.Name, ColumnIdentifier>> selectedNames = new ArrayList<Pair<CFDefinition.Name, ColumnIdentifier>>(); // empty => wildcard + private final List<Pair<CFDefinition.Name, Selector>> selectedNames = new ArrayList<Pair<CFDefinition.Name, Selector>>(); // empty => wildcard private Restriction keyRestriction; private final Restriction[] columnRestrictions; @@ -551,13 +554,13 @@ public class SelectStatement implements CQLStatement return expressions; } - private List<Pair<CFDefinition.Name, ColumnIdentifier>> getExpandedSelection() + private List<Pair<CFDefinition.Name, Selector>> getExpandedSelection() { if (selectedNames.isEmpty()) { - List<Pair<CFDefinition.Name, ColumnIdentifier>> selection = new ArrayList<Pair<CFDefinition.Name, ColumnIdentifier>>(); + List<Pair<CFDefinition.Name, Selector>> selection = new ArrayList<Pair<CFDefinition.Name, Selector>>(); for (CFDefinition.Name name : cfDef) - selection.add(Pair.create(name, name.name)); + selection.add(Pair.<CFDefinition.Name, Selector>create(name, name.name)); return selection; } else @@ -573,11 +576,61 @@ public class SelectStatement implements CQLStatement : c.value(); } - private void addToSchema(CqlMetadata schema, Pair<CFDefinition.Name, ColumnIdentifier> p) + private Column makeReturnColumn(Selector s, IColumn c) { - ByteBuffer nameAsRequested = p.right.key; - schema.name_types.put(nameAsRequested, TypeParser.getShortName(cfDef.getNameComparatorForResultSet(p.left))); - schema.value_types.put(nameAsRequested, TypeParser.getShortName(p.left.type)); + Column cqlCol; + if (s.hasFunction()) + { + cqlCol = new Column(ByteBufferUtil.bytes(s.toString())); + if (c == null || c.isMarkedForDelete()) + return cqlCol; + + switch (s.function()) + { + case WRITE_TIME: + cqlCol.setValue(ByteBufferUtil.bytes(c.timestamp())); + break; + case TTL: + if (c instanceof ExpiringColumn) + { + int ttl = ((ExpiringColumn)c).getLocalDeletionTime() - (int) (System.currentTimeMillis() / 1000); + cqlCol.setValue(ByteBufferUtil.bytes(ttl)); + } + break; + } + } + else + { + cqlCol = new Column(s.id().key); + if (c == null || c.isMarkedForDelete()) + return cqlCol; + cqlCol.setValue(value(c)).setTimestamp(c.timestamp()); + } + return cqlCol; + } + + private void addToSchema(CqlMetadata schema, Pair<CFDefinition.Name, Selector> p) + { + if (p.right.hasFunction()) + { + ByteBuffer nameAsRequested = ByteBufferUtil.bytes(p.right.toString()); + schema.name_types.put(nameAsRequested, TypeParser.getShortName(cfDef.definitionType)); + switch (p.right.function()) + { + case WRITE_TIME: + schema.value_types.put(nameAsRequested, TypeParser.getShortName(LongType.instance)); + break; + case TTL: + schema.value_types.put(nameAsRequested, TypeParser.getShortName(Int32Type.instance)); + break; + } + } + else + { + ByteBuffer nameAsRequested = p.right.id().key; + schema.name_types.put(nameAsRequested, TypeParser.getShortName(cfDef.getNameComparatorForResultSet(p.left))); + schema.value_types.put(nameAsRequested, TypeParser.getShortName(p.left.type)); + } } private Iterable<IColumn> columnsInOrder(final ColumnFamily cf, final List<ByteBuffer> variables) throws InvalidRequestException @@ -623,11 +676,11 @@ public class SelectStatement implements CQLStatement private List<CqlRow> process(List<Row> rows, CqlMetadata schema, List<ByteBuffer> variables) throws InvalidRequestException { List<CqlRow> cqlRows = new ArrayList<CqlRow>(); - List<Pair<CFDefinition.Name, ColumnIdentifier>> selection = getExpandedSelection(); + List<Pair<CFDefinition.Name, Selector>> selection = getExpandedSelection(); List<Column> thriftColumns = null; // Add schema only once - for (Pair<CFDefinition.Name, ColumnIdentifier> p : selection) + for (Pair<CFDefinition.Name, Selector> p : selection) addToSchema(schema, p); for (org.apache.cassandra.db.Row row : rows) @@ -662,18 +715,21 @@ public class SelectStatement implements CQLStatement } // Respect selection order - for (Pair<CFDefinition.Name, ColumnIdentifier> p : selection) + for (Pair<CFDefinition.Name, Selector> p : selection) { CFDefinition.Name name = p.left; - ByteBuffer nameAsRequested = p.right.key; + Selector selector = p.right; - Column col = new Column(nameAsRequested); + addToSchema(schema, p); + Column col; switch (name.kind) { case KEY_ALIAS: + col = new Column(selector.id().key); col.setValue(row.key.key).setTimestamp(-1L); break; case COLUMN_ALIAS: + col = new Column(selector.id().key); col.setTimestamp(c.timestamp()); if (cfDef.isComposite) { @@ -688,11 +744,13 @@ public class SelectStatement implements CQLStatement } break; case VALUE_ALIAS: - col.setValue(value(c)).setTimestamp(c.timestamp()); + col = makeReturnColumn(selector, c); break; case COLUMN_METADATA: // This should not happen for compact CF throw new AssertionError(); + default: + throw new AssertionError(); } thriftColumns.add(col); } @@ -737,22 +795,19 @@ public class SelectStatement implements CQLStatement thriftColumns = new ArrayList<Column>(selection.size()); // Respect selection order - for (Pair<CFDefinition.Name, ColumnIdentifier> p : selection) + for (Pair<CFDefinition.Name, Selector> p : selection) { CFDefinition.Name name = p.left; - ByteBuffer nameAsRequested = p.right.key; + Selector selector = p.right; if (name.kind == CFDefinition.Name.Kind.KEY_ALIAS) { - thriftColumns.add(new Column(nameAsRequested).setValue(row.key.key).setTimestamp(-1L)); + thriftColumns.add(new Column(selector.id().key).setValue(row.key.key).setTimestamp(-1L)); continue; } IColumn c = row.cf.getColumn(name.name.key); - Column col = new Column(name.name.key); - if (c != null && !c.isMarkedForDelete()) - col.setValue(value(c)).setTimestamp(c.timestamp()); - thriftColumns.add(col); + thriftColumns.add(makeReturnColumn(selector, c)); } cqlRows.add(new CqlRow(row.key.key, thriftColumns)); } @@ -790,23 +845,25 @@ public class SelectStatement implements CQLStatement return true; } - private CqlRow handleGroup(List<Pair<CFDefinition.Name, ColumnIdentifier>> selection, ByteBuffer key, ByteBuffer[] components, Map<ByteBuffer, IColumn> columns, CqlMetadata schema) + private CqlRow handleGroup(List<Pair<CFDefinition.Name, Selector>> selection, ByteBuffer key, ByteBuffer[] components, Map<ByteBuffer, IColumn> columns, CqlMetadata schema) { List<Column> thriftColumns = new ArrayList<Column>(selection.size()); // Respect requested order - for (Pair<CFDefinition.Name, ColumnIdentifier> p : selection) + for (Pair<CFDefinition.Name, Selector> p : selection) { CFDefinition.Name name = p.left; - ByteBuffer nameAsRequested = p.right.key; + Selector selector = p.right; - Column col = new Column(nameAsRequested); + Column col; switch (name.kind) { case KEY_ALIAS: + col = new Column(selector.id().key); col.setValue(key).setTimestamp(-1L); break; case COLUMN_ALIAS: + col = new Column(selector.id().key); col.setValue(components[name.position]); col.setTimestamp(-1L); break; @@ -815,10 +872,10 @@ public class SelectStatement implements CQLStatement throw new AssertionError(); case COLUMN_METADATA: IColumn c = columns.get(name.name.key); - // We already have excluded deleted columns - if (c != null) - col.setValue(value(c)).setTimestamp(c.timestamp()); + col = makeReturnColumn(selector, c); break; + default: + throw new AssertionError(); } thriftColumns.add(col); } @@ -828,10 +885,10 @@ public class SelectStatement implements CQLStatement public static class RawStatement extends CFStatement { private final Parameters parameters; - private final List<ColumnIdentifier> selectClause; + private final List<Selector> selectClause; private final List<Relation> whereClause; - public RawStatement(CFName cfName, Parameters parameters, List<ColumnIdentifier> selectClause, List<Relation> whereClause) + public RawStatement(CFName cfName, Parameters parameters, List<Selector> selectClause, List<Relation> whereClause) { super(cfName); this.parameters = parameters; @@ -859,12 +916,14 @@ public class SelectStatement implements CQLStatement } else { - for (ColumnIdentifier t : selectClause) + for (Selector t : selectClause) { - CFDefinition.Name name = cfDef.get(t); + CFDefinition.Name name = cfDef.get(t.id()); if (name == null) - throw new InvalidRequestException(String.format("Undefined name %s in selection clause", t)); - // Keeping the case (as in 'case sensitive') of the input name for the resultSet + throw new InvalidRequestException(String.format("Undefined name %s in selection clause", t.id())); + if (t.hasFunction() && 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)); + stmt.selectedNames.add(Pair.create(name, t)); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/f77cd113/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 new file mode 100644 index 0000000..21105c0 --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/statements/Selector.java @@ -0,0 +1,98 @@ +/* + * 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; + +public interface Selector +{ + public enum Function + { + WRITE_TIME, TTL; + + @Override + public String toString() + { + switch (this) + { + case WRITE_TIME: + return "writetime"; + case TTL: + return "ttl"; + } + throw new AssertionError(); + } + } + + public ColumnIdentifier id(); + public boolean hasFunction(); + public Function function(); + + public static class WithFunction implements 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 + ")"; + } + } +}