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());
         }
     }
 

Reply via email to