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 + ")";
+        }
+    }
+}

Reply via email to