Author: jbellis
Date: Mon May 9 19:56:02 2011
New Revision: 1101182
URL: http://svn.apache.org/viewvc?rev=1101182&view=rev
Log:
add support for presenting row key as a column in CQL result sets
patch by jbellis; reviewed by thobbs for CASSANDRA-2622
Modified:
cassandra/branches/cassandra-0.8/CHANGES.txt
cassandra/branches/cassandra-0.8/drivers/java/src/org/apache/cassandra/cql/jdbc/ColumnDecoder.java
cassandra/branches/cassandra-0.8/drivers/java/test/org/apache/cassandra/cql/JdbcDriverTest.java
cassandra/branches/cassandra-0.8/drivers/py/cql/__init__.py
cassandra/branches/cassandra-0.8/drivers/py/cql/cursor.py
cassandra/branches/cassandra-0.8/drivers/py/cql/decoders.py
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/config/CFMetaData.java
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/Cql.g
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/QueryProcessor.java
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/SelectExpression.java
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/SelectStatement.java
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/thrift/ThriftValidation.java
cassandra/branches/cassandra-0.8/test/system/test_cql.py
Modified: cassandra/branches/cassandra-0.8/CHANGES.txt
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/CHANGES.txt?rev=1101182&r1=1101181&r2=1101182&view=diff
==============================================================================
--- cassandra/branches/cassandra-0.8/CHANGES.txt (original)
+++ cassandra/branches/cassandra-0.8/CHANGES.txt Mon May 9 19:56:02 2011
@@ -11,11 +11,14 @@
* JDBC CQL driver exposes getColumn for access to timestamp
* JDBC ResultSetMetadata properties added to AbstractType
* r/m clustertool (CASSANDRA-2607)
+ * add support for presenting row key as a column in CQL result sets
+ (CASSANDRA-2622)
0.8.0-beta2
* fix NPE compacting index CFs (CASSANDRA-2528)
- * Remove checking all column families on startup for compaction candidates
(CASSANDRA-2444)
+ * Remove checking all column families on startup for compaction candidates
+ (CASSANDRA-2444)
* validate CQL create keyspace options (CASSANDRA-2525)
* fix nodetool setcompactionthroughput (CASSANDRA-2550)
* move gossip heartbeat back to its own thread (CASSANDRA-2554)
Modified:
cassandra/branches/cassandra-0.8/drivers/java/src/org/apache/cassandra/cql/jdbc/ColumnDecoder.java
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/drivers/java/src/org/apache/cassandra/cql/jdbc/ColumnDecoder.java?rev=1101182&r1=1101181&r2=1101182&view=diff
==============================================================================
---
cassandra/branches/cassandra-0.8/drivers/java/src/org/apache/cassandra/cql/jdbc/ColumnDecoder.java
(original)
+++
cassandra/branches/cassandra-0.8/drivers/java/src/org/apache/cassandra/cql/jdbc/ColumnDecoder.java
Mon May 9 19:56:02 2011
@@ -25,6 +25,7 @@ import org.apache.cassandra.config.CFMet
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.config.ConfigurationException;
import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.AsciiType;
import org.apache.cassandra.thrift.*;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
@@ -33,6 +34,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -80,12 +82,30 @@ class ColumnDecoder
{
CFMetaData md = metadata.get(String.format("%s.%s", keyspace,
columnFamily));
+ try
+ {
+ if
(ByteBufferUtil.string(name).equalsIgnoreCase(ByteBufferUtil.string(md.getKeyName())))
+ return AsciiType.instance;
+ }
+ catch (CharacterCodingException e)
+ {
+ // not be the key name
+ }
return md.comparator;
}
AbstractType getValueType(String keyspace, String columnFamily, ByteBuffer
name)
{
CFMetaData md = metadata.get(String.format("%s.%s", keyspace,
columnFamily));
+ try
+ {
+ if
(ByteBufferUtil.string(name).equalsIgnoreCase(ByteBufferUtil.string(md.getKeyName())))
+ return md.getKeyValidator();
+ }
+ catch (CharacterCodingException e)
+ {
+ // not be the key name
+ }
ColumnDefinition cd = md.getColumnDefinition(name);
return cd == null ? md.getDefaultValidator() : cd.getValidator();
}
Modified:
cassandra/branches/cassandra-0.8/drivers/java/test/org/apache/cassandra/cql/JdbcDriverTest.java
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/drivers/java/test/org/apache/cassandra/cql/JdbcDriverTest.java?rev=1101182&r1=1101181&r2=1101182&view=diff
==============================================================================
---
cassandra/branches/cassandra-0.8/drivers/java/test/org/apache/cassandra/cql/JdbcDriverTest.java
(original)
+++
cassandra/branches/cassandra-0.8/drivers/java/test/org/apache/cassandra/cql/JdbcDriverTest.java
Mon May 9 19:56:02 2011
@@ -33,6 +33,7 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.db.marshal.*;
+import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import static junit.framework.Assert.assertEquals;
@@ -120,6 +121,12 @@ public class JdbcDriverTest extends Embe
expectedMetaData(md, 2, BigInteger.class.getName(), "JdbcInteger",
"Keyspace1", "2", Types.BIGINT, IntegerType.class.getSimpleName(), true, false);
expectedMetaData(md, 3, String.class.getName(), "JdbcInteger",
"Keyspace1", "42", Types.VARCHAR, UTF8Type.class.getSimpleName(), false, true);
+ rs = stmt.executeQuery("select key, 1, 2, 42 from JdbcInteger where
key='" + key + "'");
+ assert rs.next();
+ assert Arrays.equals(rs.getBytes("key"), FBUtilities.hexToBytes(key));
+ assert rs.getObject("1").equals(new
BigInteger("36893488147419103232"));
+ assert rs.getString("42").equals("fortytwofortytwo") :
rs.getString("42");
+
stmt.executeUpdate("update JdbcUtf8 set a='aa', b='bb',
fortytwo='4242' where key='" + key + "'");
rs = stmt.executeQuery("select a, b, fortytwo from JdbcUtf8 where
key='" + key + "'");
assert rs.next();
Modified: cassandra/branches/cassandra-0.8/drivers/py/cql/__init__.py
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/drivers/py/cql/__init__.py?rev=1101182&r1=1101181&r2=1101182&view=diff
==============================================================================
--- cassandra/branches/cassandra-0.8/drivers/py/cql/__init__.py (original)
+++ cassandra/branches/cassandra-0.8/drivers/py/cql/__init__.py Mon May 9
19:56:02 2011
@@ -45,8 +45,6 @@ apilevel = 1.0
threadsafety = 1 # Threads may share the module, but not connections/cursors.
paramstyle = 'named'
-ROW_KEY = "Row Key"
-
# TODO: Pull connections out of a pool instead.
def connect(host, port=9160, keyspace='system', user=None, password=None):
return connection.Connection(host, port, keyspace, user, password)
Modified: cassandra/branches/cassandra-0.8/drivers/py/cql/cursor.py
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/drivers/py/cql/cursor.py?rev=1101182&r1=1101181&r2=1101182&view=diff
==============================================================================
--- cassandra/branches/cassandra-0.8/drivers/py/cql/cursor.py (original)
+++ cassandra/branches/cassandra-0.8/drivers/py/cql/cursor.py Mon May 9
19:56:02 2011
@@ -92,16 +92,14 @@ class Cursor:
return results
def column_families(cf_defs):
- cfresults = {}
- if cf_defs:
- for cf in cf_defs:
- cfresults[cf.name] = {"comparator": cf.comparator_type}
- cfresults[cf.name]["default_validation_class"] = \
- cf.default_validation_class
- cfresults[cf.name]["key_validation_class"] = \
- cf.key_validation_class
- cfresults[cf.name]["columns"] = columns(cf.column_metadata)
- return cfresults
+ d = {}
+ for cf in cf_defs:
+ d[cf.name] = {'comparator': cf.comparator_type,
+ 'default_validation_class':
cf.default_validation_class,
+ 'key_validation_class': cf.key_validation_class,
+ 'columns': columns(cf.column_metadata),
+ 'key_alias': cf.key_alias}
+ return d
schema = {}
client = self.parent_connection.client
Modified: cassandra/branches/cassandra-0.8/drivers/py/cql/decoders.py
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/drivers/py/cql/decoders.py?rev=1101182&r1=1101181&r2=1101182&view=diff
==============================================================================
--- cassandra/branches/cassandra-0.8/drivers/py/cql/decoders.py (original)
+++ cassandra/branches/cassandra-0.8/drivers/py/cql/decoders.py Mon May 9
19:56:02 2011
@@ -32,45 +32,44 @@ class SchemaDecoder(object):
def __comparator_for(self, keyspace, column_family):
cfam = self.__get_column_family_def(keyspace, column_family)
- if cfam and "comparator" in cfam:
+ if "comparator" in cfam:
return cfam["comparator"]
return None
def __validator_for(self, keyspace, column_family, name):
cfam = self.__get_column_family_def(keyspace, column_family)
- if cfam:
- if name in cfam["columns"]:
- return cfam["columns"][name]
- return cfam["default_validation_class"]
- return None
+ if name in cfam["columns"]:
+ return cfam["columns"][name]
+ return cfam["default_validation_class"]
def __keytype_for(self, keyspace, column_family):
cfam = self.__get_column_family_def(keyspace, column_family)
- if cfam and "key_validation_class" in cfam:
+ if "key_validation_class" in cfam:
return cfam["key_validation_class"]
return None
def decode_description(self, keyspace, column_family, row):
- key_type = self.__keytype_for(keyspace, column_family)
- description = [(cql.ROW_KEY, key_type, None, None, None, None, None,
False)]
+ description = []
comparator = self.__comparator_for(keyspace, column_family)
unmarshal = unmarshallers.get(comparator, unmarshal_noop)
for column in row.columns:
- description.append((unmarshal(column.name), comparator, None,
None, None, None, True))
-
+ if column.name == self.__get_column_family_def(keyspace,
column_family)['key_alias']:
+ description.append((column.name, 'text', None, None, None,
None, True))
+ else:
+ description.append((unmarshal(column.name), comparator, None,
None, None, None, True))
return description
def decode_row(self, keyspace, column_family, row):
- key_type = self.__keytype_for(keyspace, column_family)
- key = unmarshallers.get(key_type, unmarshal_noop)(row.key)
comparator = self.__comparator_for(keyspace, column_family)
unmarshal = unmarshallers.get(comparator, unmarshal_noop)
- values = [key]
+ values = []
for column in row.columns:
- validator = self.__validator_for(keyspace, column_family,
column.name)
if column.value is None:
values.append(None)
+ continue
+ if column.name == self.__get_column_family_def(keyspace,
column_family)['key_alias']:
+ validator = self.__keytype_for(keyspace, column_family)
else:
- values.append(unmarshallers.get(validator,
unmarshal_noop)(column.value))
-
+ validator = self.__validator_for(keyspace, column_family,
column.name)
+ values.append(unmarshallers.get(validator,
unmarshal_noop)(column.value))
return values
Modified:
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/config/CFMetaData.java
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/config/CFMetaData.java?rev=1101182&r1=1101181&r2=1101182&view=diff
==============================================================================
---
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/config/CFMetaData.java
(original)
+++
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/config/CFMetaData.java
Mon May 9 19:56:02 2011
@@ -78,6 +78,7 @@ public final class CFMetaData
public static final CFMetaData SchemaCf =
newSystemMetadata(Migration.SCHEMA_CF, 3, "current state of the schema",
UTF8Type.instance, null, DEFAULT_SYSTEM_MEMTABLE_THROUGHPUT_IN_MB);
public static final CFMetaData IndexCf =
newSystemMetadata(SystemTable.INDEX_CF, 5, "indexes that have been completed",
UTF8Type.instance, null, DEFAULT_SYSTEM_MEMTABLE_THROUGHPUT_IN_MB);
public static final CFMetaData NodeIdCf =
newSystemMetadata(SystemTable.NODE_ID_CF, 6, "nodeId and their metadata",
TimeUUIDType.instance, null, DEFAULT_SYSTEM_MEMTABLE_THROUGHPUT_IN_MB);
+ private static final ByteBuffer DEFAULT_KEY_NAME =
ByteBufferUtil.bytes("KEY");
/**
* @return A calculated memtable throughput size for this machine.
@@ -504,9 +505,9 @@ public final class CFMetaData
return rowCacheProvider;
}
- public ByteBuffer getKeyAlias()
+ public ByteBuffer getKeyName()
{
- return keyAlias;
+ return keyAlias == null ? DEFAULT_KEY_NAME : keyAlias;
}
public Map<ByteBuffer, ColumnDefinition> getColumn_metadata()
@@ -640,7 +641,6 @@ public final class CFMetaData
validateMinMaxCompactionThresholds(cf_def);
validateMemtableSettings(cf_def);
- validateAliasCompares(cf_def);
CFMetaData newCFMD = new CFMetaData(cf_def.keyspace,
cf_def.name,
@@ -785,7 +785,7 @@ public final class CFMetaData
def.setMemtable_throughput_in_mb(cfm.memtableThroughputInMb);
def.setMemtable_operations_in_millions(cfm.memtableOperationsInMillions);
def.setMerge_shards_chance(cfm.mergeShardsChance);
- def.setKey_alias(cfm.keyAlias);
+ def.setKey_alias(cfm.getKeyName());
List<org.apache.cassandra.thrift.ColumnDef> column_meta = new
ArrayList< org.apache.cassandra.thrift.ColumnDef>(cfm.column_metadata.size());
for (ColumnDefinition cd : cfm.column_metadata.values())
{
@@ -970,13 +970,6 @@ public final class CFMetaData
DatabaseDescriptor.validateMemtableOperations(cf_def.memtable_operations_in_millions);
}
- public static void validateAliasCompares(org.apache.cassandra.thrift.CfDef
cf_def) throws ConfigurationException
- {
- AbstractType comparator =
DatabaseDescriptor.getComparator(cf_def.comparator_type);
- if (cf_def.key_alias != null)
- comparator.validate(cf_def.key_alias);
- }
-
public static void
validateAliasCompares(org.apache.cassandra.db.migration.avro.CfDef cf_def)
throws ConfigurationException
{
AbstractType comparator =
DatabaseDescriptor.getComparator(cf_def.comparator_type);
Modified:
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/Cql.g
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/Cql.g?rev=1101182&r1=1101181&r2=1101182&view=diff
==============================================================================
--- cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/Cql.g
(original)
+++ cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/Cql.g
Mon May 9 19:56:02 2011
@@ -167,8 +167,8 @@ selectExpression returns [SelectExpressi
( K_REVERSED { reversed = true; } )?
( first=term { $expr = new SelectExpression(first, count, reversed); }
(',' next=term { $expr.and(next); })*
- | start=term RANGEOP finish=term { $expr = new SelectExpression(start,
finish, count, reversed); }
- | '\*' { $expr = new SelectExpression(new Term(), new Term(), count,
reversed); }
+ | start=term RANGEOP finish=term { $expr = new SelectExpression(start,
finish, count, reversed, false); }
+ | '\*' { $expr = new SelectExpression(new Term(), new Term(), count,
reversed, true); }
)
;
@@ -346,7 +346,7 @@ comparatorType
;
term returns [Term item]
- : ( t=STRING_LITERAL | t=INTEGER | t=UUID | t=IDENT ) { $item = new
Term($t.text, $t.type); }
+ : ( t=K_KEY | t=STRING_LITERAL | t=INTEGER | t=UUID | t=IDENT ) { $item =
new Term($t.text, $t.type); }
;
termList returns [List<Term> items]
Modified:
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/QueryProcessor.java
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/QueryProcessor.java?rev=1101182&r1=1101181&r2=1101182&view=diff
==============================================================================
---
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/QueryProcessor.java
(original)
+++
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/QueryProcessor.java
Mon May 9 19:56:02 2011
@@ -23,6 +23,7 @@ package org.apache.cassandra.cql;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
@@ -67,7 +68,6 @@ public class QueryProcessor
{
List<org.apache.cassandra.db.Row> rows;
QueryPath queryPath = new QueryPath(select.getColumnFamily());
- AbstractType<?> comparator = select.getComparator(keyspace);
List<ReadCommand> commands = new ArrayList<ReadCommand>();
assert select.getKeys().size() == 1;
@@ -79,16 +79,14 @@ public class QueryProcessor
// ...of a list of column names
if (!select.isColumnRange())
{
- Collection<ByteBuffer> columnNames = new ArrayList<ByteBuffer>();
- for (Term column : select.getColumnNames())
- columnNames.add(column.getByteBuffer(comparator));
-
+ Collection<ByteBuffer> columnNames = getColumnNames(select,
metadata);
validateColumnNames(columnNames);
commands.add(new SliceByNamesReadCommand(keyspace, key, queryPath,
columnNames));
}
// ...a range (slice) of column names
else
{
+ AbstractType<?> comparator = select.getComparator(keyspace);
ByteBuffer start =
select.getColumnStart().getByteBuffer(comparator);
ByteBuffer finish =
select.getColumnFinish().getByteBuffer(comparator);
@@ -117,7 +115,20 @@ public class QueryProcessor
return rows;
}
-
+
+ private static List<ByteBuffer> getColumnNames(SelectStatement select,
CFMetaData metadata) throws InvalidRequestException
+ {
+ String keyString = getKeyString(metadata);
+ List<ByteBuffer> columnNames = new ArrayList<ByteBuffer>();
+ for (Term column : select.getColumnNames())
+ {
+ // skip the key for the slice op; we'll add it to the resultset in
extractThriftColumns
+ if (!column.getText().equalsIgnoreCase(keyString))
+ columnNames.add(column.getByteBuffer(metadata.comparator));
+ }
+ return columnNames;
+ }
+
private static List<org.apache.cassandra.db.Row> multiRangeSlice(String
keyspace, SelectStatement select)
throws TimedOutException, UnavailableException, InvalidRequestException
{
@@ -146,9 +157,8 @@ public class QueryProcessor
AbstractBounds bounds = new Bounds(startToken, finishToken);
CFMetaData metadata = validateColumnFamily(keyspace,
select.getColumnFamily(), false);
- AbstractType<?> comparator = metadata.getComparatorFor(null);
// XXX: Our use of Thrift structs internally makes me Sad. :(
- SlicePredicate thriftSlicePredicate = slicePredicateFromSelect(select,
comparator);
+ SlicePredicate thriftSlicePredicate = slicePredicateFromSelect(select,
metadata);
validateSlicePredicate(metadata, thriftSlicePredicate);
int limit = select.isKeyRange() && select.getKeyStart() != null
@@ -200,16 +210,15 @@ public class QueryProcessor
throws TimedOutException, UnavailableException, InvalidRequestException
{
CFMetaData metadata = validateColumnFamily(keyspace,
select.getColumnFamily(), false);
- AbstractType<?> comparator = metadata.getComparatorFor(null);
// XXX: Our use of Thrift structs internally (still) makes me Sad. :~(
- SlicePredicate thriftSlicePredicate = slicePredicateFromSelect(select,
comparator);
+ SlicePredicate thriftSlicePredicate = slicePredicateFromSelect(select,
metadata);
validateSlicePredicate(metadata, thriftSlicePredicate);
List<IndexExpression> expressions = new ArrayList<IndexExpression>();
for (Relation columnRelation : select.getColumnRelations())
{
// Left and right side of relational expression encoded according
to comparator/validator.
- ByteBuffer entity =
columnRelation.getEntity().getByteBuffer(comparator);
+ ByteBuffer entity =
columnRelation.getEntity().getByteBuffer(metadata.comparator);
ByteBuffer value =
columnRelation.getValue().getByteBuffer(select.getValueValidator(keyspace,
entity));
expressions.add(new IndexExpression(entity,
@@ -291,7 +300,7 @@ public class QueryProcessor
}
}
- private static SlicePredicate slicePredicateFromSelect(SelectStatement
select, AbstractType<?> comparator)
+ private static SlicePredicate slicePredicateFromSelect(SelectStatement
select, CFMetaData metadata)
throws InvalidRequestException
{
SlicePredicate thriftSlicePredicate = new SlicePredicate();
@@ -299,18 +308,15 @@ public class QueryProcessor
if (select.isColumnRange() || select.getColumnNames().size() == 0)
{
SliceRange sliceRange = new SliceRange();
- sliceRange.start =
select.getColumnStart().getByteBuffer(comparator);
- sliceRange.finish =
select.getColumnFinish().getByteBuffer(comparator);
+ sliceRange.start =
select.getColumnStart().getByteBuffer(metadata.comparator);
+ sliceRange.finish =
select.getColumnFinish().getByteBuffer(metadata.comparator);
sliceRange.reversed = select.isColumnsReversed();
sliceRange.count = select.getColumnsLimit();
thriftSlicePredicate.slice_range = sliceRange;
}
else
{
- List<ByteBuffer> columnNames = new ArrayList<ByteBuffer>();
- for (Term column : select.getColumnNames())
- columnNames.add(column.getByteBuffer(comparator));
- thriftSlicePredicate.column_names = columnNames;
+ thriftSlicePredicate.column_names = getColumnNames(select,
metadata);
}
return thriftSlicePredicate;
@@ -489,19 +495,17 @@ public class QueryProcessor
// Some statements won't have (or don't need) a keyspace (think USE,
or CREATE).
if (StatementType.requiresKeyspace.contains(statement.type))
keyspace = clientState.getKeyspace();
-
+
CqlResult result = new CqlResult();
logger.debug("CQL statement type: {}", statement.type.toString());
CFMetaData metadata;
- AbstractType<?> comparator;
switch (statement.type)
{
case SELECT:
SelectStatement select = (SelectStatement)statement.statement;
clientState.hasColumnFamilyAccess(select.getColumnFamily(),
Permission.READ);
metadata = validateColumnFamily(keyspace,
select.getColumnFamily(), false);
- comparator = metadata.getComparatorFor(null);
validateSelect(keyspace, select);
List<org.apache.cassandra.db.Row> rows = null;
@@ -538,7 +542,7 @@ public class QueryProcessor
List<CqlRow> cqlRows = new ArrayList<CqlRow>();
result.type = CqlResultType.ROWS;
-
+
// Create the result set
for (org.apache.cassandra.db.Row row : rows)
{
@@ -546,7 +550,7 @@ public class QueryProcessor
if (row.cf == null)
continue;
- List<Column> thriftColumns = extractThriftColumns(select,
comparator, row);
+ List<Column> thriftColumns = extractThriftColumns(select,
metadata, row);
// Create a new row, add the columns to it, and then add
it to the list of rows
CqlRow cqlRow = new CqlRow();
cqlRow.key = row.key.key;
@@ -609,7 +613,7 @@ public class QueryProcessor
DeleteStatement delete = (DeleteStatement)statement.statement;
clientState.hasColumnFamilyAccess(delete.getColumnFamily(),
Permission.WRITE);
metadata = validateColumnFamily(keyspace,
delete.getColumnFamily(), false);
- comparator = metadata.getComparatorFor(null);
+ AbstractType comparator = metadata.getComparatorFor(null);
AbstractType<?> keyType =
DatabaseDescriptor.getCFMetaData(keyspace,
delete.getColumnFamily()).getKeyValidator();
@@ -809,11 +813,17 @@ public class QueryProcessor
return null; // We should never get here.
}
- private static List<Column> extractThriftColumns(SelectStatement select,
AbstractType<?> comparator, Row row)
+ private static List<Column> extractThriftColumns(SelectStatement select,
CFMetaData metadata, Row row)
{
List<Column> thriftColumns = new ArrayList<Column>();
if (select.isColumnRange())
{
+ if (select.isWildcard())
+ {
+ // prepend key
+ thriftColumns.add(new
Column(metadata.getKeyName()).setValue(row.key.key).setTimestamp(-1));
+ }
+
// preserve comparator order
for (IColumn c : row.cf.getSortedColumns())
{
@@ -824,13 +834,23 @@ public class QueryProcessor
}
else
{
+ String keyString = getKeyString(metadata);
+
// order columns in the order they were asked for
for (Term term : select.getColumnNames())
{
+ if (term.getText().equalsIgnoreCase(keyString))
+ {
+ // preserve case of key as it was requested
+ ByteBuffer requestedKey =
ByteBufferUtil.bytes(term.getText());
+ thriftColumns.add(new
Column(requestedKey).setValue(row.key.key).setTimestamp(-1));
+ continue;
+ }
+
ByteBuffer name;
try
{
- name = term.getByteBuffer(comparator);
+ name = term.getByteBuffer(metadata.comparator);
}
catch (InvalidRequestException e)
{
@@ -846,6 +866,20 @@ public class QueryProcessor
return thriftColumns;
}
+ private static String getKeyString(CFMetaData metadata)
+ {
+ String keyString;
+ try
+ {
+ keyString = ByteBufferUtil.string(metadata.getKeyName());
+ }
+ catch (CharacterCodingException e)
+ {
+ throw new AssertionError(e);
+ }
+ return keyString;
+ }
+
private static CQLStatement getStatement(String queryStr) throws
InvalidRequestException, RecognitionException
{
// Lexer and parser
Modified:
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/SelectExpression.java
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/SelectExpression.java?rev=1101182&r1=1101181&r2=1101182&view=diff
==============================================================================
---
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/SelectExpression.java
(original)
+++
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/SelectExpression.java
Mon May 9 19:56:02 2011
@@ -37,6 +37,7 @@ public class SelectExpression
private int numColumns = MAX_COLUMNS_DEFAULT;
private boolean reverseColumns = false;
+ private final boolean wildcard;
private Term start, finish;
private List<Term> columns;
@@ -48,12 +49,13 @@ public class SelectExpression
* @param count the number of columns to limit the results to
* @param reverse true to reverse column order
*/
- public SelectExpression(Term start, Term finish, int count, boolean
reverse)
+ public SelectExpression(Term start, Term finish, int count, boolean
reverse, boolean wildcard)
{
this.start = start;
this.finish = finish;
numColumns = count;
reverseColumns = reverse;
+ this.wildcard = wildcard;
}
/**
@@ -65,6 +67,7 @@ public class SelectExpression
*/
public SelectExpression(Term first, int count, boolean reverse)
{
+ wildcard = false;
columns = new ArrayList<Term>();
columns.add(first);
numColumns = count;
@@ -125,4 +128,9 @@ public class SelectExpression
{
return columns;
}
+
+ public boolean isWildcard()
+ {
+ return wildcard;
+ }
}
Modified:
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/SelectStatement.java
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/SelectStatement.java?rev=1101182&r1=1101181&r2=1101182&view=diff
==============================================================================
---
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/SelectStatement.java
(original)
+++
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/SelectStatement.java
Mon May 9 19:56:02 2011
@@ -81,6 +81,11 @@ public class SelectStatement
{
return expression.isColumnRange();
}
+
+ public boolean isWildcard()
+ {
+ return expression.isWildcard();
+ }
public List<Term> getColumnNames()
{
Modified:
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/thrift/ThriftValidation.java
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/thrift/ThriftValidation.java?rev=1101182&r1=1101181&r2=1101182&view=diff
==============================================================================
---
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/thrift/ThriftValidation.java
(original)
+++
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/thrift/ThriftValidation.java
Mon May 9 19:56:02 2011
@@ -26,6 +26,7 @@ import java.util.*;
import org.apache.cassandra.config.*;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.AsciiType;
import org.apache.cassandra.db.marshal.MarshalException;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.RandomPartitioner;
@@ -515,6 +516,22 @@ public class ThriftValidation
{
try
{
+ if (cf_def.key_alias != null)
+ {
+ if (!cf_def.key_alias.hasRemaining())
+ throw new InvalidRequestException("key_alias may not be
empty");
+ try
+ {
+ // it's hard to use a key in a select statement if we
can't type it.
+ // for now let's keep it simple and require ascii.
+ AsciiType.instance.validate(cf_def.key_alias);
+ }
+ catch (MarshalException e)
+ {
+ throw new InvalidRequestException("Key aliases must be
ascii");
+ }
+ }
+
ColumnFamilyType cfType =
ColumnFamilyType.create(cf_def.column_type);
if (cfType == null)
throw new InvalidRequestException("invalid column type " +
cf_def.column_type);
Modified: cassandra/branches/cassandra-0.8/test/system/test_cql.py
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/test/system/test_cql.py?rev=1101182&r1=1101181&r2=1101182&view=diff
==============================================================================
--- cassandra/branches/cassandra-0.8/test/system/test_cql.py (original)
+++ cassandra/branches/cassandra-0.8/test/system/test_cql.py Mon May 9
19:56:02 2011
@@ -127,11 +127,11 @@ class TestCql(ThriftTester):
def test_select_simple(self):
"single-row named column queries"
cursor = init()
- cursor.execute("SELECT 'ca1' FROM StandardString1 WHERE KEY='ka'")
+ cursor.execute("SELECT KEY, ca1 FROM StandardString1 WHERE KEY='ka'")
r = cursor.fetchone()
d = cursor.description
- assert d[0][0] == cql.ROW_KEY
+ assert d[0][0] == 'KEY'
assert r[0] == 'ka'
assert d[1][0] == 'ca1'
@@ -144,10 +144,10 @@ class TestCql(ThriftTester):
""")
d = cursor.description
- assert ['Row Key', 'ca1', 'col', 'cd1'] == [col_dscptn[0] for
col_dscptn in d], d
+ assert ['ca1', 'col', 'cd1'] == [col_dscptn[0] for col_dscptn in d], d
row = cursor.fetchone()
# check that the column that didn't exist in the row comes back as null
- assert ['kd', None, 'val', 'vd1'] == row, row
+ assert [None, 'val', 'vd1'] == row, row
def test_select_row_range(self):
"retrieve a range of rows with columns"
@@ -219,51 +219,39 @@ class TestCql(ThriftTester):
"column slice tests"
cursor = init()
- # all columns
+ # * includes row key, explicit slice does not
cursor.execute("SELECT * FROM StandardString1 WHERE KEY = 'ka';")
- r = cursor.fetchone()
- assert len(r) == 3
+ row = cursor.fetchone()
+ assert ['ka', 'va1', 'val'] == row, row
+
cursor.execute("SELECT ''..'' FROM StandardString1 WHERE KEY = 'ka';")
- r = cursor.fetchone()
- assert len(r) == 3
+ row = cursor.fetchone()
+ assert ['va1', 'val'] == row, row
# column subsets
cursor.execute("SELECT 1..3 FROM StandardLongA WHERE KEY = 'aa';")
assert cursor.rowcount == 1
- r = cursor.fetchone()
- assert r[0] == "aa"
- assert r[1] == "1"
- assert r[2] == "2"
- assert r[3] == "3"
+ row = cursor.fetchone()
+ assert ['1', '2', '3'] == row, row
- cursor.execute("SELECT 10..30 FROM StandardIntegerA WHERE KEY='k1'")
- assert cursor.rowcount == 1
- r = cursor.fetchone()
- assert r[0] == "k1"
- assert r[1] == "a"
- assert r[2] == "b"
- assert r[3] == "c"
-
- # range of columns (slice) by row with FIRST
cursor.execute("""
- SELECT FIRST 1 1..3 FROM StandardLongA WHERE KEY = 'aa';
+ SELECT key,20,40 FROM StandardIntegerA
+ WHERE KEY > 'k1' AND KEY < 'k7' LIMIT 5
""")
+ row = cursor.fetchone()
+ assert ['k2', 'f', 'h'] == row, row
+
+ # range of columns (slice) by row with FIRST
+ cursor.execute("SELECT FIRST 1 1..3 FROM StandardLongA WHERE KEY =
'aa'")
assert cursor.rowcount == 1
- r = cursor.fetchone()
- assert len(r) == 2
- assert r[0] == "aa"
- assert r[1] == "1"
+ row = cursor.fetchone()
+ assert ['1'] == row, row
# range of columns (slice) by row reversed
- cursor.execute("""
- SELECT FIRST 2 REVERSED 3..1 FROM StandardLongA WHERE KEY = 'aa';
- """)
+ cursor.execute("SELECT FIRST 2 REVERSED 3..1 FROM StandardLongA WHERE
KEY = 'aa'")
assert cursor.rowcount == 1, "%d != 1" % cursor.rowcount
- r = cursor.fetchone()
- assert len(r) == 3
- assert r[0] == 'aa'
- assert r[1] == "3"
- assert r[2] == "2"
+ row = cursor.fetchone()
+ assert ['3', '2'] == row, row
def test_select_range_with_single_column_results(self):
"range should not fail when keys were not set"
@@ -277,7 +265,7 @@ class TestCql(ThriftTester):
""")
cursor.execute("""
- SELECT name FROM StandardString2
+ SELECT KEY, name FROM StandardString2
""")
assert cursor.rowcount == 3, "expected 3 results, got %d" %
cursor.rowcount
@@ -305,7 +293,7 @@ class TestCql(ThriftTester):
"indexed scan where column equals value"
cursor = init()
cursor.execute("""
- SELECT 'birthdate' FROM IndexedA WHERE 'birthdate' = 100
+ SELECT KEY, birthdate FROM IndexedA WHERE birthdate = 100
""")
assert cursor.rowcount == 2
@@ -321,19 +309,19 @@ class TestCql(ThriftTester):
"indexed scan where a column is greater than a value"
cursor = init()
cursor.execute("""
- SELECT 'birthdate' FROM IndexedA WHERE 'birthdate' = 100
- AND 'unindexed' > 200
+ SELECT KEY, 'birthdate' FROM IndexedA
+ WHERE 'birthdate' = 100 AND 'unindexed' > 200
""")
assert cursor.rowcount == 1
- r = cursor.fetchone()
- assert r[0] == "asmith"
+ row = cursor.fetchone()
+ assert row[0] == "asmith", row
def test_index_scan_with_start_key(self):
"indexed scan with a starting key"
cursor = init()
cursor.execute("""
- SELECT 'birthdate' FROM IndexedA WHERE 'birthdate' = 100
- AND KEY >= 'asmithZ'
+ SELECT KEY, 'birthdate' FROM IndexedA
+ WHERE 'birthdate' = 100 AND KEY >= 'asmithZ'
""")
assert cursor.rowcount == 1
r = cursor.fetchone()
@@ -342,7 +330,7 @@ class TestCql(ThriftTester):
def test_no_where_clause(self):
"empty where clause (range query w/o start key)"
cursor = init()
- cursor.execute("SELECT 'col' FROM StandardString1 LIMIT 3")
+ cursor.execute("SELECT KEY, 'col' FROM StandardString1 LIMIT 3")
assert cursor.rowcount == 3
rows = cursor.fetchmany(3)
assert rows[0][0] == "ka"
@@ -376,7 +364,8 @@ class TestCql(ThriftTester):
cursor.execute("""
SELECT 'cd1', 'col' FROM StandardString1 WHERE KEY = 'kd'
""")
- assert ['Row Key', 'cd1', 'col'] == [col_d[0] for col_d in
cursor.description]
+ desc = [col_d[0] for col_d in cursor.description]
+ assert ['cd1', 'col'] == desc, desc
cursor.execute("""
DELETE 'cd1', 'col' FROM StandardString1 WHERE KEY = 'kd'
@@ -384,31 +373,31 @@ class TestCql(ThriftTester):
cursor.execute("""
SELECT 'cd1', 'col' FROM StandardString1 WHERE KEY = 'kd'
""")
- r = cursor.fetchone()
- assert ['kd', None, None] == r, r
+ row = cursor.fetchone()
+ assert [None, None] == row, row
def test_delete_columns_multi_rows(self):
"delete columns from multiple rows"
cursor = init()
+ # verify rows exist initially
cursor.execute("SELECT 'col' FROM StandardString1 WHERE KEY = 'kc'")
- r = cursor.fetchone()
- assert ['kc', 'val'] == r, r
-
+ row = cursor.fetchone()
+ assert ['val'] == row, row
cursor.execute("SELECT 'col' FROM StandardString1 WHERE KEY = 'kd'")
- r = cursor.fetchone()
- assert ['kd', 'val'] == r, r
+ row = cursor.fetchone()
+ assert ['val'] == row, row
+ # delete and verify data is gone
cursor.execute("""
DELETE 'col' FROM StandardString1 WHERE KEY IN ('kc', 'kd')
""")
cursor.execute("SELECT 'col' FROM StandardString1 WHERE KEY = 'kc'")
- r = cursor.fetchone()
- assert ['kc', None] == r, r
-
+ row = cursor.fetchone()
+ assert [None] == row, row
cursor.execute("SELECT 'col' FROM StandardString1 WHERE KEY = 'kd'")
r = cursor.fetchone()
- assert ['kd', None] == r, r
+ assert [None] == r, r
def test_delete_rows(self):
"delete entire rows"
@@ -416,13 +405,13 @@ class TestCql(ThriftTester):
cursor.execute("""
SELECT 'cd1', 'col' FROM StandardString1 WHERE KEY = 'kd'
""")
- assert ['Row Key', 'cd1', 'col'] == [col_d[0] for col_d in
cursor.description]
+ assert ['cd1', 'col'] == [col_d[0] for col_d in cursor.description]
cursor.execute("DELETE FROM StandardString1 WHERE KEY = 'kd'")
cursor.execute("""
SELECT 'cd1', 'col' FROM StandardString1 WHERE KEY = 'kd'
""")
- r = cursor.fetchone()
- assert ['kd', None, None] == r, r
+ row = cursor.fetchone()
+ assert [None, None] == row, row
def test_create_keyspace(self):
"create a new keyspace"
@@ -578,7 +567,7 @@ class TestCql(ThriftTester):
SELECT '%s' FROM StandardTimeUUID WHERE KEY = 'uuidtest'
""" % str(timeuuid))
d = cursor.description
- assert d[1][0] == timeuuid, "%s, %s" % (str(d[1][0]), str(timeuuid))
+ assert d[0][0] == timeuuid, "%s, %s" % (str(d[1][0]), str(timeuuid))
# Tests a node-side conversion from bigint to UUID.
ms = uuid1bytes_to_millis(uuid.uuid1().bytes)
@@ -590,7 +579,7 @@ class TestCql(ThriftTester):
SELECT 'id' FROM StandardTimeUUIDValues WHERE KEY = 'uuidtest'
""")
r = cursor.fetchone()
- assert uuid1bytes_to_millis(r[1].bytes) == ms
+ assert uuid1bytes_to_millis(r[0].bytes) == ms
# Tests a node-side conversion from ISO8601 to UUID.
cursor.execute("""
@@ -603,7 +592,7 @@ class TestCql(ThriftTester):
""")
# 2011-01-31 17:00:00-0000 == 1296493200000ms
r = cursor.fetchone()
- ms = uuid1bytes_to_millis(r[1].bytes)
+ ms = uuid1bytes_to_millis(r[0].bytes)
assert ms == 1296493200000, \
"%d != 1296493200000 (2011-01-31 17:00:00-0000)" % ms
@@ -617,7 +606,7 @@ class TestCql(ThriftTester):
SELECT 'id3' FROM StandardTimeUUIDValues WHERE KEY = 'uuidtest'
""")
r = cursor.fetchone()
- ms = uuid1bytes_to_millis(r[1].bytes)
+ ms = uuid1bytes_to_millis(r[0].bytes)
assert ((time.time() * 1e3) - ms) < 100, \
"new timeuuid not within 100ms of now (UPDATE vs. SELECT)"
@@ -631,7 +620,7 @@ class TestCql(ThriftTester):
SELECT :start..:finish FROM StandardTimeUUID WHERE KEY = slicetest
""", dict(start=uuid_range[0],
finish=uuid_range[len(uuid_range)-1]))
d = cursor.description
- for (i, col_d) in enumerate(d[1:]):
+ for (i, col_d) in enumerate(d):
assert uuid_range[i] == col_d[0]
@@ -645,7 +634,7 @@ class TestCql(ThriftTester):
cursor.execute("SELECT :name FROM StandardUUID WHERE KEY = 'uuidtest'",
dict(name=uid))
d = cursor.description
- assert d[1][0] == uid, "expected %s, got %s (%s)" % \
+ assert d[0][0] == uid, "expected %s, got %s (%s)" % \
(uid.bytes.encode('hex'), str(d[1][0]).encode('hex'), d[1][1])
# TODO: slices of uuids from cf w/ LexicalUUIDType comparator
@@ -661,18 +650,19 @@ class TestCql(ThriftTester):
cursor.execute("SELECT * FROM StandardUtf82 WHERE KEY = k1")
d = cursor.description
+ assert d[0][0] == 'KEY', d[0][0]
assert d[1][0] == u"¢", d[1][0]
assert d[2][0] == u"©", d[2][0]
assert d[3][0] == u"®", d[3][0]
assert d[4][0] == u"¿", d[4][0]
cursor.execute("SELECT :start..'' FROM StandardUtf82 WHERE KEY = k1",
dict(start="©"))
- r = cursor.fetchone()
- assert len(r) == 4
+ row = cursor.fetchone()
+ assert len(row) == 3, row
d = cursor.description
- assert d[1][0] == u"©"
- assert d[2][0] == u"®"
- assert d[3][0] == u"¿"
+ assert d[0][0] == u"©"
+ assert d[1][0] == u"®"
+ assert d[2][0] == u"¿"
def test_read_write_negative_numerics(self):
"reading and writing negative numeric values"
@@ -685,11 +675,11 @@ class TestCql(ThriftTester):
cursor.execute("SELECT :start..:finish FROM :cf WHERE KEY =
negatives;",
dict(start=-10, finish=-1, cf=cf))
r = cursor.fetchone()
- assert len(r) == 11, \
+ assert len(r) == 10, \
"returned %d columns, expected %d" % (len(r) - 1, 10)
d = cursor.description
- assert d[1][0] == -10
- assert d[10][0] == -1
+ assert d[0][0] == -10
+ assert d[9][0] == -1
def test_escaped_quotes(self):
"reading and writing strings w/ escaped quotes"
@@ -704,17 +694,17 @@ class TestCql(ThriftTester):
""", dict(key="test_escaped_quotes"))
assert cursor.rowcount == 1
r = cursor.fetchone()
- assert len(r) == 2, "wrong number of results"
+ assert len(r) == 1, "wrong number of results"
d = cursor.description
- assert d[1][0] == "x\'and\'y"
-
+ assert d[0][0] == "x'and'y"
+
def test_typed_keys(self):
"using typed keys"
cursor = init()
cursor.execute("SELECT * FROM StandardString1 WHERE KEY = :key",
dict(key="ka"))
- r = cursor.fetchone()
- assert isinstance(r[0], unicode), \
- "wrong key-type returned, expected unicode, got %s" % type(r[0])
+ row = cursor.fetchone()
+ assert isinstance(row[0], unicode), \
+ "wrong key-type returned, expected unicode, got %s" % type(row[0])
# FIXME: The above is woefully inadequate, but the test config uses
# CollatingOrderPreservingPartitioner which only supports UTF8.
@@ -760,8 +750,6 @@ class TestCql(ThriftTester):
assert cursor.rowcount == 1, "expected 1 result, got %d" %
cursor.rowcount
colnames = [col_d[0] for col_d in cursor.description]
- assert colnames[1] == "some_name", \
- "unrecognized name '%s'" % colnames[1]
- r = cursor.fetchone()
- assert r[1] == "some_value", \
- "unrecognized value '%s'" % r[1]
+ assert ['some_name'] == colnames, colnames
+ row = cursor.fetchone()
+ assert ['some_value'] == row, row