Author: jbellis
Date: Sat Aug 21 11:37:04 2010
New Revision: 987728

URL: http://svn.apache.org/viewvc?rev=987728&view=rev
Log:
allow index expressions against columns that are not part of the SlicePredicate.
patch by jbellis; reviewed by Nate McCall for CASSANDRA-1410

Modified:
    cassandra/trunk/CHANGES.txt
    cassandra/trunk/src/java/org/apache/cassandra/db/ColumnFamilyStore.java
    cassandra/trunk/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java

Modified: cassandra/trunk/CHANGES.txt
URL: 
http://svn.apache.org/viewvc/cassandra/trunk/CHANGES.txt?rev=987728&r1=987727&r2=987728&view=diff
==============================================================================
--- cassandra/trunk/CHANGES.txt (original)
+++ cassandra/trunk/CHANGES.txt Sat Aug 21 11:37:04 2010
@@ -24,6 +24,8 @@ dev
  * use JNA, if present, to take snapshots (CASSANDRA-1371)
  * truncate hints if starting 0.7 for the first time (CASSANDRA-1414)
  * fix FD leak in single-row slicepredicate queries (CASSANDRA-1416)
+ * allow index expressions against columns that are not part of the 
+   SlicePredicate (CASSANDRA-1410)
 
 
 0.7-beta1

Modified: 
cassandra/trunk/src/java/org/apache/cassandra/db/ColumnFamilyStore.java
URL: 
http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/db/ColumnFamilyStore.java?rev=987728&r1=987727&r2=987728&view=diff
==============================================================================
--- cassandra/trunk/src/java/org/apache/cassandra/db/ColumnFamilyStore.java 
(original)
+++ cassandra/trunk/src/java/org/apache/cassandra/db/ColumnFamilyStore.java Sat 
Aug 21 11:37:04 2010
@@ -897,7 +897,12 @@ public class ColumnFamilyStore implement
      */
     public ColumnFamily getColumnFamily(QueryFilter filter)
     {
-        return getColumnFamily(filter, (int) (System.currentTimeMillis() / 
1000) - metadata.gcGraceSeconds);
+        return getColumnFamily(filter, gcBefore());
+    }
+
+    private int gcBefore()
+    {
+        return (int) (System.currentTimeMillis() / 1000) - 
metadata.gcGraceSeconds;
     }
 
     private ColumnFamily cacheRow(DecoratedKey key)
