Support for non-frozen UDTS Patch by Tyler Hobbs; reviewed by Benjamin Lerer for CASSANDRA-7423
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/677230df Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/677230df Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/677230df Branch: refs/heads/trunk Commit: 677230df694752c7ecf6d5459eee60ad7cf45ecf Parents: 66fb8f5 Author: Tyler Hobbs <[email protected]> Authored: Fri Apr 8 11:56:39 2016 -0500 Committer: Tyler Hobbs <[email protected]> Committed: Fri Apr 8 15:26:12 2016 -0500 ---------------------------------------------------------------------- CHANGES.txt | 2 + bin/cqlsh.py | 3 +- doc/cql3/CQL.textile | 27 +- pylib/cqlshlib/cql3handling.py | 86 +++- pylib/cqlshlib/test/test_cqlsh_completion.py | 6 +- src/antlr/Parser.g | 20 +- .../cassandra/config/ColumnDefinition.java | 16 +- .../apache/cassandra/cql3/AbstractMarker.java | 26 +- .../org/apache/cassandra/cql3/CQL3Type.java | 86 ++-- .../apache/cassandra/cql3/ColumnCondition.java | 505 ++++++++++++++----- .../apache/cassandra/cql3/ColumnIdentifier.java | 4 +- .../org/apache/cassandra/cql3/Constants.java | 2 +- .../org/apache/cassandra/cql3/Operation.java | 145 ++++-- src/java/org/apache/cassandra/cql3/Tuples.java | 15 +- .../apache/cassandra/cql3/UntypedResultSet.java | 2 +- .../org/apache/cassandra/cql3/UserTypes.java | 186 ++++++- .../cql3/functions/AbstractFunction.java | 2 +- .../cassandra/cql3/functions/FunctionCall.java | 24 +- .../cassandra/cql3/functions/UDAggregate.java | 2 +- .../restrictions/StatementRestrictions.java | 10 +- .../cassandra/cql3/selection/Selectable.java | 22 +- .../cassandra/cql3/selection/Selection.java | 8 +- .../cassandra/cql3/selection/Selector.java | 2 +- .../cql3/statements/AlterTypeStatement.java | 10 +- .../cql3/statements/CreateTableStatement.java | 33 +- .../cql3/statements/CreateTypeStatement.java | 6 +- .../cql3/statements/DeleteStatement.java | 2 +- .../cql3/statements/ModificationStatement.java | 2 +- .../cql3/statements/SelectStatement.java | 12 +- .../cql3/statements/UpdateStatement.java | 6 +- .../cassandra/db/marshal/AbstractType.java | 21 + .../cassandra/db/marshal/CollectionType.java | 30 +- .../apache/cassandra/db/marshal/TupleType.java | 45 +- .../apache/cassandra/db/marshal/TypeParser.java | 6 +- .../apache/cassandra/db/marshal/UserType.java | 166 +++++- .../org/apache/cassandra/db/rows/CellPath.java | 13 +- .../cassandra/db/rows/ComplexColumnData.java | 1 - .../org/apache/cassandra/schema/Functions.java | 2 +- .../cassandra/schema/LegacySchemaMigrator.java | 2 +- src/java/org/apache/cassandra/schema/Types.java | 36 +- .../cassandra/service/MigrationManager.java | 1 + .../apache/cassandra/transport/DataType.java | 2 +- .../cassandra/cql3/CQL3TypeLiteralTest.java | 2 +- .../org/apache/cassandra/cql3/CQLTester.java | 80 ++- .../cassandra/cql3/ColumnConditionTest.java | 36 +- .../selection/SelectionColumnMappingTest.java | 2 +- .../cql3/validation/entities/UserTypesTest.java | 463 +++++++++++------ .../operations/InsertUpdateIfConditionTest.java | 371 ++++++++++++++ .../schema/LegacySchemaMigratorTest.java | 23 +- .../cassandra/transport/SerDeserTest.java | 3 +- 50 files changed, 2054 insertions(+), 523 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 74ba07e..5b71af1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,6 @@ 3.6 + * Support for non-frozen user-defined types, updating + individual fields of user-defined types (CASSANDRA-7423) * Make LZ4 compression level configurable (CASSANDRA-11051) * Allow per-partition LIMIT clause in CQL (CASSANDRA-7017) * Make custom filtering more extensible with UserExpression (CASSANDRA-11295) http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/bin/cqlsh.py ---------------------------------------------------------------------- diff --git a/bin/cqlsh.py b/bin/cqlsh.py index c3fcc48..2593486 100644 --- a/bin/cqlsh.py +++ b/bin/cqlsh.py @@ -898,8 +898,7 @@ class Shell(cmd.Cmd): except KeyError: raise UserTypeNotFound("User type %r not found" % typename) - return [(field_name, field_type.cql_parameterized_type()) - for field_name, field_type in zip(user_type.field_names, user_type.field_types)] + return zip(user_type.field_names, user_type.field_types) def get_userfunction_names(self, ksname=None): if ksname is None: http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/doc/cql3/CQL.textile ---------------------------------------------------------------------- diff --git a/doc/cql3/CQL.textile b/doc/cql3/CQL.textile index 83899d2..b0173a6 100644 --- a/doc/cql3/CQL.textile +++ b/doc/cql3/CQL.textile @@ -870,13 +870,17 @@ bc(syntax).. | <identifier> '=' <identifier> ('+' | '-') (<int-term> | <set-literal> | <list-literal>) | <identifier> '=' <identifier> '+' <map-literal> | <identifier> '[' <term> ']' '=' <term> + | <identifier> '.' <field> '=' <term> <condition> ::= <identifier> <op> <term> - | <identifier> IN (<variable> | '(' ( <term> ( ',' <term> )* )? ')') + | <identifier> IN <in-values> | <identifier> '[' <term> ']' <op> <term> - | <identifier> '[' <term> ']' IN <term> + | <identifier> '[' <term> ']' IN <in-values> + | <identifier> '.' <field> <op> <term> + | <identifier> '.' <field> IN <in-values> <op> ::= '<' | '<=' | '=' | '!=' | '>=' | '>' +<in-values> ::= (<variable> | '(' ( <term> ( ',' <term> )* )? ')') <where-clause> ::= <relation> ( AND <relation> )* @@ -913,6 +917,8 @@ The @c = c + 3@ form of @<assignment>@ is used to increment/decrement counters. The @id = id + <collection-literal>@ and @id[value1] = value2@ forms of @<assignment>@ are for collections. Please refer to the "relevant section":#collections for more details. +The @id.field = <term>@ form of @<assignemt>@ is for setting the value of a single field on a non-frozen user-defined types. + h4(#updateOptions). @<options>@ The @UPDATE@ and @INSERT@ statements support the following options: @@ -931,7 +937,9 @@ bc(syntax).. WHERE <where-clause> ( IF ( EXISTS | ( <condition> ( AND <condition> )*) ) )? -<selection> ::= <identifier> ( '[' <term> ']' )? +<selection> ::= <identifier> + | <identifier> '[' <term> ']' + | <identifier> '.' <field> <where-clause> ::= <relation> ( AND <relation> )* @@ -943,11 +951,14 @@ bc(syntax).. | '(' <identifier> (',' <identifier>)* ')' IN <variable> <op> ::= '=' | '<' | '>' | '<=' | '>=' +<in-values> ::= (<variable> | '(' ( <term> ( ',' <term> )* )? ')') <condition> ::= <identifier> (<op> | '!=') <term> - | <identifier> IN (<variable> | '(' ( <term> ( ',' <term> )* )? ')') + | <identifier> IN <in-values> | <identifier> '[' <term> ']' (<op> | '!=') <term> - | <identifier> '[' <term> ']' IN <term> + | <identifier> '[' <term> ']' IN <in-values> + | <identifier> '.' <field> (<op> | '!=') <term> + | <identifier> '.' <field> IN <in-values> p. __Sample:__ @@ -957,7 +968,7 @@ DELETE FROM NerdMovies USING TIMESTAMP 1240003134 WHERE movie = 'Serenity'; DELETE phone FROM Users WHERE userid IN (C73DE1D3-AF08-40F3-B124-3FF3E5109F22, B70DE1D0-9908-4AE3-BE34-5573E5B09F14); p. -The @DELETE@ statement deletes columns and rows. If column names are provided directly after the @DELETE@ keyword, only those columns are deleted from the row indicated by the @<where-clause>@ (the @id[value]@ syntax in @<selection>@ is for collection, please refer to the "collection section":#collections for more details). Otherwise, whole rows are removed. The @<where-clause>@ specifies which rows are to be deleted. Multiple rows may be deleted with one statement by using an @IN@ clause. A range of rows may be deleted using an inequality operator (such as @>=@). +The @DELETE@ statement deletes columns and rows. If column names are provided directly after the @DELETE@ keyword, only those columns are deleted from the row indicated by the @<where-clause>@. The @id[value]@ syntax in @<selection>@ is for non-frozen collections (please refer to the "collection section":#collections for more details). The @id.field@ syntax is for the deletion of non-frozen user-defined types. Otherwise, whole rows are removed. The @<where-clause>@ specifies which rows are to be deleted. Multiple rows may be deleted with one statement by using an @IN@ clause. A range of rows may be deleted using an inequality operator (such as @>=@). @DELETE@ supports the @TIMESTAMP@ option with the same semantics as the "@UPDATE@":#updateStmt statement. @@ -2318,6 +2329,10 @@ h3. 3.4.2 * "@ALTER TABLE@":#alterTableStmt @ADD@ and @DROP@ now allow mutiple columns to be added/removed * New "@PER PARTITION LIMIT@":#selectLimit option (see "CASSANDRA-7017":https://issues.apache.org/jira/browse/CASSANDRA-7017). +h3. 3.4.2 + +* User-defined types may now be stored in a non-frozen form, allowing individual fields to be updated and deleted in "@UPDATE@ statements":#updateStmt and "@DELETE@ statements":#deleteStmt, respectively. (CASSANDRA-7423) + h3. 3.4.1 * Adds @CAST@ functions. See "@Cast@":#castFun. http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/pylib/cqlshlib/cql3handling.py ---------------------------------------------------------------------- diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py index 673234b..7abed2c 100644 --- a/pylib/cqlshlib/cql3handling.py +++ b/pylib/cqlshlib/cql3handling.py @@ -552,13 +552,13 @@ def ks_name_completer(ctxt, cass): @completer_for('nonSystemKeyspaceName', 'ksname') -def ks_name_completer(ctxt, cass): +def non_system_ks_name_completer(ctxt, cass): ksnames = [n for n in cass.get_keyspace_names() if n not in SYSTEM_KEYSPACES] return map(maybe_escape_name, ksnames) @completer_for('alterableKeyspaceName', 'ksname') -def ks_name_completer(ctxt, cass): +def alterable_ks_name_completer(ctxt, cass): ksnames = [n for n in cass.get_keyspace_names() if n not in NONALTERBALE_KEYSPACES] return map(maybe_escape_name, ksnames) @@ -864,7 +864,6 @@ def insert_newval_completer(ctxt, cass): @completer_for('insertStatement', 'valcomma') def insert_valcomma_completer(ctxt, cass): - layout = get_table_meta(ctxt, cass) numcols = len(ctxt.get_binding('colname', ())) numvals = len(ctxt.get_binding('newval', ())) if numcols > numvals: @@ -888,21 +887,27 @@ syntax_rules += r''' ( "IF" ( "EXISTS" | <conditions> ))? ; <assignment> ::= updatecol=<cident> - ( "=" update_rhs=( <term> | <cident> ) + (( "=" update_rhs=( <term> | <cident> ) ( counterop=( "+" | "-" ) inc=<wholenumber> - | listadder="+" listcol=<cident> )? - | indexbracket="[" <term> "]" "=" <term> ) + | listadder="+" listcol=<cident> )? ) + | ( indexbracket="[" <term> "]" "=" <term> ) + | ( udt_field_dot="." udt_field=<identifier> "=" <term> )) ; <conditions> ::= <condition> ( "AND" <condition> )* ; -<condition> ::= <cident> ( "[" <term> "]" )? (("=" | "<" | ">" | "<=" | ">=" | "!=") <term> - | "IN" "(" <term> ( "," <term> )* ")") +<condition_op_and_rhs> ::= (("=" | "<" | ">" | "<=" | ">=" | "!=") <term>) + | ("IN" "(" <term> ( "," <term> )* ")" ) + ; +<condition> ::= conditioncol=<cident> + ( (( indexbracket="[" <term> "]" ) + |( udt_field_dot="." udt_field=<identifier> )) )? + <condition_op_and_rhs> ; ''' @completer_for('updateStatement', 'updateopt') -def insert_option_completer(ctxt, cass): +def update_option_completer(ctxt, cass): opts = set('TIMESTAMP TTL'.split()) for opt in ctxt.get_binding('updateopt', ()): opts.discard(opt.split()[0]) @@ -971,6 +976,62 @@ def update_indexbracket_completer(ctxt, cass): return ['['] return [] + +@completer_for('assignment', 'udt_field_dot') +def update_udt_field_dot_completer(ctxt, cass): + layout = get_table_meta(ctxt, cass) + curcol = dequote_name(ctxt.get_binding('updatecol', '')) + return ["."] if _is_usertype(layout, curcol) else [] + + +@completer_for('assignment', 'udt_field') +def assignment_udt_field_completer(ctxt, cass): + layout = get_table_meta(ctxt, cass) + curcol = dequote_name(ctxt.get_binding('updatecol', '')) + return _usertype_fields(ctxt, cass, layout, curcol) + + +def _is_usertype(layout, curcol): + coltype = layout.columns[curcol].cql_type + return coltype not in simple_cql_types and coltype not in ('map', 'set', 'list') + + +def _usertype_fields(ctxt, cass, layout, curcol): + if not _is_usertype(layout, curcol): + return [] + + coltype = layout.columns[curcol].cql_type + ks = ctxt.get_binding('ksname', None) + if ks is not None: + ks = dequote_name(ks) + user_type = cass.get_usertype_layout(ks, coltype) + return [field_name for (field_name, field_type) in user_type] + + +@completer_for('condition', 'indexbracket') +def condition_indexbracket_completer(ctxt, cass): + layout = get_table_meta(ctxt, cass) + curcol = dequote_name(ctxt.get_binding('conditioncol', '')) + coltype = layout.columns[curcol].cql_type + if coltype in ('map', 'list'): + return ['['] + return [] + + +@completer_for('condition', 'udt_field_dot') +def condition_udt_field_dot_completer(ctxt, cass): + layout = get_table_meta(ctxt, cass) + curcol = dequote_name(ctxt.get_binding('conditioncol', '')) + return ["."] if _is_usertype(layout, curcol) else [] + + +@completer_for('condition', 'udt_field') +def condition_udt_field_completer(ctxt, cass): + layout = get_table_meta(ctxt, cass) + curcol = dequote_name(ctxt.get_binding('conditioncol', '')) + return _usertype_fields(ctxt, cass, layout, curcol) + + syntax_rules += r''' <deleteStatement> ::= "DELETE" ( <deleteSelector> ( "," <deleteSelector> )* )? "FROM" cf=<columnFamilyName> @@ -978,7 +1039,9 @@ syntax_rules += r''' "WHERE" <whereClause> ( "IF" ( "EXISTS" | <conditions> ) )? ; -<deleteSelector> ::= delcol=<cident> ( memberbracket="[" memberselector=<term> "]" )? +<deleteSelector> ::= delcol=<cident> + ( ( "[" <term> "]" ) + | ( "." <identifier> ) )? ; <deleteOption> ::= "TIMESTAMP" <wholenumber> ; @@ -998,6 +1061,7 @@ def delete_delcol_completer(ctxt, cass): layout = get_table_meta(ctxt, cass) return map(maybe_escape_name, regular_column_names(layout)) + syntax_rules += r''' <batchStatement> ::= "BEGIN" ( "UNLOGGED" | "COUNTER" )? "BATCH" ( "USING" [batchopt]=<usingOption> @@ -1459,7 +1523,7 @@ def get_trigger_names(ctxt, cass): @completer_for('dropTriggerStatement', 'triggername') -def alter_type_field_completer(ctxt, cass): +def drop_trigger_completer(ctxt, cass): names = get_trigger_names(ctxt, cass) return map(maybe_escape_name, names) http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/pylib/cqlshlib/test/test_cqlsh_completion.py ---------------------------------------------------------------------- diff --git a/pylib/cqlshlib/test/test_cqlsh_completion.py b/pylib/cqlshlib/test/test_cqlsh_completion.py index 0f0cc4d..1f1fb48 100644 --- a/pylib/cqlshlib/test/test_cqlsh_completion.py +++ b/pylib/cqlshlib/test/test_cqlsh_completion.py @@ -367,12 +367,12 @@ class TestCqlshCompletion(CqlshCompletionCase): choices=['EXISTS', '<quotedName>', '<identifier>']) self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE TOKEN(lonelykey) <= TOKEN(13) IF EXISTS ", - choices=['>=', '!=', '<=', 'IN', '[', ';', '=', '<', '>']) + choices=['>=', '!=', '<=', 'IN', '[', ';', '=', '<', '>', '.']) def test_complete_in_delete(self): self.trycompletions('DELETE F', choices=['FROM', '<identifier>', '<quotedName>']) - self.trycompletions('DELETE a ', choices=['FROM', '[', ',']) + self.trycompletions('DELETE a ', choices=['FROM', '[', '.', ',']) self.trycompletions('DELETE a [', choices=['<wholenumber>', 'false', '-', '<uuid>', '<pgStringLiteral>', '<float>', 'TOKEN', @@ -449,7 +449,7 @@ class TestCqlshCompletion(CqlshCompletionCase): choices=['EXISTS', '<identifier>', '<quotedName>']) self.trycompletions(('DELETE FROM twenty_rows_composite_table USING TIMESTAMP 0 WHERE ' 'TOKEN(a) >= TOKEN(0) IF b '), - choices=['>=', '!=', '<=', 'IN', '[', '=', '<', '>']) + choices=['>=', '!=', '<=', 'IN', '=', '<', '>']) self.trycompletions(('DELETE FROM twenty_rows_composite_table USING TIMESTAMP 0 WHERE ' 'TOKEN(a) >= TOKEN(0) IF b < 0 '), choices=['AND', ';']) http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/antlr/Parser.g ---------------------------------------------------------------------- diff --git a/src/antlr/Parser.g b/src/antlr/Parser.g index 36d4e20..cd17475 100644 --- a/src/antlr/Parser.g +++ b/src/antlr/Parser.g @@ -429,6 +429,7 @@ deleteSelection returns [List<Operation.RawDeletion> operations] deleteOp returns [Operation.RawDeletion op] : c=cident { $op = new Operation.ColumnDeletion(c); } | c=cident '[' t=term ']' { $op = new Operation.ElementDeletion(c, t); } + | c=cident '.' field=cident { $op = new Operation.FieldDeletion(c, field); } ; usingClauseDelete[Attributes.Raw attrs] @@ -1282,7 +1283,8 @@ columnOperation[List<Pair<ColumnIdentifier.Raw, Operation.RawUpdate>> operations columnOperationDifferentiator[List<Pair<ColumnIdentifier.Raw, Operation.RawUpdate>> operations, ColumnIdentifier.Raw key] : '=' normalColumnOperation[operations, key] - | '[' k=term ']' specializedColumnOperation[operations, key, k] + | '[' k=term ']' collectionColumnOperation[operations, key, k] + | '.' field=cident udtColumnOperation[operations, key, field] ; normalColumnOperation[List<Pair<ColumnIdentifier.Raw, Operation.RawUpdate>> operations, ColumnIdentifier.Raw key] @@ -1315,13 +1317,20 @@ normalColumnOperation[List<Pair<ColumnIdentifier.Raw, Operation.RawUpdate>> oper } ; -specializedColumnOperation[List<Pair<ColumnIdentifier.Raw, Operation.RawUpdate>> operations, ColumnIdentifier.Raw key, Term.Raw k] +collectionColumnOperation[List<Pair<ColumnIdentifier.Raw, Operation.RawUpdate>> operations, ColumnIdentifier.Raw key, Term.Raw k] : '=' t=term { addRawUpdate(operations, key, new Operation.SetElement(k, t)); } ; +udtColumnOperation[List<Pair<ColumnIdentifier.Raw, Operation.RawUpdate>> operations, ColumnIdentifier.Raw key, ColumnIdentifier.Raw field] + : '=' t=term + { + addRawUpdate(operations, key, new Operation.SetField(field, t)); + } + ; + columnCondition[List<Pair<ColumnIdentifier.Raw, ColumnCondition.Raw>> conditions] // Note: we'll reject duplicates later : key=cident @@ -1337,6 +1346,13 @@ columnCondition[List<Pair<ColumnIdentifier.Raw, ColumnCondition.Raw>> conditions | marker=inMarker { conditions.add(Pair.create(key, ColumnCondition.Raw.collectionInCondition(element, marker))); } ) ) + | '.' field=cident + ( op=relationType t=term { conditions.add(Pair.create(key, ColumnCondition.Raw.udtFieldCondition(t, field, op))); } + | K_IN + ( values=singleColumnInValues { conditions.add(Pair.create(key, ColumnCondition.Raw.udtFieldInCondition(field, values))); } + | marker=inMarker { conditions.add(Pair.create(key, ColumnCondition.Raw.udtFieldInCondition(field, marker))); } + ) + ) ) ; http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/config/ColumnDefinition.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/config/ColumnDefinition.java b/src/java/org/apache/cassandra/config/ColumnDefinition.java index 6bcc2e0..2c2cbb7 100644 --- a/src/java/org/apache/cassandra/config/ColumnDefinition.java +++ b/src/java/org/apache/cassandra/config/ColumnDefinition.java @@ -164,10 +164,13 @@ public class ColumnDefinition extends ColumnSpecification implements Comparable< private static Comparator<CellPath> makeCellPathComparator(Kind kind, AbstractType<?> type) { - if (kind.isPrimaryKeyKind() || !type.isCollection() || !type.isMultiCell()) + if (kind.isPrimaryKeyKind() || !type.isMultiCell()) return null; - CollectionType collection = (CollectionType) type; + AbstractType<?> nameComparator = type.isCollection() + ? ((CollectionType) type).nameComparator() + : ((UserType) type).nameComparator(); + return new Comparator<CellPath>() { @@ -184,7 +187,7 @@ public class ColumnDefinition extends ColumnSpecification implements Comparable< // This will get more complicated once we have non-frozen UDT and nested collections assert path1.size() == 1 && path2.size() == 1; - return collection.nameComparator().compare(path1.get(0), path2.get(0)); + return nameComparator.compare(path1.get(0), path2.get(0)); } }; } @@ -365,8 +368,11 @@ public class ColumnDefinition extends ColumnSpecification implements Comparable< if (!isComplex()) throw new MarshalException("Only complex cells should have a cell path"); - assert type instanceof CollectionType; - ((CollectionType)type).nameComparator().validate(path.get(0)); + assert type.isMultiCell(); + if (type.isCollection()) + ((CollectionType)type).nameComparator().validate(path.get(0)); + else + ((UserType)type).nameComparator().validate(path.get(0)); } public static String toCQLString(Iterable<ColumnDefinition> defs) http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/AbstractMarker.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/AbstractMarker.java b/src/java/org/apache/cassandra/cql3/AbstractMarker.java index d2bc022..e490bbf 100644 --- a/src/java/org/apache/cassandra/cql3/AbstractMarker.java +++ b/src/java/org/apache/cassandra/cql3/AbstractMarker.java @@ -67,16 +67,26 @@ public abstract class AbstractMarker extends Term.NonTerminal public NonTerminal prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { - if (!(receiver.type instanceof CollectionType)) - return new Constants.Marker(bindIndex, receiver); - - switch (((CollectionType)receiver.type).kind) + if (receiver.type.isCollection()) + { + switch (((CollectionType) receiver.type).kind) + { + case LIST: + return new Lists.Marker(bindIndex, receiver); + case SET: + return new Sets.Marker(bindIndex, receiver); + case MAP: + return new Maps.Marker(bindIndex, receiver); + default: + throw new AssertionError(); + } + } + else if (receiver.type.isUDT()) { - case LIST: return new Lists.Marker(bindIndex, receiver); - case SET: return new Sets.Marker(bindIndex, receiver); - case MAP: return new Maps.Marker(bindIndex, receiver); + return new UserTypes.Marker(bindIndex, receiver); } - throw new AssertionError(); + + return new Constants.Marker(bindIndex, receiver); } public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/CQL3Type.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/CQL3Type.java b/src/java/org/apache/cassandra/cql3/CQL3Type.java index 98c0d97..d5dfeed 100644 --- a/src/java/org/apache/cassandra/cql3/CQL3Type.java +++ b/src/java/org/apache/cassandra/cql3/CQL3Type.java @@ -39,7 +39,16 @@ public interface CQL3Type { static final Logger logger = LoggerFactory.getLogger(CQL3Type.class); - public boolean isCollection(); + default boolean isCollection() + { + return false; + } + + default boolean isUDT() + { + return false; + } + public AbstractType<?> getType(); /** @@ -82,11 +91,6 @@ public interface CQL3Type this.type = type; } - public boolean isCollection() - { - return false; - } - public AbstractType<?> getType() { return type; @@ -125,11 +129,6 @@ public interface CQL3Type this(TypeParser.parse(className)); } - public boolean isCollection() - { - return false; - } - public AbstractType<?> getType() { return type; @@ -305,9 +304,9 @@ public interface CQL3Type return new UserDefined(UTF8Type.instance.compose(type.name), type); } - public boolean isCollection() + public boolean isUDT() { - return false; + return true; } public AbstractType<?> getType() @@ -377,7 +376,10 @@ public interface CQL3Type @Override public String toString() { - return "frozen<" + ColumnIdentifier.maybeQuote(name) + '>'; + if (type.isMultiCell()) + return ColumnIdentifier.maybeQuote(name); + else + return "frozen<" + ColumnIdentifier.maybeQuote(name) + '>'; } } @@ -395,11 +397,6 @@ public interface CQL3Type return new Tuple(type); } - public boolean isCollection() - { - return false; - } - public AbstractType<?> getType() { return type; @@ -485,12 +482,7 @@ public interface CQL3Type { protected boolean frozen = false; - protected abstract boolean supportsFreezing(); - - public boolean isCollection() - { - return false; - } + public abstract boolean supportsFreezing(); public boolean isFrozen() { @@ -507,6 +499,11 @@ public interface CQL3Type return false; } + public boolean isUDT() + { + return false; + } + public String keyspace() { return null; @@ -588,7 +585,7 @@ public interface CQL3Type return type; } - protected boolean supportsFreezing() + public boolean supportsFreezing() { return false; } @@ -627,7 +624,7 @@ public interface CQL3Type frozen = true; } - protected boolean supportsFreezing() + public boolean supportsFreezing() { return true; } @@ -652,7 +649,7 @@ public interface CQL3Type assert values != null : "Got null values type for a collection"; if (!frozen && values.supportsFreezing() && !values.frozen) - throw new InvalidRequestException("Non-frozen collections are not allowed inside collections: " + this); + throwNestedNonFrozenError(values); // we represent Thrift supercolumns as maps, internally, and we do allow counters in supercolumns. Thus, // for internal type parsing (think schema) we have to make an exception and allow counters as (map) values @@ -664,22 +661,31 @@ public interface CQL3Type if (keys.isCounter()) throw new InvalidRequestException("Counters are not allowed inside collections: " + this); if (!frozen && keys.supportsFreezing() && !keys.frozen) - throw new InvalidRequestException("Non-frozen collections are not allowed inside collections: " + this); + throwNestedNonFrozenError(keys); } + AbstractType<?> valueType = values.prepare(keyspace, udts).getType(); switch (kind) { case LIST: - return new Collection(ListType.getInstance(values.prepare(keyspace, udts).getType(), !frozen)); + return new Collection(ListType.getInstance(valueType, !frozen)); case SET: - return new Collection(SetType.getInstance(values.prepare(keyspace, udts).getType(), !frozen)); + return new Collection(SetType.getInstance(valueType, !frozen)); case MAP: assert keys != null : "Got null keys type for a collection"; - return new Collection(MapType.getInstance(keys.prepare(keyspace, udts).getType(), values.prepare(keyspace, udts).getType(), !frozen)); + return new Collection(MapType.getInstance(keys.prepare(keyspace, udts).getType(), valueType, !frozen)); } throw new AssertionError(); } + private void throwNestedNonFrozenError(Raw innerType) + { + if (innerType instanceof RawCollection) + throw new InvalidRequestException("Non-frozen collections are not allowed inside collections: " + this); + else + throw new InvalidRequestException("Non-frozen UDTs are not allowed inside collections: " + this); + } + public boolean referencesUserType(String name) { return (keys != null && keys.referencesUserType(name)) || values.referencesUserType(name); @@ -721,7 +727,7 @@ public interface CQL3Type public boolean canBeNonFrozen() { - return false; + return true; } public CQL3Type prepare(String keyspace, Types udts) throws InvalidRequestException @@ -744,9 +750,8 @@ public interface CQL3Type if (type == null) throw new InvalidRequestException("Unknown type " + name); - if (!frozen) - throw new InvalidRequestException("Non-frozen User-Defined types are not supported, please use frozen<>"); - + if (frozen) + type = type.freeze(); return new UserDefined(name.toString(), type); } @@ -755,7 +760,12 @@ public interface CQL3Type return this.name.getStringTypeName().equals(name); } - protected boolean supportsFreezing() + public boolean supportsFreezing() + { + return true; + } + + public boolean isUDT() { return true; } @@ -776,7 +786,7 @@ public interface CQL3Type this.types = types; } - protected boolean supportsFreezing() + public boolean supportsFreezing() { return true; } http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/ColumnCondition.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/ColumnCondition.java b/src/java/org/apache/cassandra/cql3/ColumnCondition.java index 0c2ea2a..8db0ecc 100644 --- a/src/java/org/apache/cassandra/cql3/ColumnCondition.java +++ b/src/java/org/apache/cassandra/cql3/ColumnCondition.java @@ -23,6 +23,7 @@ import java.util.*; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; +import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.db.rows.*; @@ -31,8 +32,6 @@ import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.transport.Server; import org.apache.cassandra.utils.ByteBufferUtil; -import static com.google.common.collect.Lists.newArrayList; - /** * A CQL3 condition on the value of a column or collection element. For example, "UPDATE .. IF a = 0". */ @@ -43,51 +42,71 @@ public class ColumnCondition // For collection, when testing the equality of a specific element, null otherwise. private final Term collectionElement; + // For UDT, when testing the equality of a specific field, null otherwise. + private final ColumnIdentifier field; + private final Term value; // a single value or a marker for a list of IN values private final List<Term> inValues; public final Operator operator; - private ColumnCondition(ColumnDefinition column, Term collectionElement, Term value, List<Term> inValues, Operator op) + private ColumnCondition(ColumnDefinition column, Term collectionElement, ColumnIdentifier field, Term value, List<Term> inValues, Operator op) { this.column = column; this.collectionElement = collectionElement; + this.field = field; this.value = value; this.inValues = inValues; this.operator = op; + assert field == null || collectionElement == null; if (operator != Operator.IN) assert this.inValues == null; } public static ColumnCondition condition(ColumnDefinition column, Term value, Operator op) { - return new ColumnCondition(column, null, value, null, op); + return new ColumnCondition(column, null, null, value, null, op); } public static ColumnCondition condition(ColumnDefinition column, Term collectionElement, Term value, Operator op) { - return new ColumnCondition(column, collectionElement, value, null, op); + return new ColumnCondition(column, collectionElement, null, value, null, op); + } + + public static ColumnCondition condition(ColumnDefinition column, ColumnIdentifier udtField, Term value, Operator op) + { + return new ColumnCondition(column, null, udtField, value, null, op); } public static ColumnCondition inCondition(ColumnDefinition column, List<Term> inValues) { - return new ColumnCondition(column, null, null, inValues, Operator.IN); + return new ColumnCondition(column, null, null, null, inValues, Operator.IN); } public static ColumnCondition inCondition(ColumnDefinition column, Term collectionElement, List<Term> inValues) { - return new ColumnCondition(column, collectionElement, null, inValues, Operator.IN); + return new ColumnCondition(column, collectionElement, null, null, inValues, Operator.IN); + } + + public static ColumnCondition inCondition(ColumnDefinition column, ColumnIdentifier udtField, List<Term> inValues) + { + return new ColumnCondition(column, null, udtField, null, inValues, Operator.IN); } public static ColumnCondition inCondition(ColumnDefinition column, Term inMarker) { - return new ColumnCondition(column, null, inMarker, null, Operator.IN); + return new ColumnCondition(column, null, null, inMarker, null, Operator.IN); } public static ColumnCondition inCondition(ColumnDefinition column, Term collectionElement, Term inMarker) { - return new ColumnCondition(column, collectionElement, inMarker, null, Operator.IN); + return new ColumnCondition(column, collectionElement, null, inMarker, null, Operator.IN); + } + + public static ColumnCondition inCondition(ColumnDefinition column, ColumnIdentifier udtField, Term inMarker) + { + return new ColumnCondition(column, null, udtField, inMarker, null, Operator.IN); } public Iterable<Function> getFunctions() @@ -131,11 +150,19 @@ public class ColumnCondition boolean isInCondition = operator == Operator.IN; if (column.type instanceof CollectionType) { - if (collectionElement == null) + if (collectionElement != null) + return isInCondition ? new ElementAccessInBound(this, options) : new ElementAccessBound(this, options); + else return isInCondition ? new CollectionInBound(this, options) : new CollectionBound(this, options); + } + else if (column.type.isUDT()) + { + if (field != null) + return isInCondition ? new UDTFieldAccessInBound(this, options) : new UDTFieldAccessBound(this, options); else - return isInCondition ? new ElementAccessInBound(this, options) : new ElementAccessBound(this, options); + return isInCondition ? new UDTInBound(this, options) : new UDTBound(this, options); } + return isInCondition ? new SimpleInBound(this, options) : new SimpleBound(this, options); } @@ -216,6 +243,35 @@ public class ColumnCondition return complexData == null ? Collections.<Cell>emptyIterator() : complexData.iterator(); } + private static boolean evaluateComparisonWithOperator(int comparison, Operator operator) + { + // called when comparison != 0 + switch (operator) + { + case EQ: + return false; + case LT: + case LTE: + return comparison < 0; + case GT: + case GTE: + return comparison > 0; + case NEQ: + return true; + default: + throw new AssertionError(); + } + } + + private static ByteBuffer cellValueAtIndex(Iterator<Cell> iter, int index) + { + int adv = Iterators.advance(iter, index); + if (adv == index && iter.hasNext()) + return iter.next().value(); + else + return null; + } + /** * A condition on a single non-collection column. This does not support IN operators (see SimpleInBound). */ @@ -226,7 +282,7 @@ public class ColumnCondition private SimpleBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException { super(condition.column, condition.operator); - assert !(column.type instanceof CollectionType) && condition.collectionElement == null; + assert !(column.type instanceof CollectionType) && condition.field == null; assert condition.operator != Operator.IN; this.value = condition.value.bindAndGet(options); } @@ -247,7 +303,7 @@ public class ColumnCondition private SimpleInBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException { super(condition.column, condition.operator); - assert !(column.type instanceof CollectionType) && condition.collectionElement == null; + assert !(column.type instanceof CollectionType) && condition.field == null; assert condition.operator == Operator.IN; if (condition.inValues == null) this.inValues = ((Lists.Value) condition.value.bind(options)).getElements(); @@ -311,7 +367,7 @@ public class ColumnCondition ListType listType = (ListType) column.type; if (column.type.isMultiCell()) { - ByteBuffer columnValue = getListItem(getCells(row, column), getListIndex(collectionElement)); + ByteBuffer columnValue = cellValueAtIndex(getCells(row, column), getListIndex(collectionElement)); return compareWithOperator(operator, ((ListType)column.type).getElementsType(), value, columnValue); } else @@ -330,15 +386,6 @@ public class ColumnCondition return idx; } - static ByteBuffer getListItem(Iterator<Cell> iter, int index) - { - int adv = Iterators.advance(iter, index); - if (adv == index && iter.hasNext()) - return iter.next().value(); - else - return null; - } - public ByteBuffer getCollectionElementValue() { return collectionElement; @@ -371,72 +418,47 @@ public class ColumnCondition if (collectionElement == null) throw new InvalidRequestException("Invalid null value for " + (column.type instanceof MapType ? "map" : "list") + " element access"); + ByteBuffer cellValue; + AbstractType<?> valueType; if (column.type instanceof MapType) { MapType mapType = (MapType) column.type; - AbstractType<?> valueType = mapType.getValuesType(); + valueType = mapType.getValuesType(); if (column.type.isMultiCell()) { - Cell item = getCell(row, column, CellPath.create(collectionElement)); - for (ByteBuffer value : inValues) - { - if (isSatisfiedByValue(value, item, valueType, Operator.EQ)) - return true; - } - return false; + Cell cell = getCell(row, column, CellPath.create(collectionElement)); + cellValue = cell == null ? null : cell.value(); } else { Cell cell = getCell(row, column); - ByteBuffer mapElementValue = cell == null - ? null - : mapType.getSerializer().getSerializedValue(cell.value(), collectionElement, mapType.getKeysType()); - for (ByteBuffer value : inValues) - { - if (value == null) - { - if (mapElementValue == null) - return true; - continue; - } - if (valueType.compare(value, mapElementValue) == 0) - return true; - } - return false; + cellValue = cell == null + ? null + : mapType.getSerializer().getSerializedValue(cell.value(), collectionElement, mapType.getKeysType()); } } - - ListType listType = (ListType) column.type; - AbstractType<?> elementsType = listType.getElementsType(); - if (column.type.isMultiCell()) + else // ListType { - ByteBuffer columnValue = ElementAccessBound.getListItem(getCells(row, column), ElementAccessBound.getListIndex(collectionElement)); - - for (ByteBuffer value : inValues) + ListType listType = (ListType) column.type; + valueType = listType.getElementsType(); + if (column.type.isMultiCell()) { - if (compareWithOperator(Operator.EQ, elementsType, value, columnValue)) - return true; + cellValue = cellValueAtIndex(getCells(row, column), ElementAccessBound.getListIndex(collectionElement)); } - } - else - { - Cell cell = getCell(row, column); - ByteBuffer listElementValue = cell == null - ? null - : listType.getSerializer().getElement(cell.value(), ElementAccessBound.getListIndex(collectionElement)); - - for (ByteBuffer value : inValues) + else { - if (value == null) - { - if (listElementValue == null) - return true; - continue; - } - if (elementsType.compare(value, listElementValue) == 0) - return true; + Cell cell = getCell(row, column); + cellValue = cell == null + ? null + : listType.getSerializer().getElement(cell.value(), ElementAccessBound.getListIndex(collectionElement)); } } + + for (ByteBuffer value : inValues) + { + if (compareWithOperator(Operator.EQ, valueType, value, cellValue)) + return true; + } return false; } } @@ -539,26 +561,6 @@ public class ColumnCondition return operator == Operator.EQ || operator == Operator.LTE || operator == Operator.GTE; } - private static boolean evaluateComparisonWithOperator(int comparison, Operator operator) - { - // called when comparison != 0 - switch (operator) - { - case EQ: - return false; - case LT: - case LTE: - return comparison < 0; - case GT: - case GTE: - return comparison > 0; - case NEQ: - return true; - default: - throw new AssertionError(); - } - } - static boolean listAppliesTo(ListType type, Iterator<Cell> iter, List<ByteBuffer> elements, Operator operator) { return setOrListAppliesTo(type.getElementsType(), iter, elements.iterator(), operator, false); @@ -691,6 +693,195 @@ public class ColumnCondition } } + /** A condition on a UDT field. IN operators are not supported here, see UDTFieldAccessInBound. */ + static class UDTFieldAccessBound extends Bound + { + public final ColumnIdentifier field; + public final ByteBuffer value; + + private UDTFieldAccessBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException + { + super(condition.column, condition.operator); + assert column.type.isUDT() && condition.field != null; + assert condition.operator != Operator.IN; + this.field = condition.field; + this.value = condition.value.bindAndGet(options); + } + + public boolean appliesTo(Row row) throws InvalidRequestException + { + UserType userType = (UserType) column.type; + int fieldPosition = userType.fieldPosition(field); + assert fieldPosition >= 0; + + ByteBuffer cellValue; + if (column.type.isMultiCell()) + { + Cell cell = getCell(row, column, userType.cellPathForField(field.bytes)); + cellValue = cell == null ? null : cell.value(); + } + else + { + Cell cell = getCell(row, column); + cellValue = cell == null + ? null + : userType.split(cell.value())[fieldPosition]; + } + return compareWithOperator(operator, userType.fieldType(fieldPosition), value, cellValue); + } + } + + /** An IN condition on a UDT field. For example: IF user.name IN ('a', 'b') */ + static class UDTFieldAccessInBound extends Bound + { + public final ColumnIdentifier field; + public final List<ByteBuffer> inValues; + + private UDTFieldAccessInBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException + { + super(condition.column, condition.operator); + assert column.type.isUDT() && condition.field != null; + this.field = condition.field; + + if (condition.inValues == null) + this.inValues = ((Lists.Value) condition.value.bind(options)).getElements(); + else + { + this.inValues = new ArrayList<>(condition.inValues.size()); + for (Term value : condition.inValues) + this.inValues.add(value.bindAndGet(options)); + } + } + + public boolean appliesTo(Row row) throws InvalidRequestException + { + UserType userType = (UserType) column.type; + int fieldPosition = userType.fieldPosition(field); + assert fieldPosition >= 0; + + ByteBuffer cellValue; + if (column.type.isMultiCell()) + { + Cell cell = getCell(row, column, userType.cellPathForField(field.bytes)); + cellValue = cell == null ? null : cell.value(); + } + else + { + Cell cell = getCell(row, column); + cellValue = cell == null ? null : userType.split(getCell(row, column).value())[fieldPosition]; + } + + AbstractType<?> valueType = userType.fieldType(fieldPosition); + for (ByteBuffer value : inValues) + { + if (compareWithOperator(Operator.EQ, valueType, value, cellValue)) + return true; + } + return false; + } + } + + /** A non-IN condition on an entire UDT. For example: IF user = {name: 'joe', age: 42}). */ + static class UDTBound extends Bound + { + private final ByteBuffer value; + private final int protocolVersion; + + private UDTBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException + { + super(condition.column, condition.operator); + assert column.type.isUDT() && condition.field == null; + assert condition.operator != Operator.IN; + protocolVersion = options.getProtocolVersion(); + value = condition.value.bindAndGet(options); + } + + public boolean appliesTo(Row row) throws InvalidRequestException + { + UserType userType = (UserType) column.type; + ByteBuffer rowValue; + if (userType.isMultiCell()) + { + Iterator<Cell> iter = getCells(row, column); + rowValue = iter.hasNext() ? userType.serializeForNativeProtocol(iter, protocolVersion) : null; + } + else + { + Cell cell = getCell(row, column); + rowValue = cell == null ? null : cell.value(); + } + + if (value == null) + { + if (operator == Operator.EQ) + return rowValue == null; + else if (operator == Operator.NEQ) + return rowValue != null; + else + throw new InvalidRequestException(String.format("Invalid comparison with null for operator \"%s\"", operator)); + } + + return compareWithOperator(operator, userType, value, rowValue); + } + } + + /** An IN condition on an entire UDT. For example: IF user IN ({name: 'joe', age: 42}, {name: 'bob', age: 23}). */ + public static class UDTInBound extends Bound + { + private final List<ByteBuffer> inValues; + private final int protocolVersion; + + private UDTInBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException + { + super(condition.column, condition.operator); + assert column.type.isUDT() && condition.field == null; + assert condition.operator == Operator.IN; + protocolVersion = options.getProtocolVersion(); + inValues = new ArrayList<>(); + if (condition.inValues == null) + { + Lists.Marker inValuesMarker = (Lists.Marker) condition.value; + for (ByteBuffer buffer : ((Lists.Value)inValuesMarker.bind(options)).elements) + this.inValues.add(buffer); + } + else + { + for (Term value : condition.inValues) + this.inValues.add(value.bindAndGet(options)); + } + } + + public boolean appliesTo(Row row) throws InvalidRequestException + { + UserType userType = (UserType) column.type; + ByteBuffer rowValue; + if (userType.isMultiCell()) + { + Iterator<Cell> cells = getCells(row, column); + rowValue = cells.hasNext() ? userType.serializeForNativeProtocol(cells, protocolVersion) : null; + } + else + { + Cell cell = getCell(row, column); + rowValue = cell == null ? null : cell.value(); + } + + for (ByteBuffer value : inValues) + { + if (value == null || rowValue == null) + { + if (value == rowValue) // both null + return true; + } + else if (userType.compare(value, rowValue) == 0) + { + return true; + } + } + return false; + } + } + public static class Raw { private final Term.Raw value; @@ -700,106 +891,156 @@ public class ColumnCondition // Can be null, only used with the syntax "IF m[e] = ..." (in which case it's 'e') private final Term.Raw collectionElement; + // Can be null, only used with the syntax "IF udt.field = ..." (in which case it's 'field') + private final ColumnIdentifier.Raw udtField; + private final Operator operator; - private Raw(Term.Raw value, List<Term.Raw> inValues, AbstractMarker.INRaw inMarker, Term.Raw collectionElement, Operator op) + private Raw(Term.Raw value, List<Term.Raw> inValues, AbstractMarker.INRaw inMarker, Term.Raw collectionElement, + ColumnIdentifier.Raw udtField, Operator op) { this.value = value; this.inValues = inValues; this.inMarker = inMarker; this.collectionElement = collectionElement; + this.udtField = udtField; this.operator = op; } /** A condition on a column. For example: "IF col = 'foo'" */ public static Raw simpleCondition(Term.Raw value, Operator op) { - return new Raw(value, null, null, null, op); + return new Raw(value, null, null, null, null, op); } /** An IN condition on a column. For example: "IF col IN ('foo', 'bar', ...)" */ public static Raw simpleInCondition(List<Term.Raw> inValues) { - return new Raw(null, inValues, null, null, Operator.IN); + return new Raw(null, inValues, null, null, null, Operator.IN); } /** An IN condition on a column with a single marker. For example: "IF col IN ?" */ public static Raw simpleInCondition(AbstractMarker.INRaw inMarker) { - return new Raw(null, null, inMarker, null, Operator.IN); + return new Raw(null, null, inMarker, null, null, Operator.IN); } /** A condition on a collection element. For example: "IF col['key'] = 'foo'" */ public static Raw collectionCondition(Term.Raw value, Term.Raw collectionElement, Operator op) { - return new Raw(value, null, null, collectionElement, op); + return new Raw(value, null, null, collectionElement, null, op); } /** An IN condition on a collection element. For example: "IF col['key'] IN ('foo', 'bar', ...)" */ public static Raw collectionInCondition(Term.Raw collectionElement, List<Term.Raw> inValues) { - return new Raw(null, inValues, null, collectionElement, Operator.IN); + return new Raw(null, inValues, null, collectionElement, null, Operator.IN); } /** An IN condition on a collection element with a single marker. For example: "IF col['key'] IN ?" */ public static Raw collectionInCondition(Term.Raw collectionElement, AbstractMarker.INRaw inMarker) { - return new Raw(null, null, inMarker, collectionElement, Operator.IN); + return new Raw(null, null, inMarker, collectionElement, null, Operator.IN); + } + + /** A condition on a UDT field. For example: "IF col.field = 'foo'" */ + public static Raw udtFieldCondition(Term.Raw value, ColumnIdentifier.Raw udtField, Operator op) + { + return new Raw(value, null, null, null, udtField, op); } - public ColumnCondition prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException + /** An IN condition on a collection element. For example: "IF col.field IN ('foo', 'bar', ...)" */ + public static Raw udtFieldInCondition(ColumnIdentifier.Raw udtField, List<Term.Raw> inValues) + { + return new Raw(null, inValues, null, null, udtField, Operator.IN); + } + + /** An IN condition on a collection element with a single marker. For example: "IF col.field IN ?" */ + public static Raw udtFieldInCondition(ColumnIdentifier.Raw udtField, AbstractMarker.INRaw inMarker) + { + return new Raw(null, null, inMarker, null, udtField, Operator.IN); + } + + public ColumnCondition prepare(String keyspace, ColumnDefinition receiver, CFMetaData cfm) throws InvalidRequestException { if (receiver.type instanceof CounterColumnType) throw new InvalidRequestException("Conditions on counters are not supported"); - if (collectionElement == null) + if (collectionElement != null) { + if (!(receiver.type.isCollection())) + throw new InvalidRequestException(String.format("Invalid element access syntax for non-collection column %s", receiver.name)); + + ColumnSpecification elementSpec, valueSpec; + switch ((((CollectionType) receiver.type).kind)) + { + case LIST: + elementSpec = Lists.indexSpecOf(receiver); + valueSpec = Lists.valueSpecOf(receiver); + break; + case MAP: + elementSpec = Maps.keySpecOf(receiver); + valueSpec = Maps.valueSpecOf(receiver); + break; + case SET: + throw new InvalidRequestException(String.format("Invalid element access syntax for set column %s", receiver.name)); + default: + throw new AssertionError(); + } if (operator == Operator.IN) { if (inValues == null) - return ColumnCondition.inCondition(receiver, inMarker.prepare(keyspace, receiver)); + return ColumnCondition.inCondition(receiver, collectionElement.prepare(keyspace, elementSpec), inMarker.prepare(keyspace, valueSpec)); List<Term> terms = new ArrayList<>(inValues.size()); for (Term.Raw value : inValues) - terms.add(value.prepare(keyspace, receiver)); - return ColumnCondition.inCondition(receiver, terms); + terms.add(value.prepare(keyspace, valueSpec)); + return ColumnCondition.inCondition(receiver, collectionElement.prepare(keyspace, elementSpec), terms); } else { - return ColumnCondition.condition(receiver, value.prepare(keyspace, receiver), operator); + return ColumnCondition.condition(receiver, collectionElement.prepare(keyspace, elementSpec), value.prepare(keyspace, valueSpec), operator); } } + else if (udtField != null) + { + UserType userType = (UserType) receiver.type; + ColumnIdentifier fieldIdentifier = udtField.prepare(cfm); - if (!(receiver.type.isCollection())) - throw new InvalidRequestException(String.format("Invalid element access syntax for non-collection column %s", receiver.name)); + int fieldPosition = userType.fieldPosition(fieldIdentifier); + if (fieldPosition == -1) + throw new InvalidRequestException(String.format("Unknown field %s for column %s", fieldIdentifier, receiver.name)); - ColumnSpecification elementSpec, valueSpec; - switch ((((CollectionType)receiver.type).kind)) - { - case LIST: - elementSpec = Lists.indexSpecOf(receiver); - valueSpec = Lists.valueSpecOf(receiver); - break; - case MAP: - elementSpec = Maps.keySpecOf(receiver); - valueSpec = Maps.valueSpecOf(receiver); - break; - case SET: - throw new InvalidRequestException(String.format("Invalid element access syntax for set column %s", receiver.name)); - default: - throw new AssertionError(); - } - if (operator == Operator.IN) - { - if (inValues == null) - return ColumnCondition.inCondition(receiver, collectionElement.prepare(keyspace, elementSpec), inMarker.prepare(keyspace, valueSpec)); - List<Term> terms = new ArrayList<>(inValues.size()); - for (Term.Raw value : inValues) - terms.add(value.prepare(keyspace, valueSpec)); - return ColumnCondition.inCondition(receiver, collectionElement.prepare(keyspace, elementSpec), terms); + ColumnSpecification fieldReceiver = UserTypes.fieldSpecOf(receiver, fieldPosition); + if (operator == Operator.IN) + { + if (inValues == null) + return ColumnCondition.inCondition(receiver, udtField.prepare(cfm), inMarker.prepare(keyspace, fieldReceiver)); + + List<Term> terms = new ArrayList<>(inValues.size()); + for (Term.Raw value : inValues) + terms.add(value.prepare(keyspace, fieldReceiver)); + return ColumnCondition.inCondition(receiver, udtField.prepare(cfm), terms); + } + else + { + return ColumnCondition.condition(receiver, udtField.prepare(cfm), value.prepare(keyspace, fieldReceiver), operator); + } } else { - return ColumnCondition.condition(receiver, collectionElement.prepare(keyspace, elementSpec), value.prepare(keyspace, valueSpec), operator); + if (operator == Operator.IN) + { + if (inValues == null) + return ColumnCondition.inCondition(receiver, inMarker.prepare(keyspace, receiver)); + List<Term> terms = new ArrayList<>(inValues.size()); + for (Term.Raw value : inValues) + terms.add(value.prepare(keyspace, receiver)); + return ColumnCondition.inCondition(receiver, terms); + } + else + { + return ColumnCondition.condition(receiver, value.prepare(keyspace, receiver), operator); + } } } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java b/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java index 74d0d28..f202145 100644 --- a/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java +++ b/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java @@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.MapMaker; import org.apache.cassandra.cache.IMeasurableMemory; @@ -329,7 +330,8 @@ public class ColumnIdentifier extends Selectable implements IMeasurableMemory, C } } - static String maybeQuote(String text) + @VisibleForTesting + public static String maybeQuote(String text) { if (UNQUOTED_IDENTIFIER.matcher(text).matches()) return text; http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/Constants.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Constants.java b/src/java/org/apache/cassandra/cql3/Constants.java index 4982c49..2efce59 100644 --- a/src/java/org/apache/cassandra/cql3/Constants.java +++ b/src/java/org/apache/cassandra/cql3/Constants.java @@ -162,7 +162,7 @@ public abstract class Constants public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { CQL3Type receiverType = receiver.type.asCQL3Type(); - if (receiverType.isCollection()) + if (receiverType.isCollection() || receiverType.isUDT()) return AssignmentTestable.TestResult.NOT_ASSIGNABLE; if (!(receiverType instanceof CQL3Type.Native)) http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/Operation.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Operation.java b/src/java/org/apache/cassandra/cql3/Operation.java index 51c8969..bfb05e7 100644 --- a/src/java/org/apache/cassandra/cql3/Operation.java +++ b/src/java/org/apache/cassandra/cql3/Operation.java @@ -19,6 +19,7 @@ package org.apache.cassandra.cql3; import java.util.Collections; +import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.db.DecoratedKey; @@ -107,12 +108,10 @@ public abstract class Operation * It returns an Operation which can be though as post-preparation well-typed * Operation. * - * @param receiver the "column" this operation applies to. Note that - * contrarly to the method of same name in Term.Raw, the receiver should always - * be a true column. + * @param receiver the column this operation applies to. * @return the prepared update operation. */ - public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException; + public Operation prepare(CFMetaData cfm, ColumnDefinition receiver) throws InvalidRequestException; /** * @return whether this operation can be applied alongside the {@code @@ -146,7 +145,7 @@ public abstract class Operation * @param receiver the "column" this operation applies to. * @return the prepared delete operation. */ - public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException; + public Operation prepare(String keyspace, ColumnDefinition receiver, CFMetaData cfm) throws InvalidRequestException; } public static class SetValue implements RawUpdate @@ -158,26 +157,32 @@ public abstract class Operation this.value = value; } - public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException + public Operation prepare(CFMetaData cfm, ColumnDefinition receiver) throws InvalidRequestException { - Term v = value.prepare(keyspace, receiver); + Term v = value.prepare(cfm.ksName, receiver); if (receiver.type instanceof CounterColumnType) throw new InvalidRequestException(String.format("Cannot set the value of counter column %s (counters can only be incremented/decremented, not set)", receiver.name)); - if (!(receiver.type.isCollection())) - return new Constants.Setter(receiver, v); - - switch (((CollectionType)receiver.type).kind) + if (receiver.type.isCollection()) { - case LIST: - return new Lists.Setter(receiver, v); - case SET: - return new Sets.Setter(receiver, v); - case MAP: - return new Maps.Setter(receiver, v); + switch (((CollectionType) receiver.type).kind) + { + case LIST: + return new Lists.Setter(receiver, v); + case SET: + return new Sets.Setter(receiver, v); + case MAP: + return new Maps.Setter(receiver, v); + default: + throw new AssertionError(); + } } - throw new AssertionError(); + + if (receiver.type.isUDT()) + return new UserTypes.Setter(receiver, v); + + return new Constants.Setter(receiver, v); } protected String toString(ColumnSpecification column) @@ -204,7 +209,7 @@ public abstract class Operation this.value = value; } - public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException + public Operation prepare(CFMetaData cfm, ColumnDefinition receiver) throws InvalidRequestException { if (!(receiver.type instanceof CollectionType)) throw new InvalidRequestException(String.format("Invalid operation (%s) for non collection column %s", toString(receiver), receiver.name)); @@ -214,14 +219,14 @@ public abstract class Operation switch (((CollectionType)receiver.type).kind) { case LIST: - Term idx = selector.prepare(keyspace, Lists.indexSpecOf(receiver)); - Term lval = value.prepare(keyspace, Lists.valueSpecOf(receiver)); + Term idx = selector.prepare(cfm.ksName, Lists.indexSpecOf(receiver)); + Term lval = value.prepare(cfm.ksName, Lists.valueSpecOf(receiver)); return new Lists.SetterByIndex(receiver, idx, lval); case SET: throw new InvalidRequestException(String.format("Invalid operation (%s) for set column %s", toString(receiver), receiver.name)); case MAP: - Term key = selector.prepare(keyspace, Maps.keySpecOf(receiver)); - Term mval = value.prepare(keyspace, Maps.valueSpecOf(receiver)); + Term key = selector.prepare(cfm.ksName, Maps.keySpecOf(receiver)); + Term mval = value.prepare(cfm.ksName, Maps.valueSpecOf(receiver)); return new Maps.SetterByKey(receiver, key, mval); } throw new AssertionError(); @@ -240,6 +245,47 @@ public abstract class Operation } } + public static class SetField implements RawUpdate + { + private final ColumnIdentifier.Raw field; + private final Term.Raw value; + + public SetField(ColumnIdentifier.Raw field, Term.Raw value) + { + this.field = field; + this.value = value; + } + + public Operation prepare(CFMetaData cfm, ColumnDefinition receiver) throws InvalidRequestException + { + if (!receiver.type.isUDT()) + throw new InvalidRequestException(String.format("Invalid operation (%s) for non-UDT column %s", toString(receiver), receiver.name)); + else if (!receiver.type.isMultiCell()) + throw new InvalidRequestException(String.format("Invalid operation (%s) for frozen UDT column %s", toString(receiver), receiver.name)); + + ColumnIdentifier fieldIdentifier = field.prepare(cfm); + int fieldPosition = ((UserType) receiver.type).fieldPosition(fieldIdentifier); + if (fieldPosition == -1) + throw new InvalidRequestException(String.format("UDT column %s does not have a field named %s", receiver.name, fieldIdentifier)); + + Term val = value.prepare(cfm.ksName, UserTypes.fieldSpecOf(receiver, fieldPosition)); + return new UserTypes.SetterByField(receiver, fieldIdentifier, val); + } + + protected String toString(ColumnSpecification column) + { + return String.format("%s.%s = %s", column.name, field, value); + } + + public boolean isCompatibleWith(RawUpdate other) + { + if (other instanceof SetField) + return !((SetField) other).field.equals(field); + else + return !(other instanceof SetValue); + } + } + public static class Addition implements RawUpdate { private final Term.Raw value; @@ -249,9 +295,9 @@ public abstract class Operation this.value = value; } - public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException + public Operation prepare(CFMetaData cfm, ColumnDefinition receiver) throws InvalidRequestException { - Term v = value.prepare(keyspace, receiver); + Term v = value.prepare(cfm.ksName, receiver); if (!(receiver.type instanceof CollectionType)) { @@ -294,13 +340,13 @@ public abstract class Operation this.value = value; } - public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException + public Operation prepare(CFMetaData cfm, ColumnDefinition receiver) throws InvalidRequestException { if (!(receiver.type instanceof CollectionType)) { if (!(receiver.type instanceof CounterColumnType)) throw new InvalidRequestException(String.format("Invalid operation (%s) for non counter column %s", toString(receiver), receiver.name)); - return new Constants.Substracter(receiver, value.prepare(keyspace, receiver)); + return new Constants.Substracter(receiver, value.prepare(cfm.ksName, receiver)); } else if (!(receiver.type.isMultiCell())) throw new InvalidRequestException(String.format("Invalid operation (%s) for frozen collection column %s", toString(receiver), receiver.name)); @@ -308,16 +354,16 @@ public abstract class Operation switch (((CollectionType)receiver.type).kind) { case LIST: - return new Lists.Discarder(receiver, value.prepare(keyspace, receiver)); + return new Lists.Discarder(receiver, value.prepare(cfm.ksName, receiver)); case SET: - return new Sets.Discarder(receiver, value.prepare(keyspace, receiver)); + return new Sets.Discarder(receiver, value.prepare(cfm.ksName, receiver)); case MAP: // The value for a map subtraction is actually a set ColumnSpecification vr = new ColumnSpecification(receiver.ksName, receiver.cfName, receiver.name, SetType.getInstance(((MapType)receiver.type).getKeysType(), false)); - return new Sets.Discarder(receiver, value.prepare(keyspace, vr)); + return new Sets.Discarder(receiver, value.prepare(cfm.ksName, vr)); } throw new AssertionError(); } @@ -342,9 +388,9 @@ public abstract class Operation this.value = value; } - public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException + public Operation prepare(CFMetaData cfm, ColumnDefinition receiver) throws InvalidRequestException { - Term v = value.prepare(keyspace, receiver); + Term v = value.prepare(cfm.ksName, receiver); if (!(receiver.type instanceof ListType)) throw new InvalidRequestException(String.format("Invalid operation (%s) for non list column %s", toString(receiver), receiver.name)); @@ -379,7 +425,7 @@ public abstract class Operation return id; } - public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException + public Operation prepare(String keyspace, ColumnDefinition receiver, CFMetaData cfm) throws InvalidRequestException { // No validation, deleting a column is always "well typed" return new Constants.Deleter(receiver); @@ -402,7 +448,7 @@ public abstract class Operation return id; } - public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException + public Operation prepare(String keyspace, ColumnDefinition receiver, CFMetaData cfm) throws InvalidRequestException { if (!(receiver.type.isCollection())) throw new InvalidRequestException(String.format("Invalid deletion operation for non collection column %s", receiver.name)); @@ -424,4 +470,35 @@ public abstract class Operation throw new AssertionError(); } } + + public static class FieldDeletion implements RawDeletion + { + private final ColumnIdentifier.Raw id; + private final ColumnIdentifier.Raw field; + + public FieldDeletion(ColumnIdentifier.Raw id, ColumnIdentifier.Raw field) + { + this.id = id; + this.field = field; + } + + public ColumnIdentifier.Raw affectedColumn() + { + return id; + } + + public Operation prepare(String keyspace, ColumnDefinition receiver, CFMetaData cfm) throws InvalidRequestException + { + if (!receiver.type.isUDT()) + throw new InvalidRequestException(String.format("Invalid field deletion operation for non-UDT column %s", receiver.name)); + else if (!receiver.type.isMultiCell()) + throw new InvalidRequestException(String.format("Frozen UDT column %s does not support field deletions", receiver.name)); + + ColumnIdentifier fieldIdentifier = field.prepare(cfm); + if (((UserType) receiver.type).fieldPosition(fieldIdentifier) == -1) + throw new InvalidRequestException(String.format("UDT column %s does not have a field named %s", receiver.name, fieldIdentifier)); + + return new UserTypes.DeleterByField(receiver, fieldIdentifier); + } + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/Tuples.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Tuples.java b/src/java/org/apache/cassandra/cql3/Tuples.java index 6c7df47..ca4b6d9 100644 --- a/src/java/org/apache/cassandra/cql3/Tuples.java +++ b/src/java/org/apache/cassandra/cql3/Tuples.java @@ -111,8 +111,10 @@ public class Tuples for (int i = 0; i < elements.size(); i++) { if (i >= tt.size()) + { throw new InvalidRequestException(String.format("Invalid tuple literal for %s: too many elements. Type %s expects %d but got %d", - receiver.name, tt.asCQL3Type(), tt.size(), elements.size())); + receiver.name, tt.asCQL3Type(), tt.size(), elements.size())); + } Term.Raw value = elements.get(i); ColumnSpecification spec = componentSpecOf(receiver, i); @@ -154,6 +156,13 @@ public class Tuples public static Value fromSerialized(ByteBuffer bytes, TupleType type) { + ByteBuffer[] values = type.split(bytes); + if (values.length > type.size()) + { + throw new InvalidRequestException(String.format( + "Tuple value contained too many fields (expected %s, got %s)", type.size(), values.length)); + } + return new Value(type.split(bytes)); } @@ -199,6 +208,10 @@ public class Tuples private ByteBuffer[] bindInternal(QueryOptions options) throws InvalidRequestException { + if (elements.size() > type.size()) + throw new InvalidRequestException(String.format( + "Tuple value contained too many fields (expected %s, got %s)", type.size(), elements.size())); + ByteBuffer[] buffers = new ByteBuffer[elements.size()]; for (int i = 0; i < elements.size(); i++) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/UntypedResultSet.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/UntypedResultSet.java b/src/java/org/apache/cassandra/cql3/UntypedResultSet.java index fb8d567..3d70051 100644 --- a/src/java/org/apache/cassandra/cql3/UntypedResultSet.java +++ b/src/java/org/apache/cassandra/cql3/UntypedResultSet.java @@ -245,7 +245,7 @@ public abstract class UntypedResultSet implements Iterable<UntypedResultSet.Row> { ComplexColumnData complexData = row.getComplexColumnData(def); if (complexData != null) - data.put(def.name.toString(), ((CollectionType)def.type).serializeForNativeProtocol(def, complexData.iterator(), Server.VERSION_3)); + data.put(def.name.toString(), ((CollectionType)def.type).serializeForNativeProtocol(complexData.iterator(), Server.VERSION_3)); } }
