Author: jbellis
Date: Wed Oct 20 19:18:40 2010
New Revision: 1025682
URL: http://svn.apache.org/viewvc?rev=1025682&view=rev
Log:
cli support for index queries
patch by Pavel Yaskevich; reviewed by jbellis for CASSANDRA-1635
Modified:
cassandra/trunk/CHANGES.txt
cassandra/trunk/src/java/org/apache/cassandra/cli/Cli.g
cassandra/trunk/src/java/org/apache/cassandra/cli/CliClient.java
cassandra/trunk/src/java/org/apache/cassandra/cli/CliUtils.java
cassandra/trunk/test/unit/org/apache/cassandra/cli/CliTest.java
Modified: cassandra/trunk/CHANGES.txt
URL:
http://svn.apache.org/viewvc/cassandra/trunk/CHANGES.txt?rev=1025682&r1=1025681&r2=1025682&view=diff
==============================================================================
--- cassandra/trunk/CHANGES.txt (original)
+++ cassandra/trunk/CHANGES.txt Wed Oct 20 19:18:40 2010
@@ -49,6 +49,7 @@ dev
client API until races can be fixed (CASSANDRA-1630, CASSANDRA-1585)
* add cli sanity tests (CASSANDRA-1582)
* update GC settings in cassandra.bat (CASSANDRA-1636)
+ * cli support for index queries (CASSANDRA-1635)
0.7-beta2
Modified: cassandra/trunk/src/java/org/apache/cassandra/cli/Cli.g
URL:
http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/cli/Cli.g?rev=1025682&r1=1025681&r2=1025682&view=diff
==============================================================================
--- cassandra/trunk/src/java/org/apache/cassandra/cli/Cli.g (original)
+++ cassandra/trunk/src/java/org/apache/cassandra/cli/Cli.g Wed Oct 20 19:18:40
2010
@@ -44,6 +44,7 @@ tokens {
NODE_SHOW_VERSION;
NODE_SHOW_TABLES;
NODE_THRIFT_GET;
+ NODE_THRIFT_GET_WITH_CONDITIONS;
NODE_THRIFT_SET;
NODE_THRIFT_COUNT;
NODE_THRIFT_DEL;
@@ -63,6 +64,8 @@ tokens {
CONVERT_TO_TYPE;
FUNCTION_CALL;
+ CONDITION;
+ CONDITIONS;
ARRAY;
HASH;
PAIR;
@@ -185,6 +188,17 @@ exitStatement
getStatement
: K_GET columnFamilyExpr ('AS' typeIdentifier)?
-> ^(NODE_THRIFT_GET columnFamilyExpr ( ^(CONVERT_TO_TYPE
typeIdentifier) )? )
+ | K_GET columnFamily 'WHERE' getCondition ('AND' getCondition)* ('LIMIT'
limit=IntegerLiteral)*
+ -> ^(NODE_THRIFT_GET_WITH_CONDITIONS columnFamily ^(CONDITIONS
getCondition+) ^(NODE_LIMIT $limit)*)
+ ;
+
+getCondition
+ : columnOrSuperColumn operator value
+ -> ^(CONDITION operator columnOrSuperColumn value)
+ ;
+
+operator
+ : '=' | '>' | '<' | '>=' | '<='
;
typeIdentifier
Modified: cassandra/trunk/src/java/org/apache/cassandra/cli/CliClient.java
URL:
http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/cli/CliClient.java?rev=1025682&r1=1025681&r2=1025682&view=diff
==============================================================================
--- cassandra/trunk/src/java/org/apache/cassandra/cli/CliClient.java (original)
+++ cassandra/trunk/src/java/org/apache/cassandra/cli/CliClient.java Wed Oct 20
19:18:40 2010
@@ -135,6 +135,9 @@ public class CliClient
case CliParser.NODE_THRIFT_GET:
executeGet(ast);
break;
+ case CliParser.NODE_THRIFT_GET_WITH_CONDITIONS:
+ executeGetWithConditions(ast);
+ break;
case CliParser.NODE_HELP:
printCmdHelp(ast);
break;
@@ -370,6 +373,8 @@ public class CliClient
css_.out.println("get <cf>['<key>']");
css_.out.println("get <cf>['<key>']['<col>'] (as <type>)*");
css_.out.println("get <cf>['<key>']['<super>']");
+ css_.out.println("get <cf> where <column> = <value> [and
<column> > <value> and ...] [limit <integer>]");
+ css_.out.println("Default LIMIT is 100. Available operations:
=, >, >=, <, <=\n");
css_.out.println("get <cf>['<key>']['<super>']['<col>'] (as
<type>)*");
css_.out.print("Note: `as <type>` is optional, it dynamically
converts column value to the specified type");
css_.out.println(", column value validator will be set to
<type>.");
@@ -455,6 +460,7 @@ public class CliClient
css_.out.println("rename column family <cf> <new_name>
Rename a column family.");
css_.out.println("get <cf>['<key>']
Get a slice of columns.");
css_.out.println("get <cf>['<key>']['<super>']
Get a slice of sub columns.");
+ css_.out.println("get <cf> where <column> = <value> [and <column>
> <value> and ...] [limit int]. ");
css_.out.println("get <cf>['<key>']['<col>'] (as <type>)*
Get a column value.");
css_.out.println("get <cf>['<key>']['<super>']['<col>'] (as
<type>)* Get a sub column value.");
css_.out.println("set <cf>['<key>']['<col>'] = <value>
Set a column.");
@@ -742,6 +748,100 @@ public class CliClient
formatColumnName(keySpace, columnFamily, column),
valueAsString, column.timestamp);
}
+ /**
+ * Process get operation with conditions (using Thrift get_indexed_slices
method)
+ * @param statement - tree representation of the current statement
+ * Format: ^(NODE_THRIFT_GET_WITH_CONDITIONS cf ^(CONDITIONS ^(CONDITION
>= column1 value1) ...) ^(NODE_LIMIT int)*)
+ */
+ private void executeGetWithConditions(Tree statement)
+ {
+ if (!CliMain.isConnected() || !hasKeySpace())
+ return;
+
+ IndexClause clause = new IndexClause();
+ String columnFamily = statement.getChild(0).getText();
+ // ^(CONDITIONS ^(CONDITION $column $value) ...)
+ Tree conditions = statement.getChild(1);
+
+ // fetching column family definition
+ CfDef columnFamilyDef = getCfDef(columnFamily);
+
+ // fetching all columns
+ SlicePredicate predicate = new SlicePredicate();
+ SliceRange sliceRange = new SliceRange();
+ sliceRange.setStart(new byte[0]).setFinish(new byte[0]);
+ predicate.setSlice_range(sliceRange);
+
+ for (int i = 0; i < conditions.getChildCount(); i++)
+ {
+ // ^(CONDITION operator $column $value)
+ Tree condition = conditions.getChild(i);
+
+ // =, >, >=, <, <=
+ String operator = condition.getChild(0).getText();
+ String columnNameString =
CliUtils.unescapeSQLString(condition.getChild(1).getText());
+ // it could be a basic string or function call
+ Tree valueTree = condition.getChild(2);
+
+ try
+ {
+ byte[] value;
+ byte[] columnName = columnNameAsByteArray(columnNameString,
columnFamily);
+
+ if (valueTree.getType() == CliParser.FUNCTION_CALL)
+ {
+ value = convertValueByFunction(valueTree, columnFamilyDef,
columnName);
+ }
+ else
+ {
+ String valueString =
CliUtils.unescapeSQLString(valueTree.getText());
+ value = columnValueAsByteArray(columnName, columnFamily,
valueString);
+ }
+
+ // index operator from string
+ IndexOperator idxOperator =
CliUtils.getIndexOperator(operator);
+ // adding new index expression into index clause
+ clause.addToExpressions(new IndexExpression(columnName,
idxOperator, value));
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+
+ List<KeySlice> slices;
+ clause.setStart_key(new byte[] {});
+
+ // when we have ^(NODE_LIMIT Integer)
+ if (statement.getChildCount() == 3)
+ {
+ Tree limitNode = statement.getChild(2);
+ int limitValue = Integer.valueOf(limitNode.getChild(0).getText());
+
+ if (limitValue == 0)
+ {
+ throw new IllegalArgumentException("LIMIT should be greater
than zero.");
+ }
+
+ clause.setCount(limitValue);
+ }
+
+ try
+ {
+ ColumnParent parent = new ColumnParent(columnFamily);
+ slices = thriftClient_.get_indexed_slices(parent, clause,
predicate, ConsistencyLevel.ONE);
+ printSliceList(columnFamilyDef, slices);
+ }
+ catch (InvalidRequestException e)
+ {
+ throw new RuntimeException(e.getWhy());
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+
// Execute SET statement
private void executeSet(CommonTree ast)
throws TException, InvalidRequestException, UnavailableException,
TimedOutException, UnsupportedEncodingException, NoSuchFieldException,
InstantiationException, IllegalAccessException
@@ -790,7 +890,7 @@ public class CliClient
switch (valueTree.getType())
{
case CliParser.FUNCTION_CALL:
- columnValueInBytes = convertValueByFunction(valueTree,
getCfDef(columnFamily), columnNameInBytes);
+ columnValueInBytes = convertValueByFunction(valueTree,
getCfDef(columnFamily), columnNameInBytes, true);
break;
default:
columnValueInBytes = columnValueAsByteArray(columnNameInBytes,
columnFamily, value);
@@ -1091,7 +1191,6 @@ public class CliClient
if (!CliMain.isConnected())
return;
- assert (ast.getChildCount() >= 1 || ast.getChildCount() <= 3) :
"Incorrect AST Construct!";
Iterator<CommonTree> iter = ast.getChildren().iterator();
// extract column family
@@ -1116,7 +1215,6 @@ public class CliClient
}
else
{
- assert child.getType() == CliParser.NODE_LIMIT;
if (child.getChildCount() != 1)
{
css_.out.println("Invalid limit clause");
@@ -1137,18 +1235,7 @@ public class CliClient
css_.out.println("Using default limit of 100");
}
- List<String> cfnames = new ArrayList<String>();
- for (CfDef cfd : keyspacesMap.get(keySpace).cf_defs)
- {
- cfnames.add(cfd.name);
- }
-
- int idx = cfnames.indexOf(columnFamily);
- if (idx == -1)
- {
- css_.out.println("No such column family: " + columnFamily);
- return;
- }
+ CfDef columnFamilyDef = getCfDef(columnFamily);
// read all columns and superColumns
SlicePredicate predicate = new SlicePredicate();
@@ -1163,36 +1250,15 @@ public class CliClient
ColumnParent columnParent = new ColumnParent(columnFamily);
List<KeySlice> keySlices =
thriftClient_.get_range_slices(columnParent, predicate, range,
ConsistencyLevel.ONE);
int toIndex = keySlices.size();
- List<KeySlice> limitSlices = keySlices.subList(0, toIndex);
- for (KeySlice ks : limitSlices)
+ if (limitCount < keySlices.size())
{
- css_.out.printf("-------------------\nRowKey: %s\n", new
String(ks.key, "UTF-8"));
- Iterator<ColumnOrSuperColumn> iterator = ks.getColumnsIterator();
- while (iterator.hasNext())
- {
- ColumnOrSuperColumn columnOrSuperColumn = iterator.next();
- if (columnOrSuperColumn.column != null)
- {
- Column col = columnOrSuperColumn.column;
- css_.out.printf("=> (column=%s, value=%s, timestamp=%d)\n",
- formatColumnName(keySpace, columnFamily,
col), new String(col.value, "UTF-8"), col.timestamp);
- }
- else if (columnOrSuperColumn.super_column != null)
- {
- SuperColumn superCol = columnOrSuperColumn.super_column;
- css_.out.printf("=> (super_column=%s,",
formatSuperColumnName(keySpace, columnFamily, superCol));
- for (Column col : superCol.columns)
- {
- css_.out.printf("\n (column=%s, value=%s,
timestamp=%d)",
- formatSubcolumnName(keySpace,
columnFamily, col), new String(col.value, "UTF-8"), col.timestamp);
- }
- css_.out.println(")");
- }
- }
+ // limitCount could be Integer.MAX_VALUE
+ toIndex = limitCount;
}
- css_.out.printf("\n%d row%s returned\n", toIndex, (toIndex == 0 ||
toIndex > 1 ? "s" : ""));
+
+ printSliceList(columnFamilyDef, keySlices.subList(0, toIndex));
}
private void executeShowVersion() throws TException
@@ -1743,12 +1809,26 @@ public class CliClient
/**
* Used to convert value (function argument, string) into byte[]
+ * calls convertValueByFunction method with "withUpdate" set to false
+ * @param functionCall - tree representing function call ^(FUNCTION_CALL
function_name value)
+ * @param columnFamily - column family definition (CfDef)
+ * @param columnName - also updates column family metadata for given
column
+ * @return byte[] - string value as byte[]
+ */
+ private byte[] convertValueByFunction(Tree functionCall, CfDef
columnFamily, byte[] columnName)
+ {
+ return convertValueByFunction(functionCall, columnFamily, columnName,
false);
+ }
+
+ /**
+ * Used to convert value (function argument, string) into byte[]
* @param functionCall - tree representing function call ^(FUNCTION_CALL
function_name value)
* @param columnFamily - column family definition (CfDef)
* @param columnName - column name as byte[] (used to update CfDef)
+ * @param withUpdate - also updates column family metadata for given
column
* @return byte[] - string value as byte[]
*/
- private byte[] convertValueByFunction(Tree functionCall, CfDef
columnFamily, byte[] columnName)
+ private byte[] convertValueByFunction(Tree functionCall, CfDef
columnFamily, byte[] columnName, boolean withUpdate)
{
String functionName = functionCall.getChild(0).getText();
String functionArg =
CliUtils.unescapeSQLString(functionCall.getChild(1).getText());
@@ -1771,7 +1851,10 @@ public class CliClient
byte[] value = getBytesAccordingToType(functionArg, validator);
// updating CfDef
- updateColumnMetaData(columnFamily, columnName,
validator.getClass().getName());
+ if (withUpdate)
+ {
+ updateColumnMetaData(columnFamily, columnName,
validator.getClass().getName());
+ }
return value;
}
@@ -1841,4 +1924,61 @@ public class CliClient
return null;
}
+
+ /**
+ * Prints out KeySlice list
+ * @param columnFamilyDef - column family definition
+ * @param slices - list of the KeySlice's to print out
+ * @throws UnsupportedEncodingException - when trying to covert key
+ * @throws NotFoundException - column not found
+ * @throws TException - transfer is broken
+ * @throws IllegalAccessException - can't do operation
+ * @throws InstantiationException - can't instantiate a class
+ * @throws NoSuchFieldException - column not found
+ */
+ private void printSliceList(CfDef columnFamilyDef, List<KeySlice> slices)
+ throws UnsupportedEncodingException, NotFoundException,
TException, IllegalAccessException, InstantiationException, NoSuchFieldException
+ {
+ AbstractType validator;
+ String columnFamilyName = columnFamilyDef.getName();
+
+ for (KeySlice ks : slices)
+ {
+ css_.out.printf("-------------------\n");
+ css_.out.printf("RowKey: %s\n", new String(ks.key, "UTF-8"));
+
+ Iterator<ColumnOrSuperColumn> iterator = ks.getColumnsIterator();
+
+ while (iterator.hasNext())
+ {
+ ColumnOrSuperColumn columnOrSuperColumn = iterator.next();
+
+ if (columnOrSuperColumn.column != null)
+ {
+ Column col = columnOrSuperColumn.column;
+ validator = getValidatorForValue(columnFamilyDef,
col.getName());
+
+ css_.out.printf("=> (column=%s, value=%s, timestamp=%d)\n",
+ formatColumnName(keySpace,
columnFamilyName, col), validator.getString(col.value), col.timestamp);
+ }
+ else if (columnOrSuperColumn.super_column != null)
+ {
+ SuperColumn superCol = columnOrSuperColumn.super_column;
+ css_.out.printf("=> (super_column=%s,",
formatSuperColumnName(keySpace, columnFamilyName, superCol));
+
+ for (Column col : superCol.columns)
+ {
+ validator = getValidatorForValue(columnFamilyDef,
col.getName());
+
+ css_.out.printf("\n (column=%s, value=%s,
timestamp=%d)",
+ formatSubcolumnName(keySpace,
columnFamilyName, col), validator.getString(col.value), col.timestamp);
+ }
+
+ css_.out.println(")");
+ }
+ }
+ }
+
+ css_.out.printf("\n%d Row%s Returned.\n", slices.size(),
(slices.size() > 1 ? "s" : ""));
+ }
}
Modified: cassandra/trunk/src/java/org/apache/cassandra/cli/CliUtils.java
URL:
http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/cli/CliUtils.java?rev=1025682&r1=1025681&r2=1025682&view=diff
==============================================================================
--- cassandra/trunk/src/java/org/apache/cassandra/cli/CliUtils.java (original)
+++ cassandra/trunk/src/java/org/apache/cassandra/cli/CliUtils.java Wed Oct 20
19:18:40 2010
@@ -1,4 +1,6 @@
package org.apache.cassandra.cli;
+
+import org.apache.cassandra.thrift.IndexOperator;
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -95,4 +97,34 @@ public class CliUtils
return sb.toString();
}
+ /**
+ * Returns IndexOperator from string representation
+ * @param operator - string representing IndexOperator (=, >=, >, <, <=)
+ * @return IndexOperator - enum value of IndexOperator or null if not found
+ */
+ public static IndexOperator getIndexOperator(String operator)
+ {
+ if (operator.equals("="))
+ {
+ return IndexOperator.EQ;
+ }
+ else if (operator.equals(">="))
+ {
+ return IndexOperator.GTE;
+ }
+ else if (operator.equals(">"))
+ {
+ return IndexOperator.GT;
+ }
+ else if (operator.equals("<"))
+ {
+ return IndexOperator.LT;
+ }
+ else if (operator.equals("<="))
+ {
+ return IndexOperator.LTE;
+ }
+
+ return null;
+ }
}
Modified: cassandra/trunk/test/unit/org/apache/cassandra/cli/CliTest.java
URL:
http://svn.apache.org/viewvc/cassandra/trunk/test/unit/org/apache/cassandra/cli/CliTest.java?rev=1025682&r1=1025681&r2=1025682&view=diff
==============================================================================
--- cassandra/trunk/test/unit/org/apache/cassandra/cli/CliTest.java (original)
+++ cassandra/trunk/test/unit/org/apache/cassandra/cli/CliTest.java Wed Oct 20
19:18:40 2010
@@ -33,11 +33,12 @@ public class CliTest extends TestCase
// please add new statements here so they could be auto-runned by this
test.
private String[] statements = {
"use TestKeySpace",
- "create column family CF1 with comparator=UTF8Type and
column_metadata=[{ column_name:world, validation_class:IntegerType }]",
+ "create column family CF1 with comparator=UTF8Type and
column_metadata=[{ column_name:world, validation_class:IntegerType,
index_type:0, index_name:IdxName }, { column_name:world2,
validation_class:LongType, index_type:0, index_name:LongIdxName}]",
"set CF1[hello][world] = 123848374878933948398384",
"get CF1[hello][world]",
+ "set CF1[hello][world2] = 15",
+ "get CF1 where world2 = long(15)",
"set CF1['hello'][time_spent_uuid] =
timeuuid(a8098c1a-f86e-11da-bd1a-00112444be1e)",
- "get CF1['hello'][time_spent_uuid] as LexicalUUIDType",
"create column family CF2 with comparator=IntegerType",
"set CF2['key'][98349387493847748398334] = 'some text'",
"get CF2['key'][98349387493847748398334]",
@@ -91,7 +92,14 @@ public class CliTest extends TestCase
}
else if (statement.startsWith("get "))
{
- assertTrue(result.startsWith("=> (column="));
+ if (statement.contains("where"))
+ {
+
assertTrue(result.startsWith("-------------------\nRowKey:"));
+ }
+ else
+ {
+ assertTrue(result.startsWith("=> (column="));
+ }
}
outStream.reset(); // reset stream so we have only output from
next statement all the time