@@ -1136,15 +1141,68 @@ public class ColumnFamilyStore implement
 
     public List<Row> scan(IndexClause clause, AbstractBounds range, IFilter 
dataFilter)
     {
+        // Start with the most-restrictive indexed clause, then apply 
remaining clauses
+        // to each row matching that clause.
         // TODO: allow merge join instead of just one index + loop
-        IndexExpression first = highestSelectivityPredicate(clause);
-        ColumnFamilyStore indexCFS = 
getIndexedColumnFamilyStore(first.column_name);
+        IndexExpression primary = highestSelectivityPredicate(clause);
+        ColumnFamilyStore indexCFS = 
getIndexedColumnFamilyStore(primary.column_name);
         assert indexCFS != null;
-        DecoratedKey indexKey = indexCFS.partitioner.decorateKey(first.value);
+        DecoratedKey indexKey = 
indexCFS.partitioner.decorateKey(primary.value);
+
+        // if the slicepredicate doesn't contain all the columns for which we 
have expressions to evaluate,
+        // it needs to be expanded to include those too
+        IFilter firstFilter = dataFilter;
+        NamesQueryFilter extraFilter = null;
+        if (clause.expressions.size() > 1)
+        {
+            if (dataFilter instanceof SliceQueryFilter)
+            {
+                // if we have a high chance of getting all the columns in a 
single index slice, do that.
+                // otherwise, create an extraFilter to fetch by name the 
columns referenced by the additional expressions.
+                if (getMaxRowSize() < DatabaseDescriptor.getColumnIndexSize())
+                {
+                    firstFilter = new 
SliceQueryFilter(ArrayUtils.EMPTY_BYTE_ARRAY,
+                                                       
ArrayUtils.EMPTY_BYTE_ARRAY,
+                                                       ((SliceQueryFilter) 
dataFilter).reversed,
+                                                       Integer.MAX_VALUE);
+                }
+                else
+                {
+                    SortedSet<byte[]> columns = new 
TreeSet<byte[]>(getComparator());
+                    for (IndexExpression expr : clause.expressions)
+                    {
+                        if (expr == primary)
+                            continue;
+                        columns.add(expr.column_name);
+                    }
+                    extraFilter = new NamesQueryFilter(columns);
+                }
+            }
+            else
+            {
+                // just add in columns that are not part of the resultset
+                assert dataFilter instanceof NamesQueryFilter;
+                SortedSet<byte[]> columns = new 
TreeSet<byte[]>(getComparator());
+                for (IndexExpression expr : clause.expressions)
+                {
+                    if (expr == primary || ((NamesQueryFilter) 
dataFilter).columns.contains(expr.column_name))
+                        continue;
+                    columns.add(expr.column_name);
+                }
+                if (columns.size() > 0)
+                {
+                    columns.addAll(((NamesQueryFilter) dataFilter).columns);
+                    firstFilter = new NamesQueryFilter(columns);
+                }
+            }
+        }
 
         List<Row> rows = new ArrayList<Row>();
         byte[] startKey = clause.start_key;
-        
+        QueryPath path = new QueryPath(columnFamily);
+
+        // fetch row keys matching the primary expression, fetch the slice 
predicate for each
+        // and filter by remaining expressions.  repeat until finished w/ 
assigned range or index row is exhausted.
         outer:
         while (true)
         {
@@ -1176,9 +1234,38 @@ public class ColumnFamilyStore implement
                     break outer;
                 if (!range.contains(dk.token))
                     continue;
-                ColumnFamily data = getColumnFamily(new QueryFilter(dk, new 
QueryPath(columnFamily), dataFilter));
-                if (satisfies(data, clause, first))
+
+                // get the row columns requested, and additional columns for 
the expressions if necessary
+                ColumnFamily data = getColumnFamily(new QueryFilter(dk, path, 
firstFilter));
+                if (extraFilter != null)
+                {
+                    // we might have gotten the expression columns in with the 
main data slice, but
+                    // we can't know for sure until that slice is done.  So, 
we'll do the extra query
+                    // if we go through and any expression columns are not 
present.
+                    for (IndexExpression expr : clause.expressions)
+                    {
+                        if (expr != primary && 
data.getColumn(expr.column_name) == null)
+                        {
+                            data.addAll(getColumnFamily(new QueryFilter(dk, 
path, extraFilter)));
+                            break;
+                        }
+                    }
+                }
+
+                if (satisfies(data, clause, primary))
+                {
+                    // cut the resultset back to what was requested, if 
necessary
+                    if (firstFilter != dataFilter)
+                    {
+                        ColumnFamily expandedData = data;
+                        data = expandedData.cloneMeShallow();
+                        IColumnIterator iter = 
dataFilter.getMemtableColumnIterator(expandedData, dk, getComparator());
+                        new QueryFilter(dk, path, 
dataFilter).collectCollatedColumns(data, iter, gcBefore());
+                    }
+
                     rows.add(new Row(dk, data));
+                }
+
                 if (rows.size() == clause.count)
                     break outer;
             }

Modified: 
cassandra/trunk/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java
URL: 
http://svn.apache.org/viewvc/cassandra/trunk/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java?rev=987728&r1=987727&r2=987728&view=diff
==============================================================================
--- 
cassandra/trunk/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java 
(original)
+++ 
cassandra/trunk/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java 
Sat Aug 21 11:37:04 2010
@@ -179,6 +179,7 @@ public class ColumnFamilyStoreTest exten
         rm.add(new QueryPath("Indexed1", null, "birthdate".getBytes("UTF8")), 
FBUtilities.toByteArray(3L), new TimestampClock(0));
         rm.apply();
 
+        // basic single-expression query
         IndexExpression expr = new 
IndexExpression("birthdate".getBytes("UTF8"), IndexOperator.EQ, 
FBUtilities.toByteArray(1L));
         IndexClause clause = new IndexClause(Arrays.asList(expr), 
ArrayUtils.EMPTY_BYTE_ARRAY, 100);
         IFilter filter = new IdentityQueryFilter();
@@ -193,12 +194,28 @@ public class ColumnFamilyStoreTest exten
         assert Arrays.equals(FBUtilities.toByteArray(1L), 
rows.get(0).cf.getColumn("birthdate".getBytes("UTF8")).value());
         assert Arrays.equals(FBUtilities.toByteArray(1L), 
rows.get(1).cf.getColumn("birthdate".getBytes("UTF8")).value());
 
+        // add a second expression
         IndexExpression expr2 = new 
IndexExpression("notbirthdate".getBytes("UTF8"), IndexOperator.GTE, 
FBUtilities.toByteArray(2L));
         clause = new IndexClause(Arrays.asList(expr, expr2), 
ArrayUtils.EMPTY_BYTE_ARRAY, 100);
         rows = 
Table.open("Keyspace1").getColumnFamilyStore("Indexed1").scan(clause, range, 
filter);
 
         assert rows.size() == 1 : StringUtils.join(rows, ",");
         assert Arrays.equals("k3".getBytes(), rows.get(0).key.key);
+
+        // same query again, but with resultset not including the subordinate 
expression
+        rows = 
Table.open("Keyspace1").getColumnFamilyStore("Indexed1").scan(clause, range, 
new NamesQueryFilter("birthdate".getBytes("UTF8")));
+
+        assert rows.size() == 1 : StringUtils.join(rows, ",");
+        assert Arrays.equals("k3".getBytes(), rows.get(0).key.key);
+        assert rows.get(0).cf.getColumnCount() == 1 : rows.get(0).cf;
+
+        // once more, this time with a slice rowset that needs to be expanded
+        SliceQueryFilter sqf = new 
SliceQueryFilter(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.EMPTY_BYTE_ARRAY, 
false, 0);
+        rows = 
Table.open("Keyspace1").getColumnFamilyStore("Indexed1").scan(clause, range, 
sqf);
+
+        assert rows.size() == 1 : StringUtils.join(rows, ",");
+        assert Arrays.equals("k3".getBytes(), rows.get(0).key.key);
+        assert rows.get(0).cf.getColumnCount() == 0;
     }
 
     @Test


Reply via email to