Add support for arithmetic operators patch by Benjamin Lerer; reviewed by Sylvain Lebresne for CASSANDRA-11935
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/8b3de2f4 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/8b3de2f4 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/8b3de2f4 Branch: refs/heads/cassandra-3.X Commit: 8b3de2f4908c4651491b0f20b80f7bb96cff26ed Parents: 075539a Author: Benjamin Lerer <b.le...@gmail.com> Authored: Mon Nov 21 18:04:42 2016 +0100 Committer: Benjamin Lerer <b.le...@gmail.com> Committed: Mon Nov 21 18:04:42 2016 +0100 ---------------------------------------------------------------------- CHANGES.txt | 3 + doc/source/cql/changes.rst | 4 +- doc/source/cql/definitions.rst | 4 +- doc/source/cql/index.rst | 1 + doc/source/cql/operators.rst | 57 ++ pylib/cqlshlib/cql3handling.py | 2 +- src/antlr/Lexer.g | 6 +- src/antlr/Parser.g | 248 +++++-- .../org/apache/cassandra/cql3/Constants.java | 58 +- src/java/org/apache/cassandra/cql3/Lists.java | 85 ++- src/java/org/apache/cassandra/cql3/Maps.java | 122 ++- src/java/org/apache/cassandra/cql3/Sets.java | 95 ++- src/java/org/apache/cassandra/cql3/Tuples.java | 147 +++- .../org/apache/cassandra/cql3/UserTypes.java | 115 ++- .../cassandra/cql3/functions/FunctionCall.java | 37 +- .../cql3/functions/FunctionResolver.java | 91 ++- .../cassandra/cql3/functions/OperationFcts.java | 380 ++++++++++ .../cql3/selection/CollectionFactory.java | 91 +++ .../cql3/selection/ForwardingFactory.java | 90 +++ .../cassandra/cql3/selection/ListSelector.java | 104 +++ .../cassandra/cql3/selection/MapSelector.java | 195 +++++ .../cql3/selection/ScalarFunctionSelector.java | 9 - .../cassandra/cql3/selection/Selectable.java | 647 +++++++++++++++- .../cassandra/cql3/selection/Selector.java | 11 - .../cassandra/cql3/selection/SetSelector.java | 106 +++ .../cassandra/cql3/selection/TupleSelector.java | 101 +++ .../cql3/selection/UserTypeSelector.java | 177 +++++ .../org/apache/cassandra/db/SystemKeyspace.java | 1 + .../cassandra/db/marshal/AbstractType.java | 13 +- .../cassandra/db/marshal/BooleanType.java | 2 +- .../apache/cassandra/db/marshal/ByteType.java | 56 +- .../cassandra/db/marshal/CounterColumnType.java | 40 +- .../apache/cassandra/db/marshal/DateType.java | 2 +- .../cassandra/db/marshal/DecimalType.java | 76 +- .../apache/cassandra/db/marshal/DoubleType.java | 69 +- .../apache/cassandra/db/marshal/EmptyType.java | 2 +- .../apache/cassandra/db/marshal/FloatType.java | 61 +- .../apache/cassandra/db/marshal/Int32Type.java | 48 +- .../cassandra/db/marshal/IntegerType.java | 69 +- .../cassandra/db/marshal/LexicalUUIDType.java | 2 +- .../apache/cassandra/db/marshal/LongType.java | 52 +- .../apache/cassandra/db/marshal/NumberType.java | 223 ++++++ .../cassandra/db/marshal/ReversedType.java | 2 +- .../apache/cassandra/db/marshal/ShortType.java | 51 +- .../cassandra/db/marshal/TimeUUIDType.java | 2 +- .../cassandra/db/marshal/TimestampType.java | 2 +- .../apache/cassandra/db/marshal/TupleType.java | 5 + .../apache/cassandra/db/marshal/UUIDType.java | 2 +- .../apache/cassandra/db/marshal/UserType.java | 5 + .../exceptions/OperationExecutionException.java | 57 ++ .../cassandra/serializers/ByteSerializer.java | 4 +- .../apache/cassandra/utils/ByteBufferUtil.java | 17 + .../org/apache/cassandra/cql3/CQLTester.java | 4 +- .../cql3/functions/OperationFctsTest.java | 744 +++++++++++++++++++ .../selection/SelectionColumnMappingTest.java | 94 +++ .../cql3/selection/TermSelectionTest.java | 386 +++++++++- .../cql3/validation/operations/SelectTest.java | 10 + 57 files changed, 4767 insertions(+), 320 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 24641a6..1f1625c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +3.12 + * Add support for arithmetic operators (CASSANDRA-11935) + 3.11 * AnticompactionRequestSerializer serializedSize is incorrect (CASSANDRA-12934) http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/doc/source/cql/changes.rst ---------------------------------------------------------------------- diff --git a/doc/source/cql/changes.rst b/doc/source/cql/changes.rst index 913bdb4..a33bb63 100644 --- a/doc/source/cql/changes.rst +++ b/doc/source/cql/changes.rst @@ -27,8 +27,8 @@ The following describes the changes in each version of CQL. - Adds a new ``duration `` :ref:`data types <data-types>` (:jira:`11873`). - Support for ``GROUP BY`` (:jira:`10707`). - Adds a ``DEFAULT UNSET`` option for ``INSERT JSON`` to ignore omitted columns (:jira:`11424`). -- Allows ``null`` as a legal value for TTL on insert and update. It will be treated as equivalent to -inserting a 0 (:jira:`12216`). +- Allows ``null`` as a legal value for TTL on insert and update. It will be treated as equivalent to inserting a 0 (:jira:`12216`). +- Adds support for arithmetic operators (:jira:`11935`) 3.4.2 ^^^^^ http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/doc/source/cql/definitions.rst ---------------------------------------------------------------------- diff --git a/doc/source/cql/definitions.rst b/doc/source/cql/definitions.rst index e54bcd7..cd548f5 100644 --- a/doc/source/cql/definitions.rst +++ b/doc/source/cql/definitions.rst @@ -119,9 +119,10 @@ Terms CQL has the notion of a *term*, which denotes the kind of values that CQL support. Terms are defined by: .. productionlist:: - term: `constant` | `literal` | `function_call` | `type_hint` | `bind_marker` + term: `constant` | `literal` | `function_call` | `arithmetic_operation` | `type_hint` | `bind_marker` literal: `collection_literal` | `udt_literal` | `tuple_literal` function_call: `identifier` '(' [ `term` (',' `term`)* ] ')' + arithmetic_operation: '-' `term` | `term` ('+' | '-' | '*' | '/' | '%') `term` type_hint: '(' `cql_type` `)` term bind_marker: '?' | ':' `identifier` @@ -132,6 +133,7 @@ A term is thus one of: (see the linked sections for details). - A function call: see :ref:`the section on functions <cql-functions>` for details on which :ref:`native function <native-functions>` exists and how to define your own :ref:`user-defined ones <udfs>`. +- An arithmetic operation between terms. see :ref:`the section on arithmetic operations <arithmetic_operators>` - A *type hint*: see the :ref:`related section <type-hints>` for details. - A bind marker, which denotes a variable to be bound at execution time. See the section on :ref:`prepared-statements` for details. A bind marker can be either anonymous (``?``) or named (``:some_name``). The latter form provides a more http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/doc/source/cql/index.rst ---------------------------------------------------------------------- diff --git a/doc/source/cql/index.rst b/doc/source/cql/index.rst index 00d90e4..6fa8135 100644 --- a/doc/source/cql/index.rst +++ b/doc/source/cql/index.rst @@ -38,6 +38,7 @@ thrift API (and earlier version 1 and 2 of CQL). mvs security functions + operators json triggers appendices http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/doc/source/cql/operators.rst ---------------------------------------------------------------------- diff --git a/doc/source/cql/operators.rst b/doc/source/cql/operators.rst new file mode 100644 index 0000000..05f1c61 --- /dev/null +++ b/doc/source/cql/operators.rst @@ -0,0 +1,57 @@ +.. Licensed to the Apache Software Foundation (ASF) under one +.. or more contributor license agreements. See the NOTICE file +.. distributed with this work for additional information +.. regarding copyright ownership. The ASF licenses this file +.. to you under the Apache License, Version 2.0 (the +.. "License"); you may not use this file except in compliance +.. with the License. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +.. highlight:: cql + +.. _arithmetic_operators: + +Arithmetic Operators +-------------------- + +CQL supports the following operators: + +=============== ======================================================================================================= + Operator Description +=============== ======================================================================================================= + \- (unary) Negates operand + \+ Addition + \- Substraction + \* Multiplication + / Division + % Returns the remainder of a division +=============== ======================================================================================================= + +Arithmetic operations are only supported on numeric types or counters. + +The return type of the operation will be based on the operand types: + +============= =========== ========== ========== ========== ========== ========== ========== ========== ========== + left/right tinyint smallint int bigint counter float double varint decimal +============= =========== ========== ========== ========== ========== ========== ========== ========== ========== + **tinyint** tinyint smallint int bigint bigint float double varint decimal + **smallint** smallint smallint int bigint bigint float double varint decimal + **int** int int int bigint bigint float double varint decimal + **bigint** bigint bigint bigint bigint bigint double double varint decimal + **counter** bigint bigint bigint bigint bigint double double varint decimal + **float** float float float double double float double decimal decimal + **double** double double double double double double double decimal decimal + **varint** varint varint varint decimal decimal decimal decimal decimal decimal + **decimal** decimal decimal decimal decimal decimal decimal decimal decimal decimal +============= =========== ========== ========== ========== ========== ========== ========== ========== ========== + +``*``, ``/`` and ``%`` operators have a higher precedence level than ``+`` and ``-`` operator. By consequence, +they will be evaluated before. If two operator in an expression have the same precedence level, they will be evaluated +left to right based on their position in the expression. http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/pylib/cqlshlib/cql3handling.py ---------------------------------------------------------------------- diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py index c628dcd..f81b19f 100644 --- a/pylib/cqlshlib/cql3handling.py +++ b/pylib/cqlshlib/cql3handling.py @@ -152,7 +152,7 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ; <colon> ::= ":" ; <star> ::= "*" ; <endtoken> ::= ";" ; -<op> ::= /[-+=,().]/ ; +<op> ::= /[+-=%/,().]/ ; <cmp> ::= /[<>!]=?/ ; <brackets> ::= /[][{}]/ ; http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/antlr/Lexer.g ---------------------------------------------------------------------- diff --git a/src/antlr/Lexer.g b/src/antlr/Lexer.g index 23cbed6..1685964 100644 --- a/src/antlr/Lexer.g +++ b/src/antlr/Lexer.g @@ -176,8 +176,10 @@ K_EXISTS: E X I S T S; K_MAP: M A P; K_LIST: L I S T; -K_NAN: N A N; -K_INFINITY: I N F I N I T Y; +K_POSITIVE_NAN: N A N; +K_NEGATIVE_NAN: '-' N A N; +K_POSITIVE_INFINITY: I N F I N I T Y; +K_NEGATIVE_INFINITY: '-' I N F I N I T Y; K_TUPLE: T U P L E; K_TRIGGER: T R I G G E R; http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/antlr/Parser.g ---------------------------------------------------------------------- diff --git a/src/antlr/Parser.g b/src/antlr/Parser.g index 3d06dc3..5fd6851 100644 --- a/src/antlr/Parser.g +++ b/src/antlr/Parser.g @@ -260,7 +260,6 @@ useStatement returns [UseStatement stmt] */ selectStatement returns [SelectStatement.RawStatement expr] @init { - boolean isDistinct = false; Term.Raw limit = null; Term.Raw perPartitionLimit = null; Map<ColumnDefinition.Raw, Boolean> orderings = new LinkedHashMap<>(); @@ -269,8 +268,8 @@ selectStatement returns [SelectStatement.RawStatement expr] boolean isJson = false; } : K_SELECT - ( K_JSON { isJson = true; } )? - ( ( K_DISTINCT { isDistinct = true; } )? sclause=selectClause ) + // json is a valid column name. By consequence, we need to resolve the ambiguity for "json - json" + ( (K_JSON selectClause)=> K_JSON { isJson = true; } )? sclause=selectClause K_FROM cf=columnFamilyName ( K_WHERE wclause=whereClause )? ( K_GROUP K_BY groupByClause[groups] ( ',' groupByClause[groups] )* )? @@ -281,15 +280,22 @@ selectStatement returns [SelectStatement.RawStatement expr] { SelectStatement.Parameters params = new SelectStatement.Parameters(orderings, groups, - isDistinct, + $sclause.isDistinct, allowFiltering, isJson); WhereClause where = wclause == null ? WhereClause.empty() : wclause.build(); - $expr = new SelectStatement.RawStatement(cf, params, sclause, where, limit, perPartitionLimit); + $expr = new SelectStatement.RawStatement(cf, params, $sclause.selectors, where, limit, perPartitionLimit); } ; -selectClause returns [List<RawSelector> expr] +selectClause returns [boolean isDistinct, List<RawSelector> selectors] + @init{ $isDistinct = false; } + // distinct is a valid column name. By consequence, we need to resolve the ambiguity for "distinct - distinct" + : (K_DISTINCT selectors)=> K_DISTINCT s=selectors { $isDistinct = true; $selectors = s; } + | s=selectors { $selectors = s; } + ; + +selectors returns [List<RawSelector> expr] : t1=selector { $expr = new ArrayList<RawSelector>(); $expr.add(t1); } (',' tN=selector { $expr.add(tN); })* | '\*' { $expr = Collections.<RawSelector>emptyList();} ; @@ -299,28 +305,117 @@ selector returns [RawSelector s] : us=unaliasedSelector (K_AS c=noncol_ident { alias = c; })? { $s = new RawSelector(us, alias); } ; +unaliasedSelector returns [Selectable.Raw s] + : a=selectionAddition {$s = a;} + ; + +selectionAddition returns [Selectable.Raw s] + : l=selectionMultiplication {$s = l;} + ( '+' r=selectionMultiplication {$s = Selectable.WithFunction.Raw.newOperation('+', $s, r);} + | '-' r=selectionMultiplication {$s = Selectable.WithFunction.Raw.newOperation('-', $s, r);} + )* + ; + +selectionMultiplication returns [Selectable.Raw s] + : l=selectionGroup {$s = l;} + ( '\*' r=selectionGroup {$s = Selectable.WithFunction.Raw.newOperation('*', $s, r);} + | '/' r=selectionGroup {$s = Selectable.WithFunction.Raw.newOperation('/', $s, r);} + | '%' r=selectionGroup {$s = Selectable.WithFunction.Raw.newOperation('\%', $s, r);} + )* + ; + +selectionGroup returns [Selectable.Raw s] + : (selectionGroupWithField)=> f=selectionGroupWithField { $s=f; } + | g=selectionGroupWithoutField { $s=g; } + | '-' g=selectionGroup {$s = Selectable.WithFunction.Raw.newNegation(g);} + ; + +selectionGroupWithField returns [Selectable.Raw s] + @init { Selectable.Raw tmp = null; } + @after { $s = tmp; } + : g=selectionGroupWithoutField {tmp=g;} ( '.' fi=fident { tmp = new Selectable.WithFieldSelection.Raw(tmp, fi); } )+ + ; + +selectionGroupWithoutField returns [Selectable.Raw s] + @init { Selectable.Raw tmp = null; } + @after { $s = tmp; } + : sn=simpleUnaliasedSelector { tmp=sn; } + | (selectionTypeHint)=> h=selectionTypeHint { tmp=h; } + | t=selectionTupleOrNestedSelector { tmp=t; } + | l=selectionList { tmp=l; } + | m=selectionMapOrSet { tmp=m; } + // UDTs are equivalent to maps from the syntax point of view, so the final decision will be done in Selectable.WithMapOrUdt + ; + +selectionTypeHint returns [Selectable.Raw s] + : '(' ct=comparatorType ')' a=selectionGroupWithoutField { $s = new Selectable.WithTypeHint.Raw(ct, a); } + ; + +selectionList returns [Selectable.Raw s] + @init { List<Selectable.Raw> l = new ArrayList<>(); } + @after { $s = new Selectable.WithList.Raw(l); } + : '[' ( t1=unaliasedSelector { l.add(t1); } ( ',' tn=unaliasedSelector { l.add(tn); } )* )? ']' + ; + +selectionMapOrSet returns [Selectable.Raw s] + : '{' t1=unaliasedSelector ( m=selectionMap[t1] { $s = m; } | st=selectionSet[t1] { $s = st; }) '}' + | '{' '}' { $s = new Selectable.WithSet.Raw(Collections.emptyList());} + ; + +selectionMap [Selectable.Raw k1] returns [Selectable.Raw s] + @init { List<Pair<Selectable.Raw, Selectable.Raw>> m = new ArrayList<>(); } + @after { $s = new Selectable.WithMapOrUdt.Raw(m); } + : ':' v1=unaliasedSelector { m.add(Pair.create(k1, v1)); } ( ',' kn=unaliasedSelector ':' vn=unaliasedSelector { m.add(Pair.create(kn, vn)); } )* + ; + +selectionSet [Selectable.Raw t1] returns [Selectable.Raw s] + @init { List<Selectable.Raw> l = new ArrayList<>(); l.add(t1); } + @after { $s = new Selectable.WithSet.Raw(l); } + : ( ',' tn=unaliasedSelector { l.add(tn); } )* + ; + +selectionTupleOrNestedSelector returns [Selectable.Raw s] + @init { List<Selectable.Raw> l = new ArrayList<>(); } + @after { $s = new Selectable.BetweenParenthesesOrWithTuple.Raw(l); } + : '(' t1=unaliasedSelector { l.add(t1); } (',' tn=unaliasedSelector { l.add(tn); } )* ')' + ; + /* * A single selection. The core of it is selecting a column, but we also allow any term and function, as well as * sub-element selection for UDT. */ -unaliasedSelector returns [Selectable.Raw s] - @init { Selectable.Raw tmp = null; } - : ( c=cident { tmp = c; } - | v=value { tmp = new Selectable.WithTerm.Raw(v); } - | '(' ct=comparatorType ')' v=value { tmp = new Selectable.WithTerm.Raw(new TypeCast(ct, v)); } - | K_COUNT '(' '\*' ')' { tmp = Selectable.WithFunction.Raw.newCountRowsFunction(); } - | K_WRITETIME '(' c=cident ')' { tmp = new Selectable.WritetimeOrTTL.Raw(c, true); } - | K_TTL '(' c=cident ')' { tmp = new Selectable.WritetimeOrTTL.Raw(c, false); } - | K_CAST '(' sn=unaliasedSelector K_AS t=native_type ')' {tmp = new Selectable.WithCast.Raw(sn, t);} - | f=functionName args=selectionFunctionArgs { tmp = new Selectable.WithFunction.Raw(f, args); } - ) ( '.' fi=fident { tmp = new Selectable.WithFieldSelection.Raw(tmp, fi); } )* { $s = tmp; } +simpleUnaliasedSelector returns [Selectable.Raw s] + : c=sident { $s = c; } + | l=selectionLiteral { $s = new Selectable.WithTerm.Raw(l); } + | f=selectionFunction { $s = f; } + ; + +selectionFunction returns [Selectable.Raw s] + : K_COUNT '(' '\*' ')' { $s = Selectable.WithFunction.Raw.newCountRowsFunction(); } + | K_WRITETIME '(' c=cident ')' { $s = new Selectable.WritetimeOrTTL.Raw(c, true); } + | K_TTL '(' c=cident ')' { $s = new Selectable.WritetimeOrTTL.Raw(c, false); } + | K_CAST '(' sn=unaliasedSelector K_AS t=native_type ')' {$s = new Selectable.WithCast.Raw(sn, t);} + | f=functionName args=selectionFunctionArgs { $s = new Selectable.WithFunction.Raw(f, args); } + ; + +selectionLiteral returns [Term.Raw value] + : c=constant { $value = c; } + | K_NULL { $value = Constants.NULL_LITERAL; } + | ':' id=noncol_ident { $value = newBindVariables(id); } + | QMARK { $value = newBindVariables(null); } ; selectionFunctionArgs returns [List<Selectable.Raw> a] - : '(' ')' { $a = Collections.emptyList(); } - | '(' s1=unaliasedSelector { List<Selectable.Raw> args = new ArrayList<Selectable.Raw>(); args.add(s1); } - ( ',' sn=unaliasedSelector { args.add(sn); } )* - ')' { $a = args; } + @init{ $a = new ArrayList<>(); } + : '(' (s1=unaliasedSelector { $a.add(s1); } + ( ',' sn=unaliasedSelector { $a.add(sn); } )*)? + ')' + ; + +sident returns [Selectable.Raw id] + : t=IDENT { $id = Selectable.RawIdentifier.forUnquoted($t.text); } + | t=QUOTED_NAME { $id = ColumnDefinition.RawIdentifier.forQuoted($t.text); } + | k=unreserved_keyword { $id = ColumnDefinition.RawIdentifier.forUnquoted(k); } ; whereClause returns [WhereClause.Builder clause] @@ -661,14 +756,17 @@ cfamDefinition[CreateTableStatement.RawStatement expr] ; cfamColumns[CreateTableStatement.RawStatement expr] - : k=ident v=comparatorType { boolean isStatic=false; } (K_STATIC {isStatic = true;})? { $expr.addDefinition(k, v, isStatic); } + @init { boolean isStatic = false; } + : k=ident v=comparatorType (K_STATIC {isStatic = true;})? { $expr.addDefinition(k, v, isStatic); } (K_PRIMARY K_KEY { $expr.addKeyAliases(Collections.singletonList(k)); })? | K_PRIMARY K_KEY '(' pkDef[expr] (',' c=ident { $expr.addColumnAlias(c); } )* ')' ; pkDef[CreateTableStatement.RawStatement expr] - : k=ident { $expr.addKeyAliases(Collections.singletonList(k)); } - | '(' { List<ColumnIdentifier> l = new ArrayList<ColumnIdentifier>(); } k1=ident { l.add(k1); } ( ',' kn=ident { l.add(kn); } )* ')' { $expr.addKeyAliases(l); } + @init {List<ColumnIdentifier> l = new ArrayList<ColumnIdentifier>();} + @after{ $expr.addKeyAliases(l); } + : k1=ident { l.add(k1);} + | '(' k1=ident { l.add(k1); } ( ',' kn=ident { l.add(kn); } )* ')' ; cfamProperty[CFProperties props] @@ -743,7 +841,7 @@ createMaterializedViewStatement returns [CreateViewStatement expr] List<ColumnDefinition.Raw> compositeKeys = new ArrayList<>(); } : K_CREATE K_MATERIALIZED K_VIEW (K_IF K_NOT K_EXISTS { ifNotExists = true; })? cf=columnFamilyName K_AS - K_SELECT sclause=selectClause K_FROM basecf=columnFamilyName + K_SELECT sclause=selectors K_FROM basecf=columnFamilyName (K_WHERE wclause=whereClause)? K_PRIMARY K_KEY ( '(' '(' k1=cident { partitionKeys.add(k1); } ( ',' kn=cident { partitionKeys.add(kn); } )* ')' ( ',' c1=cident { compositeKeys.add(c1); } )* ')' @@ -849,14 +947,14 @@ alterTypeStatement returns [AlterTypeStatement expr] : K_ALTER K_TYPE name=userTypeName ( K_ALTER f=fident K_TYPE v=comparatorType { $expr = AlterTypeStatement.alter(name, f, v); } | K_ADD f=fident v=comparatorType { $expr = AlterTypeStatement.addition(name, f, v); } - | K_RENAME - { Map<FieldIdentifier, FieldIdentifier> renames = new HashMap<>(); } - id1=fident K_TO toId1=fident { renames.put(id1, toId1); } - ( K_AND idn=fident K_TO toIdn=fident { renames.put(idn, toIdn); } )* - { $expr = AlterTypeStatement.renames(name, renames); } + | K_RENAME r=renamedColumns { $expr = AlterTypeStatement.renames(name, r); } ) ; +renamedColumns returns [Map<FieldIdentifier, FieldIdentifier> renames] + @init {$renames = new HashMap<>();} + : id1=fident K_TO toId1=fident { renames.put(id1, toId1); } ( K_AND idn=fident K_TO toIdn=fident { renames.put(idn, toIdn); } )* + ; /** * DROP KEYSPACE [IF EXISTS] <KSP>; @@ -1155,7 +1253,7 @@ roleOptions[RoleOptions opts] roleOption[RoleOptions opts] : K_PASSWORD '=' v=STRING_LITERAL { opts.setOption(IRoleManager.Option.PASSWORD, $v.text); } - | K_OPTIONS '=' m=mapLiteral { opts.setOption(IRoleManager.Option.OPTIONS, convertPropertyMap(m)); } + | K_OPTIONS '=' m=fullMapLiteral { opts.setOption(IRoleManager.Option.OPTIONS, convertPropertyMap(m)); } | K_SUPERUSER '=' b=BOOLEAN { opts.setOption(IRoleManager.Option.SUPERUSER, Boolean.valueOf($b.text)); } | K_LOGIN '=' b=BOOLEAN { opts.setOption(IRoleManager.Option.LOGIN, Boolean.valueOf($b.text)); } ; @@ -1258,34 +1356,49 @@ constant returns [Constants.Literal constant] | t=DURATION { $constant = Constants.Literal.duration($t.text);} | t=UUID { $constant = Constants.Literal.uuid($t.text); } | t=HEXNUMBER { $constant = Constants.Literal.hex($t.text); } - | { String sign=""; } ('-' {sign = "-"; } )? t=(K_NAN | K_INFINITY) { $constant = Constants.Literal.floatingPoint(sign + $t.text); } + | ((K_POSITIVE_NAN | K_NEGATIVE_NAN) { $constant = Constants.Literal.floatingPoint("NaN"); } + | K_POSITIVE_INFINITY { $constant = Constants.Literal.floatingPoint("Infinity"); } + | K_NEGATIVE_INFINITY { $constant = Constants.Literal.floatingPoint("-Infinity"); }) ; -mapLiteral returns [Maps.Literal map] - : '{' { List<Pair<Term.Raw, Term.Raw>> m = new ArrayList<Pair<Term.Raw, Term.Raw>>(); } - ( k1=term ':' v1=term { m.add(Pair.create(k1, v1)); } ( ',' kn=term ':' vn=term { m.add(Pair.create(kn, vn)); } )* )? - '}' { $map = new Maps.Literal(m); } +fullMapLiteral returns [Maps.Literal map] + @init { List<Pair<Term.Raw, Term.Raw>> m = new ArrayList<Pair<Term.Raw, Term.Raw>>();} + @after{ $map = new Maps.Literal(m); } + : '{' ( k1=term ':' v1=term { m.add(Pair.create(k1, v1)); } ( ',' kn=term ':' vn=term { m.add(Pair.create(kn, vn)); } )* )? + '}' ; setOrMapLiteral[Term.Raw t] returns [Term.Raw value] - : ':' v=term { List<Pair<Term.Raw, Term.Raw>> m = new ArrayList<Pair<Term.Raw, Term.Raw>>(); m.add(Pair.create(t, v)); } - ( ',' kn=term ':' vn=term { m.add(Pair.create(kn, vn)); } )* - { $value = new Maps.Literal(m); } - | { List<Term.Raw> s = new ArrayList<Term.Raw>(); s.add(t); } - ( ',' tn=term { s.add(tn); } )* - { $value = new Sets.Literal(s); } + : m=mapLiteral[t] { $value=m; } + | s=setLiteral[t] { $value=s; } + ; + +setLiteral[Term.Raw t] returns [Term.Raw value] + @init { List<Term.Raw> s = new ArrayList<Term.Raw>(); s.add(t); } + @after { $value = new Sets.Literal(s); } + : ( ',' tn=term { s.add(tn); } )* + ; + +mapLiteral[Term.Raw k] returns [Term.Raw value] + @init { List<Pair<Term.Raw, Term.Raw>> m = new ArrayList<Pair<Term.Raw, Term.Raw>>(); } + @after { $value = new Maps.Literal(m); } + : ':' v=term { m.add(Pair.create(k, v)); } ( ',' kn=term ':' vn=term { m.add(Pair.create(kn, vn)); } )* ; collectionLiteral returns [Term.Raw value] - : '[' { List<Term.Raw> l = new ArrayList<Term.Raw>(); } - ( t1=term { l.add(t1); } ( ',' tn=term { l.add(tn); } )* )? - ']' { $value = new Lists.Literal(l); } + : l=listLiteral { $value = l; } | '{' t=term v=setOrMapLiteral[t] { $value = v; } '}' // Note that we have an ambiguity between maps and set for "{}". So we force it to a set literal, // and deal with it later based on the type of the column (SetLiteral.java). | '{' '}' { $value = new Sets.Literal(Collections.<Term.Raw>emptyList()); } ; +listLiteral returns [Term.Raw value] + @init {List<Term.Raw> l = new ArrayList<Term.Raw>();} + @after {$value = new Lists.Literal(l);} + : '[' ( t1=term { l.add(t1); } ( ',' tn=term { l.add(tn); } )* )? ']' { $value = new Lists.Literal(l); } + ; + usertypeLiteral returns [UserTypes.Literal ut] @init{ Map<FieldIdentifier, Term.Raw> m = new HashMap<>(); } @after{ $ut = new UserTypes.Literal(m); } @@ -1338,9 +1451,33 @@ functionArgs returns [List<Term.Raw> args] ; term returns [Term.Raw term] - : v=value { $term = v; } - | f=function { $term = f; } - | '(' c=comparatorType ')' t=term { $term = new TypeCast(c, t); } + : t=termAddition { $term = t; } + ; + +termAddition returns [Term.Raw term] + : l=termMultiplication {$term = l;} + ( '+' r=termMultiplication {$term = FunctionCall.Raw.newOperation('+', $term, r);} + | '-' r=termMultiplication {$term = FunctionCall.Raw.newOperation('-', $term, r);} + )* + ; + +termMultiplication returns [Term.Raw term] + : l=termGroup {$term = l;} + ( '\*' r=termGroup {$term = FunctionCall.Raw.newOperation('*', $term, r);} + | '/' r=termGroup {$term = FunctionCall.Raw.newOperation('/', $term, r);} + | '%' r=termGroup {$term = FunctionCall.Raw.newOperation('\%', $term, r);} + )* + ; + +termGroup returns [Term.Raw term] + : t=simpleTerm { $term = t; } + | '-' t=simpleTerm { $term = FunctionCall.Raw.newNegation(t); } + ; + +simpleTerm returns [Term.Raw term] + : v=value { $term = v; } + | f=function { $term = f; } + | '(' c=comparatorType ')' t=simpleTerm { $term = new TypeCast(c, t); } ; columnOperation[List<Pair<ColumnDefinition.Raw, Operation.RawUpdate>> operations] @@ -1436,7 +1573,7 @@ properties[PropertyDefinitions props] property[PropertyDefinitions props] : k=noncol_ident '=' simple=propertyValue { try { $props.addProperty(k.toString(), simple); } catch (SyntaxException e) { addRecognitionError(e.getMessage()); } } - | k=noncol_ident '=' map=mapLiteral { try { $props.addProperty(k.toString(), convertPropertyMap(map)); } catch (SyntaxException e) { addRecognitionError(e.getMessage()); } } + | k=noncol_ident '=' map=fullMapLiteral { try { $props.addProperty(k.toString(), convertPropertyMap(map)); } catch (SyntaxException e) { addRecognitionError(e.getMessage()); } } ; propertyValue returns [String str] @@ -1463,8 +1600,7 @@ relation[WhereClause.Builder clauses] { $clauses.add(new SingleColumnRelation(name, Operator.IN, marker)); } | name=cident K_IN inValues=singleColumnInValues { $clauses.add(SingleColumnRelation.createInRelation($name.id, inValues)); } - | name=cident K_CONTAINS { Operator rt = Operator.CONTAINS; } (K_KEY { rt = Operator.CONTAINS_KEY; })? - t=term { $clauses.add(new SingleColumnRelation(name, rt, t)); } + | name=cident rt=containsOperator t=term { $clauses.add(new SingleColumnRelation(name, rt, t)); } | name=cident '[' key=term ']' type=relationType t=term { $clauses.add(new SingleColumnRelation(name, key, type, t)); } | ids=tupleOfIdentifiers ( K_IN @@ -1489,6 +1625,10 @@ relation[WhereClause.Builder clauses] | '(' relation[$clauses] ')' ; +containsOperator returns [Operator o] + : K_CONTAINS { o = Operator.CONTAINS; } (K_KEY { o = Operator.CONTAINS_KEY; })? + ; + inMarker returns [AbstractMarker.INRaw marker] : QMARK { $marker = newINBindVariables(null); } | ':' name=noncol_ident { $marker = newINBindVariables(name); } @@ -1587,9 +1727,9 @@ collection_type returns [CQL3Type.Raw pt] ; tuple_type returns [CQL3Type.Raw t] - : K_TUPLE '<' { List<CQL3Type.Raw> types = new ArrayList<>(); } - t1=comparatorType { types.add(t1); } (',' tn=comparatorType { types.add(tn); })* - '>' { $t = CQL3Type.Raw.tuple(types); } + @init {List<CQL3Type.Raw> types = new ArrayList<>();} + @after {$t = CQL3Type.Raw.tuple(types);} + : K_TUPLE '<' t1=comparatorType { types.add(t1); } (',' tn=comparatorType { types.add(tn); })* '>' ; username http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/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 03e2053..8d54063 100644 --- a/src/java/org/apache/cassandra/cql3/Constants.java +++ b/src/java/org/apache/cassandra/cql3/Constants.java @@ -17,6 +17,8 @@ */ package org.apache.cassandra.cql3; +import java.math.BigDecimal; +import java.math.BigInteger; import java.nio.ByteBuffer; import org.slf4j.Logger; @@ -38,7 +40,54 @@ public abstract class Constants public enum Type { - STRING, INTEGER, UUID, FLOAT, BOOLEAN, HEX, DURATION; + STRING, + INTEGER + { + public AbstractType<?> getPreferedTypeFor(String text) + { + // We only try to determine the smallest possible type between int, long and BigInteger + BigInteger b = new BigInteger(text); + + if (b.equals(BigInteger.valueOf(b.intValue()))) + return Int32Type.instance; + + if (b.equals(BigInteger.valueOf(b.longValue()))) + return LongType.instance; + + return IntegerType.instance; + } + }, + UUID, + FLOAT + { + public AbstractType<?> getPreferedTypeFor(String text) + { + if ("NaN".equals(text) || "-NaN".equals(text) || "Infinity".equals(text) || "-Infinity".equals(text)) + return DoubleType.instance; + + // We only try to determine the smallest possible type between double and BigDecimal + BigDecimal b = new BigDecimal(text); + + if (b.equals(BigDecimal.valueOf(b.doubleValue()))) + return DoubleType.instance; + + return DecimalType.instance; + } + }, + BOOLEAN, + HEX, + DURATION; + + /** + * Returns the exact type for the specified text + * + * @param text the text for which the type must be determined + * @return the exact type or {@code null} if it is not known. + */ + public AbstractType<?> getPreferedTypeFor(String text) + { + return null; + } } private static class UnsetLiteral extends Term.Raw @@ -119,12 +168,14 @@ public abstract class Constants { private final Type type; private final String text; + private final AbstractType<?> preferedType; private Literal(Type type, String text) { assert type != null && text != null; this.type = type; this.text = text; + this.preferedType = type.getPreferedTypeFor(text); } public static Literal string(String text) @@ -204,6 +255,11 @@ public abstract class Constants return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; CQL3Type.Native nt = (CQL3Type.Native)receiverType; + + // If the receiver type match the prefered type we can straight away return an exact match + if (nt.getType().equals(preferedType)) + return AssignmentTestable.TestResult.EXACT_MATCH; + switch (type) { case STRING: http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/Lists.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Lists.java b/src/java/org/apache/cassandra/cql3/Lists.java index 037162b..eb4b685 100644 --- a/src/java/org/apache/cassandra/cql3/Lists.java +++ b/src/java/org/apache/cassandra/cql3/Lists.java @@ -22,8 +22,11 @@ import static org.apache.cassandra.cql3.Constants.UNSET_VALUE; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.cql3.functions.Function; @@ -53,7 +56,67 @@ public abstract class Lists public static ColumnSpecification valueSpecOf(ColumnSpecification column) { - return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((ListType)column.type).getElementsType()); + return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((ListType<?>)column.type).getElementsType()); + } + + /** + * Tests that the list with the specified elements can be assigned to the specified column. + * + * @param receiver the receiving column + * @param elements the list elements + */ + public static AssignmentTestable.TestResult testListAssignment(ColumnSpecification receiver, + List<? extends AssignmentTestable> elements) + { + if (!(receiver.type instanceof ListType)) + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; + + // If there is no elements, we can't say it's an exact match (an empty list if fundamentally polymorphic). + if (elements.isEmpty()) + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + + ColumnSpecification valueSpec = valueSpecOf(receiver); + return AssignmentTestable.TestResult.testAll(receiver.ksName, valueSpec, elements); + } + + /** + * Create a <code>String</code> representation of the list containing the specified elements. + * + * @param elements the list elements + * @return a <code>String</code> representation of the list + */ + public static String listToString(List<?> elements) + { + return listToString(elements, Object::toString); + } + + /** + * Create a <code>String</code> representation of the list from the specified items associated to + * the list elements. + * + * @param items items associated to the list elements + * @param mapper the mapper used to map the items to the <code>String</code> representation of the list elements + * @return a <code>String</code> representation of the list + */ + public static <T> String listToString(Iterable<T> items, java.util.function.Function<T, String> mapper) + { + return StreamSupport.stream(items.spliterator(), false) + .map(e -> mapper.apply(e)) + .collect(Collectors.joining(", ", "[", "]")); + } + + /** + * Returns the exact ListType from the items if it can be known. + * + * @param items the items mapped to the list elements + * @param mapper the mapper used to retrieve the element types from the items + * @return the exact ListType from the items if it can be known or <code>null</code> + */ + public static <T> AbstractType<?> getExactListTypeIfKnown(List<T> items, + java.util.function.Function<T, AbstractType<?>> mapper) + { + Optional<AbstractType<?>> type = items.stream().map(mapper).filter(Objects::nonNull).findFirst(); + return type.isPresent() ? ListType.getInstance(type.get(), false) : null; } public static class Literal extends Term.Raw @@ -103,32 +166,18 @@ public abstract class Lists public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { - if (!(receiver.type instanceof ListType)) - return AssignmentTestable.TestResult.NOT_ASSIGNABLE; - - // If there is no elements, we can't say it's an exact match (an empty list if fundamentally polymorphic). - if (elements.isEmpty()) - return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; - - ColumnSpecification valueSpec = Lists.valueSpecOf(receiver); - return AssignmentTestable.TestResult.testAll(keyspace, valueSpec, elements); + return testListAssignment(receiver, elements); } @Override public AbstractType<?> getExactTypeIfKnown(String keyspace) { - for (Term.Raw term : elements) - { - AbstractType<?> type = term.getExactTypeIfKnown(keyspace); - if (type != null) - return ListType.getInstance(type, false); - } - return null; + return getExactListTypeIfKnown(elements, p -> p.getExactTypeIfKnown(keyspace)); } public String getText() { - return elements.stream().map(Term.Raw::getText).collect(Collectors.joining(", ", "[", "]")); + return listToString(elements, Term.Raw::getText); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/Maps.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Maps.java b/src/java/org/apache/cassandra/cql3/Maps.java index 5c3bc44..6189285 100644 --- a/src/java/org/apache/cassandra/cql3/Maps.java +++ b/src/java/org/apache/cassandra/cql3/Maps.java @@ -44,12 +44,89 @@ public abstract class Maps public static ColumnSpecification keySpecOf(ColumnSpecification column) { - return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("key(" + column.name + ")", true), ((MapType)column.type).getKeysType()); + return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("key(" + column.name + ")", true), ((MapType<? , ?>)column.type).getKeysType()); } public static ColumnSpecification valueSpecOf(ColumnSpecification column) { - return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((MapType)column.type).getValuesType()); + return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((MapType<?, ?>)column.type).getValuesType()); + } + + /** + * Tests that the map with the specified entries can be assigned to the specified column. + * + * @param receiver the receiving column + * @param entries the map entries + */ + public static <T extends AssignmentTestable> AssignmentTestable.TestResult testMapAssignment(ColumnSpecification receiver, + List<Pair<T, T>> entries) + { + ColumnSpecification keySpec = keySpecOf(receiver); + ColumnSpecification valueSpec = valueSpecOf(receiver); + + // It's an exact match if all are exact match, but is not assignable as soon as any is non assignable. + AssignmentTestable.TestResult res = AssignmentTestable.TestResult.EXACT_MATCH; + for (Pair<T, T> entry : entries) + { + AssignmentTestable.TestResult t1 = entry.left.testAssignment(receiver.ksName, keySpec); + AssignmentTestable.TestResult t2 = entry.right.testAssignment(receiver.ksName, valueSpec); + if (t1 == AssignmentTestable.TestResult.NOT_ASSIGNABLE || t2 == AssignmentTestable.TestResult.NOT_ASSIGNABLE) + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; + if (t1 != AssignmentTestable.TestResult.EXACT_MATCH || t2 != AssignmentTestable.TestResult.EXACT_MATCH) + res = AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + } + return res; + } + + /** + * Create a <code>String</code> representation of the list containing the specified elements. + * + * @param elements the list elements + * @return a <code>String</code> representation of the list + */ + public static <T> String mapToString(List<Pair<T, T>> entries) + { + return mapToString(entries, Object::toString); + } + + /** + * Create a <code>String</code> representation of the map from the specified items associated to + * the map entries. + * + * @param items items associated to the map entries + * @param mapper the mapper used to map the items to the <code>String</code> representation of the map entries + * @return a <code>String</code> representation of the map + */ + public static <T> String mapToString(List<Pair<T, T>> items, + java.util.function.Function<T, String> mapper) + { + return items.stream() + .map(p -> String.format("%s: %s", mapper.apply(p.left), mapper.apply(p.right))) + .collect(Collectors.joining(", ", "{", "}")); + } + + /** + * Returns the exact MapType from the entries if it can be known. + * + * @param entries the entries + * @param mapper the mapper used to retrieve the key and value types from the entries + * @return the exact MapType from the entries if it can be known or <code>null</code> + */ + public static <T> AbstractType<?> getExactMapTypeIfKnown(List<Pair<T, T>> entries, + java.util.function.Function<T, AbstractType<?>> mapper) + { + AbstractType<?> keyType = null; + AbstractType<?> valueType = null; + for (Pair<T, T> entry : entries) + { + if (keyType == null) + keyType = mapper.apply(entry.left); + if (valueType == null) + valueType = mapper.apply(entry.right); + if (keyType != null && valueType != null) + return MapType.getInstance(keyType, valueType, false); + } + return null; } public static class Literal extends Term.Raw @@ -82,7 +159,7 @@ public abstract class Maps values.put(k, v); } - DelayedValue value = new DelayedValue(((MapType)receiver.type).getKeysType(), values); + DelayedValue value = new DelayedValue(((MapType<?, ?>)receiver.type).getKeysType(), values); return allTerminal ? value.bind(QueryOptions.DEFAULT) : value; } @@ -104,51 +181,18 @@ public abstract class Maps public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { - if (!(receiver.type instanceof MapType)) - return AssignmentTestable.TestResult.NOT_ASSIGNABLE; - - // If there is no elements, we can't say it's an exact match (an empty map if fundamentally polymorphic). - if (entries.isEmpty()) - return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; - - ColumnSpecification keySpec = Maps.keySpecOf(receiver); - ColumnSpecification valueSpec = Maps.valueSpecOf(receiver); - // It's an exact match if all are exact match, but is not assignable as soon as any is non assignable. - AssignmentTestable.TestResult res = AssignmentTestable.TestResult.EXACT_MATCH; - for (Pair<Term.Raw, Term.Raw> entry : entries) - { - AssignmentTestable.TestResult t1 = entry.left.testAssignment(keyspace, keySpec); - AssignmentTestable.TestResult t2 = entry.right.testAssignment(keyspace, valueSpec); - if (t1 == AssignmentTestable.TestResult.NOT_ASSIGNABLE || t2 == AssignmentTestable.TestResult.NOT_ASSIGNABLE) - return AssignmentTestable.TestResult.NOT_ASSIGNABLE; - if (t1 != AssignmentTestable.TestResult.EXACT_MATCH || t2 != AssignmentTestable.TestResult.EXACT_MATCH) - res = AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; - } - return res; + return testMapAssignment(receiver, entries); } @Override public AbstractType<?> getExactTypeIfKnown(String keyspace) { - AbstractType<?> keyType = null; - AbstractType<?> valueType = null; - for (Pair<Term.Raw, Term.Raw> entry : entries) - { - if (keyType == null) - keyType = entry.left.getExactTypeIfKnown(keyspace); - if (valueType == null) - valueType = entry.right.getExactTypeIfKnown(keyspace); - if (keyType != null && valueType != null) - return MapType.getInstance(keyType, valueType, false); - } - return null; + return getExactMapTypeIfKnown(entries, p -> p.getExactTypeIfKnown(keyspace)); } public String getText() { - return entries.stream() - .map(entry -> String.format("%s: %s", entry.left.getText(), entry.right.getText())) - .collect(Collectors.joining(", ", "{", "}")); + return mapToString(entries, Term.Raw::getText); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/Sets.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Sets.java b/src/java/org/apache/cassandra/cql3/Sets.java index d17a771..e79bda7 100644 --- a/src/java/org/apache/cassandra/cql3/Sets.java +++ b/src/java/org/apache/cassandra/cql3/Sets.java @@ -22,6 +22,7 @@ import static org.apache.cassandra.cql3.Constants.UNSET_VALUE; import java.nio.ByteBuffer; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.cql3.functions.Function; @@ -43,7 +44,73 @@ public abstract class Sets public static ColumnSpecification valueSpecOf(ColumnSpecification column) { - return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((SetType)column.type).getElementsType()); + return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((SetType<?>)column.type).getElementsType()); + } + + /** + * Tests that the set with the specified elements can be assigned to the specified column. + * + * @param receiver the receiving column + * @param elements the set elements + */ + public static AssignmentTestable.TestResult testSetAssignment(ColumnSpecification receiver, + List<? extends AssignmentTestable> elements) + { + if (!(receiver.type instanceof SetType)) + { + // We've parsed empty maps as a set literal to break the ambiguity so handle that case now + if (receiver.type instanceof MapType && elements.isEmpty()) + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; + } + + // If there is no elements, we can't say it's an exact match (an empty set if fundamentally polymorphic). + if (elements.isEmpty()) + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + + ColumnSpecification valueSpec = valueSpecOf(receiver); + return AssignmentTestable.TestResult.testAll(receiver.ksName, valueSpec, elements); + } + + /** + * Create a <code>String</code> representation of the set containing the specified elements. + * + * @param elements the set elements + * @return a <code>String</code> representation of the set + */ + public static String setToString(List<?> elements) + { + return setToString(elements, Object::toString); + } + + /** + * Create a <code>String</code> representation of the set from the specified items associated to + * the set elements. + * + * @param items items associated to the set elements + * @param mapper the mapper used to map the items to the <code>String</code> representation of the set elements + * @return a <code>String</code> representation of the set + */ + public static <T> String setToString(Iterable<T> items, java.util.function.Function<T, String> mapper) + { + return StreamSupport.stream(items.spliterator(), false) + .map(e -> mapper.apply(e)) + .collect(Collectors.joining(", ", "{", "}")); + } + + /** + * Returns the exact SetType from the items if it can be known. + * + * @param items the items mapped to the set elements + * @param mapper the mapper used to retrieve the element types from the items + * @return the exact SetType from the items if it can be known or <code>null</code> + */ + public static <T> AbstractType<?> getExactSetTypeIfKnown(List<T> items, + java.util.function.Function<T, AbstractType<?>> mapper) + { + Optional<AbstractType<?>> type = items.stream().map(mapper).filter(Objects::nonNull).findFirst(); + return type.isPresent() ? SetType.getInstance(type.get(), false) : null; } public static class Literal extends Term.Raw @@ -105,38 +172,18 @@ public abstract class Sets public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { - if (!(receiver.type instanceof SetType)) - { - // We've parsed empty maps as a set literal to break the ambiguity so handle that case now - if (receiver.type instanceof MapType && elements.isEmpty()) - return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; - - return AssignmentTestable.TestResult.NOT_ASSIGNABLE; - } - - // If there is no elements, we can't say it's an exact match (an empty set if fundamentally polymorphic). - if (elements.isEmpty()) - return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; - - ColumnSpecification valueSpec = Sets.valueSpecOf(receiver); - return AssignmentTestable.TestResult.testAll(keyspace, valueSpec, elements); + return testSetAssignment(receiver, elements); } @Override public AbstractType<?> getExactTypeIfKnown(String keyspace) { - for (Term.Raw term : elements) - { - AbstractType<?> type = term.getExactTypeIfKnown(keyspace); - if (type != null) - return SetType.getInstance(type, false); - } - return null; + return getExactSetTypeIfKnown(elements, p -> p.getExactTypeIfKnown(keyspace)); } public String getText() { - return elements.stream().map(Term.Raw::getText).collect(Collectors.joining(", ", "{", "}")); + return setToString(elements, Term.Raw::getText); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/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 32a31fd..bae756a 100644 --- a/src/java/org/apache/cassandra/cql3/Tuples.java +++ b/src/java/org/apache/cassandra/cql3/Tuples.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,6 +34,8 @@ import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.ByteBufferUtil; +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; + /** * Static helper methods and classes for tuples. */ @@ -65,7 +68,12 @@ public class Tuples public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { - validateAssignableTo(keyspace, receiver); + // The parser cannot differentiate between a tuple with one element and a term between parenthesis. + // By consequence, we need to wait until we know the target type to determine which one it is. + if (elements.size() == 1 && !(receiver.type instanceof TupleType)) + return elements.get(0).prepare(keyspace, receiver); + + validateTupleAssignableTo(receiver, elements); List<Term> values = new ArrayList<>(elements.size()); boolean allTerminal = true; @@ -102,38 +110,14 @@ public class Tuples return allTerminal ? value.bind(QueryOptions.DEFAULT) : value; } - private void validateAssignableTo(String keyspace, ColumnSpecification receiver) throws InvalidRequestException - { - if (!(receiver.type instanceof TupleType)) - throw new InvalidRequestException(String.format("Invalid tuple type literal for %s of type %s", receiver.name, receiver.type.asCQL3Type())); - - TupleType tt = (TupleType)receiver.type; - 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())); - } - - Term.Raw value = elements.get(i); - ColumnSpecification spec = componentSpecOf(receiver, i); - if (!value.testAssignment(keyspace, spec).isAssignable()) - throw new InvalidRequestException(String.format("Invalid tuple literal for %s: component %d is not of type %s", receiver.name, i, spec.type.asCQL3Type())); - } - } - public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { - try - { - validateAssignableTo(keyspace, receiver); - return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; - } - catch (InvalidRequestException e) - { - return AssignmentTestable.TestResult.NOT_ASSIGNABLE; - } + // The parser cannot differentiate between a tuple with one element and a term between parenthesis. + // By consequence, we need to wait until we know the target type to determine which one it is. + if (elements.size() == 1 && !(receiver.type instanceof TupleType)) + return elements.get(0).testAssignment(keyspace, receiver); + + return testTupleAssignment(receiver, elements); } @Override @@ -152,7 +136,7 @@ public class Tuples public String getText() { - return elements.stream().map(Term.Raw::getText).collect(Collectors.joining(", ", "(", ")")); + return tupleToString(elements, Term.Raw::getText); } } @@ -436,17 +420,100 @@ public class Tuples } } - public static String tupleToString(List<?> items) + /** + * Create a <code>String</code> representation of the tuple containing the specified elements. + * + * @param elements the tuple elements + * @return a <code>String</code> representation of the tuple + */ + public static String tupleToString(List<?> elements) + { + return tupleToString(elements, Object::toString); + } + + /** + * Create a <code>String</code> representation of the tuple from the specified items associated to + * the tuples elements. + * + * @param items items associated to the tuple elements + * @param mapper the mapper used to map the items to the <code>String</code> representation of the tuple elements + * @return a <code>String</code> representation of the tuple + */ + public static <T> String tupleToString(Iterable<T> items, java.util.function.Function<T, String> mapper) + { + return StreamSupport.stream(items.spliterator(), false) + .map(e -> mapper.apply(e)) + .collect(Collectors.joining(", ", "(", ")")); + } + + /** + * Returns the exact TupleType from the items if it can be known. + * + * @param items the items mapped to the tuple elements + * @param mapper the mapper used to retrieve the element types from the items + * @return the exact TupleType from the items if it can be known or <code>null</code> + */ + public static <T> AbstractType<?> getExactTupleTypeIfKnown(List<T> items, + java.util.function.Function<T, AbstractType<?>> mapper) + { + List<AbstractType<?>> types = new ArrayList<>(items.size()); + for (T item : items) + { + AbstractType<?> type = mapper.apply(item); + if (type == null) + return null; + types.add(type); + } + return new TupleType(types); + } + + /** + * Checks if the tuple with the specified elements can be assigned to the specified column. + * + * @param receiver the receiving column + * @param elements the tuple elements + * @throws InvalidRequestException if the tuple cannot be assigned to the specified column. + */ + public static void validateTupleAssignableTo(ColumnSpecification receiver, + List<? extends AssignmentTestable> elements) { + if (!(receiver.type instanceof TupleType)) + throw invalidRequest("Invalid tuple type literal for %s of type %s", receiver.name, receiver.type.asCQL3Type()); - StringBuilder sb = new StringBuilder("("); - for (int i = 0; i < items.size(); i++) + TupleType tt = (TupleType)receiver.type; + for (int i = 0; i < elements.size(); i++) + { + if (i >= tt.size()) + { + throw invalidRequest("Invalid tuple literal for %s: too many elements. Type %s expects %d but got %d", + receiver.name, tt.asCQL3Type(), tt.size(), elements.size()); + } + + AssignmentTestable value = elements.get(i); + ColumnSpecification spec = componentSpecOf(receiver, i); + if (!value.testAssignment(receiver.ksName, spec).isAssignable()) + throw invalidRequest("Invalid tuple literal for %s: component %d is not of type %s", + receiver.name, i, spec.type.asCQL3Type()); + } + } + + /** + * Tests that the tuple with the specified elements can be assigned to the specified column. + * + * @param receiver the receiving column + * @param elements the tuple elements + */ + public static AssignmentTestable.TestResult testTupleAssignment(ColumnSpecification receiver, + List<? extends AssignmentTestable> elements) + { + try + { + validateTupleAssignableTo(receiver, elements); + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + } + catch (InvalidRequestException e) { - sb.append(items.get(i)); - if (i < items.size() - 1) - sb.append(", "); + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; } - sb.append(')'); - return sb.toString(); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/UserTypes.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/UserTypes.java b/src/java/org/apache/cassandra/cql3/UserTypes.java index e867179..ae62177 100644 --- a/src/java/org/apache/cassandra/cql3/UserTypes.java +++ b/src/java/org/apache/cassandra/cql3/UserTypes.java @@ -19,6 +19,7 @@ package org.apache.cassandra.cql3; import java.nio.ByteBuffer; import java.util.*; +import java.util.stream.Collectors; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.cql3.functions.Function; @@ -47,6 +48,78 @@ public abstract class UserTypes ut.fieldType(field)); } + public static <T extends AssignmentTestable> void validateUserTypeAssignableTo(ColumnSpecification receiver, + Map<FieldIdentifier, T> entries) + { + if (!receiver.type.isUDT()) + throw new InvalidRequestException(String.format("Invalid user type literal for %s of type %s", receiver, receiver.type.asCQL3Type())); + + UserType ut = (UserType) receiver.type; + for (int i = 0; i < ut.size(); i++) + { + FieldIdentifier field = ut.fieldName(i); + T value = entries.get(field); + if (value == null) + continue; + + ColumnSpecification fieldSpec = fieldSpecOf(receiver, i); + if (!value.testAssignment(receiver.ksName, fieldSpec).isAssignable()) + { + throw new InvalidRequestException(String.format("Invalid user type literal for %s: field %s is not of type %s", + receiver, field, fieldSpec.type.asCQL3Type())); + } + } + } + + /** + * Tests that the map with the specified entries can be assigned to the specified column. + * + * @param receiver the receiving column + * @param entries the map entries + */ + public static <T extends AssignmentTestable> AssignmentTestable.TestResult testUserTypeAssignment(ColumnSpecification receiver, + Map<FieldIdentifier, T> entries) + { + try + { + validateUserTypeAssignableTo(receiver, entries); + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + } + catch (InvalidRequestException e) + { + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; + } + } + + /** + * Create a {@code String} representation of the user type from the specified items associated to + * the user type entries. + * + * @param items items associated to the user type entries + * @param mapper the mapper used to user type the items to the {@code String} representation of the map entries + * @return a {@code String} representation of the user type + */ + public static <T> String userTypeToString(Map<FieldIdentifier, T> items) + { + return userTypeToString(items, Object::toString); + } + + /** + * Create a {@code String} representation of the user type from the specified items associated to + * the user type entries. + * + * @param items items associated to the user type entries + * @return a {@code String} representation of the user type + */ + public static <T> String userTypeToString(Map<FieldIdentifier, T> items, + java.util.function.Function<T, String> mapper) + { + return items.entrySet() + .stream() + .map(p -> String.format("%s: %s", p.getKey(), mapper.apply(p.getValue()))) + .collect(Collectors.joining(", ", "{", "}")); + } + public static class Literal extends Term.Raw { public final Map<FieldIdentifier, Term.Raw> entries; @@ -95,37 +168,12 @@ public abstract class UserTypes private void validateAssignableTo(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { - if (!receiver.type.isUDT()) - throw new InvalidRequestException(String.format("Invalid user type literal for %s of type %s", receiver, receiver.type.asCQL3Type())); - - UserType ut = (UserType)receiver.type; - for (int i = 0; i < ut.size(); i++) - { - FieldIdentifier field = ut.fieldName(i); - Term.Raw value = entries.get(field); - if (value == null) - continue; - - ColumnSpecification fieldSpec = fieldSpecOf(receiver, i); - if (!value.testAssignment(keyspace, fieldSpec).isAssignable()) - { - throw new InvalidRequestException(String.format("Invalid user type literal for %s: field %s is not of type %s", - receiver, field, fieldSpec.type.asCQL3Type())); - } - } + validateUserTypeAssignableTo(receiver, entries); } public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { - try - { - validateAssignableTo(keyspace, receiver); - return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; - } - catch (InvalidRequestException e) - { - return AssignmentTestable.TestResult.NOT_ASSIGNABLE; - } + return testUserTypeAssignment(receiver, entries); } public AbstractType<?> getExactTypeIfKnown(String keyspace) @@ -135,18 +183,7 @@ public abstract class UserTypes public String getText() { - StringBuilder sb = new StringBuilder(); - sb.append("{"); - Iterator<Map.Entry<FieldIdentifier, Term.Raw>> iter = entries.entrySet().iterator(); - while (iter.hasNext()) - { - Map.Entry<FieldIdentifier, Term.Raw> entry = iter.next(); - sb.append(entry.getKey()).append(": ").append(entry.getValue().getText()); - if (iter.hasNext()) - sb.append(", "); - } - sb.append("}"); - return sb.toString(); + return userTypeToString(entries, Term.Raw::getText); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java index bcc912f..b8f5652 100644 --- a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java +++ b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java @@ -19,6 +19,8 @@ package org.apache.cassandra.cql3.functions; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -30,6 +32,8 @@ import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.ByteBufferUtil; +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; + public class FunctionCall extends Term.NonTerminal { private final ScalarFunction fun; @@ -130,26 +134,45 @@ public class FunctionCall extends Term.NonTerminal this.terms = terms; } + public static Raw newOperation(char operator, Term.Raw left, Term.Raw right) + { + FunctionName name = OperationFcts.getFunctionNameFromOperator(operator); + return new Raw(name, Arrays.asList(left, right)); + } + + public static Raw newNegation(Term.Raw raw) + { + FunctionName name = FunctionName.nativeFunction(OperationFcts.NEGATION_FUNCTION_NAME); + return new Raw(name, Collections.singletonList(raw)); + } + public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { Function fun = FunctionResolver.get(keyspace, name, terms, receiver.ksName, receiver.cfName, receiver.type); if (fun == null) - throw new InvalidRequestException(String.format("Unknown function %s called", name)); + throw invalidRequest("Unknown function %s called", name); if (fun.isAggregate()) - throw new InvalidRequestException("Aggregation function are not supported in the where clause"); + throw invalidRequest("Aggregation function are not supported in the where clause"); ScalarFunction scalarFun = (ScalarFunction) fun; // Functions.get() will complain if no function "name" type check with the provided arguments. // We still have to validate that the return type matches however if (!scalarFun.testAssignment(keyspace, receiver).isAssignable()) - throw new InvalidRequestException(String.format("Type error: cannot assign result of function %s (type %s) to %s (type %s)", - scalarFun.name(), scalarFun.returnType().asCQL3Type(), - receiver.name, receiver.type.asCQL3Type())); + { + if (OperationFcts.isOperation(name)) + throw invalidRequest("Type error: cannot assign result of operation %s (type %s) to %s (type %s)", + OperationFcts.getOperator(scalarFun.name()), scalarFun.returnType().asCQL3Type(), + receiver.name, receiver.type.asCQL3Type()); + + throw invalidRequest("Type error: cannot assign result of function %s (type %s) to %s (type %s)", + scalarFun.name(), scalarFun.returnType().asCQL3Type(), + receiver.name, receiver.type.asCQL3Type()); + } if (fun.argTypes().size() != terms.size()) - throw new InvalidRequestException(String.format("Incorrect number of arguments specified for function %s (expected %d, found %d)", - fun, fun.argTypes().size(), terms.size())); + throw invalidRequest("Incorrect number of arguments specified for function %s (expected %d, found %d)", + fun, fun.argTypes().size(), terms.size()); List<Term> parameters = new ArrayList<>(terms.size()); for (int i = 0; i < terms.size(); i++) http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/functions/FunctionResolver.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/FunctionResolver.java b/src/java/org/apache/cassandra/cql3/functions/FunctionResolver.java index 9e0b706..7234d1f 100644 --- a/src/java/org/apache/cassandra/cql3/functions/FunctionResolver.java +++ b/src/java/org/apache/cassandra/cql3/functions/FunctionResolver.java @@ -22,11 +22,15 @@ import java.util.Collection; import java.util.List; import org.apache.cassandra.config.Schema; -import org.apache.cassandra.cql3.*; +import org.apache.cassandra.cql3.AbstractMarker; +import org.apache.cassandra.cql3.AssignmentTestable; +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.exceptions.InvalidRequestException; import static java.util.stream.Collectors.joining; +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; public final class FunctionResolver { @@ -112,33 +116,83 @@ public final class FunctionResolver List<Function> compatibles = null; for (Function toTest : candidates) { - AssignmentTestable.TestResult r = matchAguments(keyspace, toTest, providedArgs, receiverKs, receiverCf); - switch (r) + if (matchReturnType(toTest, receiverType)) { - case EXACT_MATCH: - // We always favor exact matches - return toTest; - case WEAKLY_ASSIGNABLE: - if (compatibles == null) - compatibles = new ArrayList<>(); - compatibles.add(toTest); - break; + AssignmentTestable.TestResult r = matchAguments(keyspace, toTest, providedArgs, receiverKs, receiverCf); + switch (r) + { + case EXACT_MATCH: + // We always favor exact matches + return toTest; + case WEAKLY_ASSIGNABLE: + if (compatibles == null) + compatibles = new ArrayList<>(); + compatibles.add(toTest); + break; + } } } if (compatibles == null) { - throw new InvalidRequestException(String.format("Invalid call to function %s, none of its type signatures match (known type signatures: %s)", - name, format(candidates))); + if (OperationFcts.isOperation(name)) + throw invalidRequest("the '%s' operation is not supported between %s and %s", + OperationFcts.getOperator(name), providedArgs.get(0), providedArgs.get(1)); + + throw invalidRequest("Invalid call to function %s, none of its type signatures match (known type signatures: %s)", + name, format(candidates)); } if (compatibles.size() > 1) - throw new InvalidRequestException(String.format("Ambiguous call to function %s (can be matched by following signatures: %s): use type casts to disambiguate", - name, format(compatibles))); + { + if (OperationFcts.isOperation(name)) + { + if (receiverType != null && !containsMarkers(providedArgs)) + { + for (Function toTest : compatibles) + { + List<AbstractType<?>> argTypes = toTest.argTypes(); + if (receiverType.equals(argTypes.get(0)) && receiverType.equals(argTypes.get(1))) + return toTest; + } + } + throw invalidRequest("Ambiguous '%s' operation: use type casts to disambiguate", + OperationFcts.getOperator(name), providedArgs.get(0), providedArgs.get(1)); + } + + if (OperationFcts.isNegation(name)) + throw invalidRequest("Ambiguous negation: use type casts to disambiguate"); + throw invalidRequest("Ambiguous call to function %s (can be matched by following signatures: %s): use type casts to disambiguate", + name, format(compatibles)); + } return compatibles.get(0); } + /** + * Checks if at least one of the specified arguments is a marker. + * + * @param args the arguments to check + * @return {@code true} if if at least one of the specified arguments is a marker, {@code false} otherwise + */ + private static boolean containsMarkers(List<? extends AssignmentTestable> args) + { + return args.stream().anyMatch(AbstractMarker.Raw.class::isInstance); + } + + /** + * Checks that the return type of the specified function can be assigned to the specified receiver. + * + * @param fun the function to check + * @param receiverType the receiver type + * @return {@code true} if the return type of the specified function can be assigned to the specified receiver, + * {@code false} otherwise. + */ + private static boolean matchReturnType(Function fun, AbstractType<?> receiverType) + { + return receiverType == null || fun.returnType().testAssignment(receiverType).isAssignable(); + } + // This method and matchArguments are somewhat duplicate, but this method allows us to provide more precise errors in the common // case where there is no override for a given function. This is thus probably worth the minor code duplication. private static void validateTypes(String keyspace, @@ -146,10 +200,10 @@ public final class FunctionResolver List<? extends AssignmentTestable> providedArgs, String receiverKs, String receiverCf) - throws InvalidRequestException { if (providedArgs.size() != fun.argTypes().size()) - throw new InvalidRequestException(String.format("Invalid number of arguments in call to function %s: %d required but %d provided", fun.name(), fun.argTypes().size(), providedArgs.size())); + throw invalidRequest("Invalid number of arguments in call to function %s: %d required but %d provided", + fun.name(), fun.argTypes().size(), providedArgs.size()); for (int i = 0; i < providedArgs.size(); i++) { @@ -162,7 +216,8 @@ public final class FunctionResolver ColumnSpecification expected = makeArgSpec(receiverKs, receiverCf, fun, i); if (!provided.testAssignment(keyspace, expected).isAssignable()) - throw new InvalidRequestException(String.format("Type error: %s cannot be passed as argument %d of function %s of type %s", provided, i, fun.name(), expected.type.asCQL3Type())); + throw invalidRequest("Type error: %s cannot be passed as argument %d of function %s of type %s", + provided, i, fun.name(), expected.type.asCQL3Type()); } }