Author: jbellis Date: Sat Jun 11 01:45:16 2011 New Revision: 1134502 URL: http://svn.apache.org/viewvc?rev=1134502&view=rev Log: add cql key alias support patch by pyaskevich; reviewed by jbellis for CASSANDRA-2480
Modified: cassandra/branches/cassandra-0.8/CHANGES.txt cassandra/branches/cassandra-0.8/doc/cql/CQL.textile cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/config/CFMetaData.java cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/AbstractModification.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/CreateColumnFamilyStatement.java cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/DeleteStatement.java cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/QueryProcessor.java cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/SelectStatement.java cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/UpdateStatement.java cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/WhereClause.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=1134502&r1=1134501&r2=1134502&view=diff ============================================================================== --- cassandra/branches/cassandra-0.8/CHANGES.txt (original) +++ cassandra/branches/cassandra-0.8/CHANGES.txt Sat Jun 11 01:45:16 2011 @@ -10,6 +10,7 @@ - DROP INDEX (CASSANDRA-2617) - add SCHEMA/TABLE as aliases for KS/CF (CASSANDRA-2743) - server handles wait-for-schema-agreement (CASSANDRA-2756) + - key alias support (CASSANDRA-2480) * add support for comparator parameters and a generic ReverseType (CASSANDRA-2355) * add CompositeType and DynamicCompositeType (CASSANDRA-2231) Modified: cassandra/branches/cassandra-0.8/doc/cql/CQL.textile URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/doc/cql/CQL.textile?rev=1134502&r1=1134501&r2=1134502&view=diff ============================================================================== --- cassandra/branches/cassandra-0.8/doc/cql/CQL.textile (original) +++ cassandra/branches/cassandra-0.8/doc/cql/CQL.textile Sat Jun 11 01:45:16 2011 @@ -54,11 +54,11 @@ Following the column family clause is an h3. Filtering rows bc. -SELECT ... WHERE KEY = keyname AND name1 = value1 -SELECT ... WHERE KEY >= startkey and KEY =< endkey AND name1 = value1 -SELECT ... WHERE KEY IN ('<key>', '<key>', '<key>', ...) +SELECT ... WHERE <KEY> = keyname AND name1 = value1 +SELECT ... WHERE <KEY> >= startkey and <KEY> =< endkey AND name1 = value1 +SELECT ... WHERE <KEY> IN ('<key>', '<key>', '<key>', ...) -The WHERE clause provides for filtering the rows that appear in results. The clause can filter on a key name, or range of keys, and in the case of indexed columns, on column values. Key filters are specified using the @KEY@ keyword, a relational operator, (one of @=@, @>@, @>=@, @<@, and @<=@), and a term value. When terms appear on both sides of a relational operator it is assumed the filter applies to an indexed column. With column index filters, the term on the left of the operator is the name, the term on the right is the value to filter __on__. +The WHERE clause provides for filtering the rows that appear in results. The clause can filter on a key name, or range of keys, and in the case of indexed columns, on column values. Key filters are specified using the @KEY@ keyword or key alias name, a relational operator, (one of @=@, @>@, @>=@, @<@, and @<=@), and a term value. When terms appear on both sides of a relational operator it is assumed the filter applies to an indexed column. With column index filters, the term on the left of the operator is the name, the term on the right is the value to filter __on__. __Note: The greater-than and less-than operators (@>@ and @<@) result in key ranges that are inclusive of the terms. There is no supported notion of "strictly" greater-than or less-than; these operators are merely supported as aliases to @>=@ and @<=@.__ @@ -85,7 +85,7 @@ h2. INSERT _Synopsis:_ bc. -INSERT INTO <COLUMN FAMILY> (KEY, <col>, <col>, ...) VALUES (<key>, <val>, <val>, ...) [USING CONSISTENCY <LEVEL> [AND TIMESTAMP <timestamp>] [AND TTL <timeToLive>]]; +INSERT INTO <COLUMN FAMILY> (<KEY>, <col>, <col>, ...) VALUES (<key>, <val>, <val>, ...) [USING CONSISTENCY <LEVEL> [AND TIMESTAMP <timestamp>] [AND TTL <timeToLive>]]; An @INSERT@ is used to write one or more columns to a record in a Cassandra column family. No results are returned. @@ -97,9 +97,9 @@ _Synopsis:_ bc. UPDATE <COLUMN FAMILY> [USING <CONSISTENCY> [AND TIMESTAMP <timestamp>] [AND TTL <timeToLive>]] - SET name1 = value1, name2 = value2 WHERE KEY = keyname; + SET name1 = value1, name2 = value2 WHERE <KEY> = keyname; -An @UPDATE@ is used to write one or more columns to a record in a Cassandra column family. No results are returned. +An @UPDATE@ is used to write one or more columns to a record in a Cassandra column family. No results are returned. Key can be given using @KEY@ keyword or by alias set per ColumnFamily. h3. Column Family @@ -132,20 +132,20 @@ UPDATE ... [USING TTL <timeToLive>] ... h3. Specifying Columns and Row bc. -UPDATE ... SET name1 = value1, name2 = value2 WHERE KEY = keyname; -UPDATE ... SET name1 = value1, name2 = value2 WHERE KEY IN ('<key>', '<key>', ...) +UPDATE ... SET name1 = value1, name2 = value2 WHERE <KEY> = keyname; +UPDATE ... SET name1 = value1, name2 = value2 WHERE <KEY> IN ('<key>', '<key>', ...) -Rows are created or updated by supplying column names and values in term assignment format. Multiple columns can be set by separating the name/value pairs using commas. Each update statement requires exactly one key to be specified using a WHERE clause and the @KEY@ keyword. +Rows are created or updated by supplying column names and values in term assignment format. Multiple columns can be set by separating the name/value pairs using commas. Each update statement requires exactly one key to be specified using a WHERE clause and the @KEY@ keyword or key alias. h2. DELETE _Synopsis:_ bc. -DELETE [COLUMNS] FROM <COLUMN FAMILY> [USING <CONSISTENCY>] WHERE KEY = keyname1 -DELETE [COLUMNS] FROM <COLUMN FAMILY> [USING <CONSISTENCY>] WHERE KEY IN (keyname1, keyname2); +DELETE [COLUMNS] FROM <COLUMN FAMILY> [USING <CONSISTENCY>] WHERE <KEY> = keyname1 +DELETE [COLUMNS] FROM <COLUMN FAMILY> [USING <CONSISTENCY>] WHERE <KEY> IN (keyname1, keyname2); -A @DELETE@ is used to perform the removal of one or more columns from one or more rows. +A @DELETE@ is used to perform the removal of one or more columns from one or more rows. Key can be given using @KEY@ keyword or by alias set per ColumnFamily. h3. Specifying Columns @@ -171,10 +171,10 @@ Following the column family identifier i h3(#deleterows). Specifying Rows bc. -DELETE ... WHERE KEY = keyname1 -DELETE ... WHERE KEY IN (keyname1, keyname2) +DELETE ... WHERE <KEY> = keyname1 +DELETE ... WHERE <KEY> IN (keyname1, keyname2) -The @WHERE@ clause is used to determine which row(s) a @DELETE@ applies to. The first form allows the specification of a single keyname using the @KEY@ keyword and the @=@ operator. The second form allows a list of keyname terms to be specified using the @IN@ notation and a parenthesized list of comma-delimited keyname terms. +The @WHERE@ clause is used to determine which row(s) a @DELETE@ applies to. The first form allows the specification of a single keyname using the @KEY@ keyword (or by key alias) and the @=@ operator. The second form allows a list of keyname terms to be specified using the @IN@ notation and a parenthesized list of comma-delimited keyname terms. h2. BATCH @@ -229,8 +229,8 @@ h2. CREATE COLUMNFAMILY _Synopsis:_ bc. -CREATE COLUMNFAMILY <COLUMN FAMILY> (KEY <type> PRIMARY KEY [, name1 type, name2 type, ...]); -CREATE COLUMNFAMILY <COLUMN FAMILY> (KEY <type> PRIMARY KEY [, name1 type, name2 type, ...]) +CREATE COLUMNFAMILY <COLUMN FAMILY> (<KEY> <type> PRIMARY KEY [, name1 type, name2 type, ...]); +CREATE COLUMNFAMILY <COLUMN FAMILY> (<KEY> <type> PRIMARY KEY [, name1 type, name2 type, ...]) [WITH keyword1 = arg1 [AND keyword2 = arg2 [AND ...]]]; @CREATE COLUMNFAMILY@ statements create new column family namespaces under the current keyspace. Valid column family names are strings of alphanumeric characters and underscores, which begin with a letter. @@ -238,14 +238,14 @@ CREATE COLUMNFAMILY <COLUMN FAMILY> (KEY h3(#keytypes). Specifying Key Type bc. -CREATE ... (KEY <type> PRIMARY KEY) ... +CREATE ... (<KEY> <type> PRIMARY KEY) ... -When creating a new column family, you must specify key type. The list of possible key types is identical to column comparators/validators, (see "Specifying Column Type":columntypes). It's important to note that the key type must be compatible with the partitioner in use, for example @OrderPreservingPartitioner@ and @CollatingOrderPreservingPartitioner@ both require UTF-8 keys. +When creating a new column family, you must specify key type. The list of possible key types is identical to column comparators/validators, (see "Specifying Column Type":columntypes). It's important to note that the key type must be compatible with the partitioner in use, for example @OrderPreservingPartitioner@ and @CollatingOrderPreservingPartitioner@ both require UTF-8 keys. If you use name instead of @KEY@ keyword, name alias will be set automatically. h3(#columntypes). Specifying Column Type (optional) bc. -CREATE ... (KEY <type> PRIMARY KEY, name1 type, name2 type) ... +CREATE ... (<KEY> <type> PRIMARY KEY, name1 type, name2 type) ... It is possible to assign columns a type during column family creation. Columns configured with a type are validated accordingly when a write occurs. Column types are specified as a parenthesized, comma-separated list of column term and type pairs. The list of recognized types are: 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=1134502&r1=1134501&r2=1134502&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 Sat Jun 11 01:45:16 2011 @@ -61,7 +61,8 @@ public final class CFMetaData public final static double DEFAULT_MEMTABLE_OPERATIONS_IN_MILLIONS = sizeMemtableOperations(DEFAULT_MEMTABLE_THROUGHPUT_IN_MB); public final static double DEFAULT_MERGE_SHARDS_CHANCE = 0.1; public final static String DEFAULT_ROW_CACHE_PROVIDER = "org.apache.cassandra.cache.ConcurrentLinkedHashCacheProvider"; - + public final static ByteBuffer DEFAULT_KEY_NAME = ByteBufferUtil.bytes("KEY"); + private static final int MIN_CF_ID = 1000; private static final AtomicInteger idGen = new AtomicInteger(MIN_CF_ID); @@ -73,7 +74,6 @@ 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. Modified: cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/AbstractModification.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/AbstractModification.java?rev=1134502&r1=1134501&r2=1134502&view=diff ============================================================================== --- cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/AbstractModification.java (original) +++ cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/AbstractModification.java Sat Jun 11 01:45:16 2011 @@ -20,14 +20,13 @@ */ package org.apache.cassandra.cql; +import java.util.List; + import org.apache.cassandra.db.IMutation; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.thrift.ConsistencyLevel; import org.apache.cassandra.thrift.InvalidRequestException; -import java.nio.ByteBuffer; -import java.util.List; - public abstract class AbstractModification { public static final ConsistencyLevel defaultConsistency = ConsistencyLevel.ONE; @@ -36,18 +35,20 @@ public abstract class AbstractModificati protected final ConsistencyLevel cLevel; protected final Long timestamp; protected final int timeToLive; + protected final String keyName; - public AbstractModification(String columnFamily, Attributes attrs) + public AbstractModification(String columnFamily, String keyAlias, Attributes attrs) { - this(columnFamily, attrs.getConsistencyLevel(), attrs.getTimestamp(), attrs.getTimeToLive()); + this(columnFamily, keyAlias, attrs.getConsistencyLevel(), attrs.getTimestamp(), attrs.getTimeToLive()); } - public AbstractModification(String columnFamily, ConsistencyLevel cLevel, Long timestamp, int timeToLive) + public AbstractModification(String columnFamily, String keyAlias, ConsistencyLevel cLevel, Long timestamp, int timeToLive) { this.columnFamily = columnFamily; this.cLevel = cLevel; this.timestamp = timestamp; this.timeToLive = timeToLive; + this.keyName = keyAlias.toUpperCase(); } public String getColumnFamily() 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=1134502&r1=1134501&r2=1134502&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 Sat Jun 11 01:45:16 2011 @@ -185,8 +185,9 @@ whereClause returns [WhereClause clause] } : first=relation { $clause = new WhereClause(first); } (K_AND next=relation { $clause.and(next); })* - | K_KEY K_IN '(' f1=term { inClause.andKeyEquals(f1); } - (',' fN=term { inClause.andKeyEquals(fN); } )* ')' + | key_alias=term { inClause.setKeyAlias(key_alias.getText()); } + K_IN '(' f1=term { inClause.andKeyEquals(f1); } + (',' fN=term { inClause.andKeyEquals(fN); } )* ')' { $clause = inClause; } ; @@ -212,12 +213,12 @@ insertStatement returns [UpdateStatement List<Term> columnValues = new ArrayList<Term>(); } K_INSERT K_INTO columnFamily=( IDENT | STRING_LITERAL | INTEGER ) - '(' K_KEY ( ',' column_name=term { columnNames.add($column_name.item); } )+ ')' + '(' key_alias=term ( ',' column_name=term { columnNames.add($column_name.item); } )+ ')' K_VALUES '(' key=term ( ',' column_value=term { columnValues.add($column_value.item); })+ ')' ( usingClause[attrs] )? { - return new UpdateStatement($columnFamily.text, columnNames, columnValues, Collections.singletonList(key), attrs); + return new UpdateStatement($columnFamily.text, key_alias.getText(), columnNames, columnValues, Collections.singletonList(key), attrs); } ; @@ -298,11 +299,11 @@ updateStatement returns [UpdateStatement K_UPDATE columnFamily=( IDENT | STRING_LITERAL | INTEGER ) ( usingClause[attrs] )? K_SET termPairWithOperation[columns] (',' termPairWithOperation[columns])* - K_WHERE ( K_KEY '=' key=term { keyList = Collections.singletonList(key); } - | - K_KEY K_IN '(' keys=termList { keyList = $keys.items; } ')' ) + K_WHERE ( key_alias=term ('=' key=term { keyList = Collections.singletonList(key); } + | + K_IN '(' keys=termList { keyList = $keys.items; } ')' )) { - return new UpdateStatement($columnFamily.text, columns, keyList, attrs); + return new UpdateStatement($columnFamily.text, key_alias.getText(), columns, keyList, attrs); } ; @@ -326,11 +327,11 @@ deleteStatement returns [DeleteStatement ( cols=termList { columnsList = $cols.items; })? K_FROM columnFamily=( IDENT | STRING_LITERAL | INTEGER ) ( K_USING K_CONSISTENCY K_LEVEL { cLevel = ConsistencyLevel.valueOf($K_LEVEL.text); } )? - K_WHERE ( K_KEY '=' key=term { keyList = Collections.singletonList(key); } - | K_KEY K_IN '(' keys=termList { keyList = $keys.items; } ')' + K_WHERE ( key_alias=term ('=' key=term { keyList = Collections.singletonList(key); } + | K_IN '(' keys=termList { keyList = $keys.items; } ')') )? { - return new DeleteStatement(columnsList, $columnFamily.text, cLevel, keyList); + return new DeleteStatement(columnsList, $columnFamily.text, key_alias.getText(), cLevel, keyList); } ; @@ -366,7 +367,7 @@ createColumnFamilyStatement returns [Cre createCfamColumns[CreateColumnFamilyStatement expr] : n=term v=createCfamColumnValidator { $expr.addColumn(n, $v.validator); } - | K_KEY v=createCfamColumnValidator K_PRIMARY K_KEY { $expr.setKeyType($v.validator); } + | k=term v=createCfamColumnValidator K_PRIMARY K_KEY { $expr.setKeyAlias(k.getText()); $expr.setKeyType($v.validator); } ; createCfamColumnValidator returns [String validator] @@ -451,9 +452,8 @@ termPairWithOperation[Map<Term, Operatio // Note: ranges are inclusive so >= and >, and < and <= all have the same semantics. relation returns [Relation rel] - : { Term entity = new Term("KEY", STRING_LITERAL); } - (name=term { entity = $name.item; } ) type=('=' | '<' | '<=' | '>=' | '>') t=term - { return new Relation(entity, $type.text, $t.item); } + : name=term type=('=' | '<' | '<=' | '>=' | '>') t=term + { return new Relation($name.item, $type.text, $t.item); } ; // TRUNCATE <CF>; Modified: cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java?rev=1134502&r1=1134501&r2=1134502&view=diff ============================================================================== --- cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java (original) +++ cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java Sat Jun 11 01:45:16 2011 @@ -35,6 +35,7 @@ import org.apache.cassandra.db.ColumnFam import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.TypeParser; import org.apache.cassandra.thrift.InvalidRequestException; +import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; /** A <code>CREATE COLUMNFAMILY</code> parsed from a CQL query statement. */ @@ -95,7 +96,8 @@ public class CreateColumnFamilyStatement private final Map<Term, String> columns = new HashMap<Term, String>(); private final Map<String, String> properties = new HashMap<String, String>(); private List<String> keyValidator = new ArrayList<String>(); - + private ByteBuffer keyAlias = null; + public CreateColumnFamilyStatement(String name) { this.name = name; @@ -180,7 +182,14 @@ public class CreateColumnFamilyStatement { return keyValidator.get(0); } - + + public void setKeyAlias(String alias) + { + // if we got KEY in input we don't need to set an alias + if (!alias.toUpperCase().equals("KEY")) + keyAlias = ByteBufferUtil.bytes(alias); + } + /** Map a keyword to the corresponding value */ public void addProperty(String name, String value) { @@ -268,7 +277,8 @@ public class CreateColumnFamilyStatement .mergeShardsChance(0.0) .columnMetadata(getColumns(comparator)) .keyValidator(TypeParser.parse(comparators.get(getKeyType()))) - .rowCacheProvider(FBUtilities.newCacheProvider(getPropertyString(KW_ROW_CACHE_PROVIDER, CFMetaData.DEFAULT_ROW_CACHE_PROVIDER))); + .rowCacheProvider(FBUtilities.newCacheProvider(getPropertyString(KW_ROW_CACHE_PROVIDER, CFMetaData.DEFAULT_ROW_CACHE_PROVIDER))) + .keyAlias(keyAlias); } catch (ConfigurationException e) { Modified: cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/DeleteStatement.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/DeleteStatement.java?rev=1134502&r1=1134501&r2=1134502&view=diff ============================================================================== --- cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/DeleteStatement.java (original) +++ cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/DeleteStatement.java Sat Jun 11 01:45:16 2011 @@ -47,9 +47,9 @@ public class DeleteStatement extends Abs private List<Term> columns; private List<Term> keys; - public DeleteStatement(List<Term> columns, String columnFamily, ConsistencyLevel cLevel, List<Term> keys) + public DeleteStatement(List<Term> columns, String columnFamily, String keyName, ConsistencyLevel cLevel, List<Term> keys) { - super(columnFamily, cLevel, null, 0); + super(columnFamily, keyName, cLevel, null, 0); this.columns = columns; this.keys = keys; @@ -102,6 +102,7 @@ public class DeleteStatement extends Abs public void mutationForKey(RowMutation mutation, String keyspace, Long timestamp) throws InvalidRequestException { CFMetaData metadata = validateColumnFamily(keyspace, columnFamily); + QueryProcessor.validateKeyAlias(metadata, keyName); AbstractType comparator = metadata.getComparatorFor(null); 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=1134502&r1=1134501&r2=1134502&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 Sat Jun 11 01:45:16 2011 @@ -65,6 +65,8 @@ public class QueryProcessor private static final long timeLimitForSchemaAgreement = 10 * 1000; + public static final String DEFAULT_KEY_NAME = bufferToString(CFMetaData.DEFAULT_KEY_NAME); + private static List<org.apache.cassandra.db.Row> getSlice(String keyspace, SelectStatement select) throws InvalidRequestException, TimedOutException, UnavailableException { @@ -401,6 +403,14 @@ public class QueryProcessor } } + public static void validateKeyAlias(CFMetaData cfm, String key) throws InvalidRequestException + { + assert key.toUpperCase().equals(key); // should always be uppercased by caller + String realKeyAlias = bufferToString(cfm.getKeyName()).toUpperCase(); + if (!realKeyAlias.equals(key)) + throw new InvalidRequestException(String.format("Expected key '%s' to be present in WHERE clause for '%s'", key, cfm.cfName)); + } + private static void validateColumnNames(Iterable<ByteBuffer> columns) throws InvalidRequestException { @@ -500,8 +510,15 @@ public class QueryProcessor SelectStatement select = (SelectStatement)statement.statement; clientState.hasColumnFamilyAccess(select.getColumnFamily(), Permission.READ); metadata = validateColumnFamily(keyspace, select.getColumnFamily()); + + // need to do this in here because we need a CFMD.getKeyName() + select.extractKeyAliasFromColumns(metadata); + + if (select.getKeys().size() > 0) + validateKeyAlias(metadata, select.getKeyAlias()); + validateSelect(keyspace, select); - + List<org.apache.cassandra.db.Row> rows; // By-key @@ -969,4 +986,16 @@ public class QueryProcessor throw new SchemaDisagreementException(); } + + private static String bufferToString(ByteBuffer string) + { + try + { + return ByteBufferUtil.string(string); + } + catch (CharacterCodingException e) + { + throw new RuntimeException(e.getMessage(), e); + } + } } 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=1134502&r1=1134501&r2=1134502&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 Sat Jun 11 01:45:16 2011 @@ -23,6 +23,7 @@ package org.apache.cassandra.cql; import java.nio.ByteBuffer; import java.util.List; +import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.thrift.ConsistencyLevel; @@ -142,6 +143,16 @@ public class SelectStatement return clause.includeFinishKey(); } + public String getKeyAlias() + { + return clause.getKeyAlias(); + } + + public void extractKeyAliasFromColumns(CFMetaData cfm) + { + clause.extractKeysFromColumns(cfm); + } + public AbstractType getComparator(String keyspace) { return DatabaseDescriptor.getComparator(keyspace, columnFamily); Modified: cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/UpdateStatement.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/UpdateStatement.java?rev=1134502&r1=1134501&r2=1134502&view=diff ============================================================================== --- cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/UpdateStatement.java (original) +++ cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/UpdateStatement.java Sat Jun 11 01:45:16 2011 @@ -49,22 +49,24 @@ public class UpdateStatement extends Abs private Map<Term, Operation> columns; private List<Term> columnNames, columnValues; private List<Term> keys; - + /** * Creates a new UpdateStatement from a column family name, columns map, consistency * level, and key term. * * @param columnFamily column family name + * @param keyName alias key name * @param columns a map of column name/values pairs * @param keys the keys to update * @param attrs additional attributes for statement (CL, timestamp, timeToLive) */ public UpdateStatement(String columnFamily, + String keyName, Map<Term, Operation> columns, List<Term> keys, Attributes attrs) { - super(columnFamily, attrs); + super(columnFamily, keyName, attrs); this.columns = columns; this.keys = keys; @@ -76,18 +78,20 @@ public class UpdateStatement extends Abs * alternate update format, <code>INSERT</code>. * * @param columnFamily column family name + * @param keyName alias key name * @param columnNames list of column names * @param columnValues list of column values (corresponds to names) * @param keys the keys to update * @param attrs additional attributes for statement (CL, timestamp, timeToLive) */ public UpdateStatement(String columnFamily, + String keyName, List<Term> columnNames, List<Term> columnValues, List<Term> keys, Attributes attrs) { - super(columnFamily, attrs); + super(columnFamily, keyName, attrs); this.columnNames = columnNames; this.columnValues = columnValues; @@ -139,6 +143,8 @@ public class UpdateStatement extends Abs CFMetaData metadata = validateColumnFamily(keyspace, columnFamily, hasCommutativeOperation); + QueryProcessor.validateKeyAlias(metadata, keyName); + // Avoid unnecessary authorizations. if (!(cfamsSeen.contains(columnFamily))) { Modified: cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/WhereClause.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/WhereClause.java?rev=1134502&r1=1134501&r2=1134502&view=diff ============================================================================== --- cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/WhereClause.java (original) +++ cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/WhereClause.java Sat Jun 11 01:45:16 2011 @@ -21,7 +21,15 @@ package org.apache.cassandra.cql; */ +import org.apache.cassandra.config.CFMetaData; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.thrift.InvalidRequestException; +import org.apache.cassandra.thrift.ThriftValidation; +import org.apache.cassandra.utils.ByteBufferUtil; + +import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; /** @@ -30,10 +38,13 @@ import java.util.List; */ public class WhereClause { + // added to either by the parser, e.g. from an IN clause, or by extractKeysFromColumns private List<Term> keys = new ArrayList<Term>(); private Term startKey, finishKey; private List<Relation> columns = new ArrayList<Relation>(); private boolean includeStartKey = false, includeFinishKey = false; + // set by extractKeysFromColumns + private String keyAlias = null; /** * Create a new WhereClause with the first parsed relation. @@ -96,11 +107,6 @@ public class WhereClause return startKey != null; } - public boolean isKeyList() - { - return !isKeyRange(); - } - public Term getStartKey() { return startKey; @@ -125,4 +131,47 @@ public class WhereClause { return includeFinishKey; } + + public void setKeyAlias(String alias) + { + keyAlias = alias.toUpperCase(); + } + + public String getKeyAlias() + { + // TODO fix special casing here, key alias should always be set post-extract + // key alias as not related to keys in here, it can be unset when we have a query like + // SELECT * FROM <CF> WHERE key = 1 and col > 2 and col < 3; + // it will be always set when statement looks like this + // SELECT * FROM <CF> WHERE <key> IN (.., .., ..); + // key is NULL when KEY keyword is used or when key alias given by user was not recognized + // validateKeyAlias will throw an exception for us in that case + return keyAlias == null ? QueryProcessor.DEFAULT_KEY_NAME : keyAlias; + } + + public void extractKeysFromColumns(CFMetaData cfm) + { + ByteBuffer realKeyAlias = cfm.getKeyName(); + + if (!keys.isEmpty()) + return; // we already have key(s) set + + Iterator<Relation> iter = columns.iterator(); + + while (iter.hasNext()) + { + Relation relation = iter.next(); + + ByteBuffer name = ByteBufferUtil.bytes(relation.getEntity().getText()); + + if (name.equals(realKeyAlias)) + { + // setting found key as an alias + keyAlias = relation.getEntity().getText().toUpperCase(); + keys.add(relation.getValue()); // add a key value to the keys list + iter.remove(); // removing it from the columns + break; + } + } + } } 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=1134502&r1=1134501&r2=1134502&view=diff ============================================================================== --- cassandra/branches/cassandra-0.8/test/system/test_cql.py (original) +++ cassandra/branches/cassandra-0.8/test/system/test_cql.py Sat Jun 11 01:45:16 2011 @@ -1185,3 +1185,90 @@ class TestCql(ThriftTester): assert_raises(cql.ProgrammingError, cursor.execute, "UPDATE CounterCF SET count_me = count_not_me + 2 WHERE key = 'counter1'") + + def test_key_alias_support(self): + "should be possible to use alias instead of KEY keyword" + cursor = init() + + cursor.execute(""" + CREATE SCHEMA KeyAliasKeyspace WITH strategy_options:replication_factor = '1' + AND strategy_class = 'SimpleStrategy'; + """) + cursor.execute("USE KeyAliasKeyspace;") + + # create a Column Family with key alias + cursor.execute(""" + CREATE COLUMNFAMILY KeyAliasCF ( + 'id' varint PRIMARY KEY, + 'username' text + ) WITH comment = 'shiny, new, cf' AND default_validation = ascii; + """) + + # TODO: temporary (until this can be done with CQL). + ksdef = thrift_client.describe_keyspace("KeyAliasKeyspace") + cfdef = ksdef.cf_defs[0] + + assert len(ksdef.cf_defs) == 1, \ + "expected 1 column family total, found %d" % len(ksdef.cf_defs) + assert cfdef.key_alias == 'id', "expected 'id' alias, got %s" % cfdef.key_alias + + # try do insert/update + cursor.execute("INSERT INTO KeyAliasCF (id, username) VALUES (1, jbellis)") + + # check if we actually stored anything + cursor.execute("SELECT * FROM KeyAliasCF WHERE id = 1") + assert cursor.rowcount == 1, "expected 1 results, got %d" % cursor.rowcount + colnames = [col_d[0] for col_d in cursor.description] + assert len(colnames) == 2 + + r = cursor.fetchone() + assert len(r) == 2, "expected 2, got %d" % len(r) + assert r[0] == 1 + assert r[1] == 'jbellis' + + cursor.execute("UPDATE KeyAliasCF SET username = 'xedin' WHERE id = 2") + + # check if we actually stored anything + cursor.execute("SELECT * FROM KeyAliasCF WHERE id = 2") + assert cursor.rowcount == 1, "expected 1 results, got %d" % cursor.rowcount + colnames = [col_d[0] for col_d in cursor.description] + assert len(colnames) == 2 + + r = cursor.fetchone() + assert len(r) == 2, "expected 2, got %d" % len(r) + assert r[0] == 2 + assert r[1] == 'xedin' + + # delete with key alias + cursor.execute("DELETE FROM KeyAliasCF WHERE id = 2") + # check if we actually stored anything + cursor.execute("SELECT * FROM KeyAliasCF WHERE id = 2") + assert cursor.rowcount == 1, "expected 1 results, got %d" % cursor.rowcount + + r = cursor.fetchone() + assert len(r) == 1, "expected 1, got %s" % r + assert r[0] == 2, "expected id = 2, got %d" % r[0] + + # if alias was set you can't use KEY keyword anymore + assert_raises(cql.ProgrammingError, + cursor.execute, + "INSERT INTO KeyAliasCF (KEY, username) VALUES (6, jbellis)") + + assert_raises(cql.ProgrammingError, + cursor.execute, + "UPDATE KeyAliasCF SET username = 'xedin' WHERE KEY = 7") + + assert_raises(cql.ProgrammingError, + cursor.execute, + "DELETE FROM KeyAliasCF WHERE KEY = 2") + + assert_raises(cql.ProgrammingError, + cursor.execute, + "SELECT * FROM KeyAliasCF WHERE KEY = 2") + + assert_raises(cql.ProgrammingError, + cursor.execute, + "SELECT * FROM KeyAliasCF WHERE KEY IN (1, 2)") + + cursor.execute("USE Keyspace1") + cursor.execute("DROP KEYSPACE KeyAliasKeyspace")