cassandra-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sn...@apache.org
Subject [2/3] cassandra git commit: Update cqlsh for UDFs
Date Tue, 23 Jun 2015 17:21:54 GMT
Update cqlsh for UDFs

patch by Robert Stupp; reviewed by Aleksey Yeschenko for CASSANDRA-7556


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/cfab9dac
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/cfab9dac
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/cfab9dac

Branch: refs/heads/trunk
Commit: cfab9dac19e85d304810ad47470d995e5b383d77
Parents: 6e1bdeb
Author: Robert Stupp <snazy@snazy.de>
Authored: Tue Jun 23 19:19:57 2015 +0200
Committer: Robert Stupp <snazy@snazy.de>
Committed: Tue Jun 23 19:19:57 2015 +0200

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 bin/cqlsh                                       | 116 ++++++++++++++++-
 lib/cassandra-driver-2.6.0c1.zip                | Bin 0 -> 198898 bytes
 ...driver-internal-only-2.5.1.post0-074650b.zip | Bin 195907 -> 0 bytes
 pylib/cqlshlib/cql3handling.py                  |  93 ++++++++++++--
 pylib/cqlshlib/helptopics.py                    | 125 +++++++++++++++++++
 src/java/org/apache/cassandra/cql3/Cql.g        |   2 +
 7 files changed, 326 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/cfab9dac/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 16fe569..c0480d7 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 2.2
+ * Update cqlsh for UDFs (CASSANDRA-7556)
  * Change Windows kernel default timer resolution (CASSANDRA-9634)
  * Deprected sstable2json and json2sstable (CASSANDRA-9618)
  * Allow native functions in user-defined aggregates (CASSANDRA-9542)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cfab9dac/bin/cqlsh
----------------------------------------------------------------------
diff --git a/bin/cqlsh b/bin/cqlsh
index b2a729c..a556a92 100755
--- a/bin/cqlsh
+++ b/bin/cqlsh
@@ -265,7 +265,11 @@ cqlsh_extra_syntax_rules = r'''
                    ;
 
 <describeCommand> ::= ( "DESCRIBE" | "DESC" )
-                                  ( "KEYSPACES"
+                                  ( "FUNCTIONS" ksname=<keyspaceName>?
+                                  | "FUNCTION" udf=<anyFunctionName>
+                                  | "AGGREGATES" ksname=<keyspaceName>?
+                                  | "AGGREGATE" uda=<userAggregateName>
+                                  | "KEYSPACES"
                                   | "KEYSPACE" ksname=<keyspaceName>?
                                   | ( "COLUMNFAMILY" | "TABLE" ) cf=<columnFamilyName>
                                   | ( "COLUMNFAMILIES" | "TABLES" )
@@ -437,6 +441,12 @@ class VersionNotSupported(Exception):
 class UserTypeNotFound(Exception):
     pass
 
+class FunctionNotFound(Exception):
+    pass
+
+class AggregateNotFound(Exception):
+    pass
+
 
 class DecodeError(Exception):
     verb = 'decode'
@@ -784,6 +794,18 @@ class Shell(cmd.Cmd):
         return [(field_name, field_type.cql_parameterized_type())
                 for field_name, field_type in zip(user_type.field_names, user_type.field_types)]
 
+    def get_userfunction_names(self, ksname=None):
+        if ksname is None:
+            ksname = self.current_keyspace
+
+        return map(lambda f: f.name, self.get_keyspace_meta(ksname).functions.values())
+
+    def get_useraggregate_names(self, ksname=None):
+        if ksname is None:
+            ksname = self.current_keyspace
+
+        return map(lambda f: f.name, self.get_keyspace_meta(ksname).aggregates.values())
+
     def get_cluster_name(self):
         return self.conn.metadata.cluster_name
 
@@ -1284,6 +1306,8 @@ class Shell(cmd.Cmd):
     def describe_columnfamily(self, ksname, cfname):
         if ksname is None:
             ksname = self.current_keyspace
+        if ksname is None:
+            raise NoKeyspaceError("No keyspace specified and no current keyspace")
         print
         self.print_recreate_columnfamily(ksname, cfname, sys.stdout)
         print
@@ -1301,6 +1325,60 @@ class Shell(cmd.Cmd):
             cmd.Cmd.columnize(self, protect_names(self.get_columnfamily_names(ksname)))
             print
 
+    def describe_functions(self, ksname=None):
+        print
+        if ksname is None:
+            for ksmeta in self.get_keyspaces():
+                name = protect_name(ksmeta.name)
+                print 'Keyspace %s' % (name,)
+                print '---------%s' % ('-' * len(name))
+                cmd.Cmd.columnize(self, protect_names(ksmeta.functions.keys()))
+                print
+        else:
+            ksmeta = self.get_keyspace_meta(ksname)
+            cmd.Cmd.columnize(self, protect_names(ksmeta.functions.keys()))
+            print
+
+    def describe_function(self, ksname, functionname):
+        if ksname is None:
+            ksname = self.current_keyspace
+        if ksname is None:
+            raise NoKeyspaceError("No keyspace specified and no current keyspace")
+        print
+        ksmeta = self.get_keyspace_meta(ksname)
+        functions = filter(lambda f: f.name == functionname, ksmeta.functions.values())
+        if len(functions) == 0:
+            raise FunctionNotFound("User defined function %r not found" % functionname)
+        print "\n\n".join(func.as_cql_query(formatted=True) for func in functions)
+        print
+
+    def describe_aggregates(self, ksname=None):
+        print
+        if ksname is None:
+            for ksmeta in self.get_keyspaces():
+                name = protect_name(ksmeta.name)
+                print 'Keyspace %s' % (name,)
+                print '---------%s' % ('-' * len(name))
+                cmd.Cmd.columnize(self, protect_names(ksmeta.aggregates.keys()))
+                print
+        else:
+            ksmeta = self.get_keyspace_meta(ksname)
+            cmd.Cmd.columnize(self, protect_names(ksmeta.aggregates.keys()))
+            print
+
+    def describe_aggregate(self, ksname, aggregatename):
+        if ksname is None:
+            ksname = self.current_keyspace
+        if ksname is None:
+            raise NoKeyspaceError("No keyspace specified and no current keyspace")
+        print
+        ksmeta = self.get_keyspace_meta(ksname)
+        aggregates = filter(lambda f: f.name == aggregatename, ksmeta.aggregates.values())
+        if len(aggregates) == 0:
+            raise FunctionNotFound("User defined aggregate %r not found" % aggregatename)
+        print "\n\n".join(aggr.as_cql_query(formatted=True) for aggr in aggregates)
+        print
+
     def describe_usertypes(self, ksname):
         print
         if ksname is None:
@@ -1318,6 +1396,8 @@ class Shell(cmd.Cmd):
     def describe_usertype(self, ksname, typename):
         if ksname is None:
             ksname = self.current_keyspace
+        if ksname is None:
+            raise NoKeyspaceError("No keyspace specified and no current keyspace")
         print
         ksmeta = self.get_keyspace_meta(ksname)
         try:
@@ -1395,11 +1475,41 @@ class Shell(cmd.Cmd):
           Output CQL commands that could be used to recreate the entire (non-system) schema.
           Works as though "DESCRIBE KEYSPACE k" was invoked for each non-system keyspace
           k. Use DESCRIBE FULL SCHEMA to include the system keyspaces.
+
+        DESCRIBE FUNCTIONS <keyspace>
+
+          Output the names of all user defined functions in the given keyspace.
+
+        DESCRIBE FUNCTION [<keyspace>.]<function>
+
+          Describe the given user defined function.
+
+        DESCRIBE AGGREGATES <keyspace>
+
+          Output the names of all user defined aggregates in the given keyspace.
+
+        DESCRIBE AGGREGATE [<keyspace>.]<aggregate>
+
+          Describe the given user defined aggregate.
         """
         what = parsed.matched[1][1].lower()
-        if what == 'keyspaces':
+        if what == 'functions':
+            ksname = self.cql_unprotect_name(parsed.get_binding('ksname', None))
+            self.describe_functions(ksname)
+        elif what == 'function':
+            ksname = self.cql_unprotect_name(parsed.get_binding('ksname', None))
+            functionname = self.cql_unprotect_name(parsed.get_binding('udfname'))
+            self.describe_function(ksname, functionname)
+        elif what == 'aggregates':
+            ksname = self.cql_unprotect_name(parsed.get_binding('ksname', None))
+            self.describe_aggregates(ksname)
+        elif what == 'aggregate':
+            ksname = self.cql_unprotect_name(parsed.get_binding('ksname', None))
+            aggregatename = self.cql_unprotect_name(parsed.get_binding('udaname'))
+            self.describe_aggregate(ksname, aggregatename)
+        elif what == 'keyspaces':
             self.describe_keyspaces()
-        if what == 'keyspace':
+        elif what == 'keyspace':
             ksname = self.cql_unprotect_name(parsed.get_binding('ksname', ''))
             if not ksname:
                 ksname = self.current_keyspace

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cfab9dac/lib/cassandra-driver-2.6.0c1.zip
----------------------------------------------------------------------
diff --git a/lib/cassandra-driver-2.6.0c1.zip b/lib/cassandra-driver-2.6.0c1.zip
new file mode 100644
index 0000000..0e77468
Binary files /dev/null and b/lib/cassandra-driver-2.6.0c1.zip differ

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cfab9dac/lib/cassandra-driver-internal-only-2.5.1.post0-074650b.zip
----------------------------------------------------------------------
diff --git a/lib/cassandra-driver-internal-only-2.5.1.post0-074650b.zip b/lib/cassandra-driver-internal-only-2.5.1.post0-074650b.zip
deleted file mode 100644
index ce21a7a..0000000
Binary files a/lib/cassandra-driver-internal-only-2.5.1.post0-074650b.zip and /dev/null differ

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cfab9dac/pylib/cqlshlib/cql3handling.py
----------------------------------------------------------------------
diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py
index ae66a4e..75b2871 100644
--- a/pylib/cqlshlib/cql3handling.py
+++ b/pylib/cqlshlib/cql3handling.py
@@ -209,10 +209,20 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/
;
 <mapLiteral> ::= "{" <term> ":" <term> ( "," <term> ":" <term>
)* "}"
                ;
 
-<userFunctionName> ::= <identifier> ( "." <identifier> )?
-               ;
+<anyFunctionName> ::= ( ksname=<cfOrKsName> dot="." )? udfname=<cfOrKsName>
;
+
+<userFunctionName> ::= ( ksname=<nonSystemKeyspaceName> dot="." )? udfname=<cfOrKsName>
;
+
+<refUserFunctionName> ::= udfname=<cfOrKsName> ;
 
-<functionName> ::= <userFunctionName>
+<userAggregateName> ::= ( ksname=<nonSystemKeyspaceName> dot="." )? udaname=<cfOrKsName>
;
+
+<functionAggregateName> ::= ( ksname=<nonSystemKeyspaceName> dot="." )? functionname=<cfOrKsName>
;
+
+<aggregateName> ::= <userAggregateName>
+                  ;
+
+<functionName> ::= <functionAggregateName>
                  | "TOKEN"
                  ;
 
@@ -645,6 +655,69 @@ syntax_rules += r'''
                   ;
 '''
 
+
+
+def udf_name_completer(ctxt, cass):
+    ks = ctxt.get_binding('ksname', None)
+    if ks is not None:
+        ks = dequote_name(ks)
+    try:
+        udfnames = cass.get_userfunction_names(ks)
+    except Exception:
+        if ks is None:
+            return ()
+        raise
+    return map(maybe_escape_name, udfnames)
+
+
+def uda_name_completer(ctxt, cass):
+    ks = ctxt.get_binding('ksname', None)
+    if ks is not None:
+        ks = dequote_name(ks)
+    try:
+        udanames = cass.get_useraggregate_names(ks)
+    except Exception:
+        if ks is None:
+            return ()
+        raise
+    return map(maybe_escape_name, udanames)
+
+
+def udf_uda_name_completer(ctxt, cass):
+    ks = ctxt.get_binding('ksname', None)
+    if ks is not None:
+        ks = dequote_name(ks)
+    try:
+        functionnames = cass.get_userfunction_names(ks) + cass.get_useraggregate_names(ks)
+    except Exception:
+        if ks is None:
+            return ()
+        raise
+    return map(maybe_escape_name, functionnames)
+
+
+def ref_udf_name_completer(ctxt, cass):
+    try:
+        udanames = cass.get_userfunction_names(None)
+    except Exception:
+        return ()
+    return map(maybe_escape_name, udanames)
+
+
+completer_for('functionAggregateName', 'ksname')(cf_ks_name_completer)
+completer_for('functionAggregateName', 'dot')(cf_ks_dot_completer)
+completer_for('functionAggregateName', 'functionname')(udf_uda_name_completer)
+completer_for('anyFunctionName', 'ksname')(cf_ks_name_completer)
+completer_for('anyFunctionName', 'dot')(cf_ks_dot_completer)
+completer_for('anyFunctionName', 'udfname')(udf_name_completer)
+completer_for('userFunctionName', 'ksname')(cf_ks_name_completer)
+completer_for('userFunctionName', 'dot')(cf_ks_dot_completer)
+completer_for('userFunctionName', 'udfname')(udf_name_completer)
+completer_for('refUserFunctionName', 'udfname')(ref_udf_name_completer)
+completer_for('userAggregateName', 'ksname')(cf_ks_dot_completer)
+completer_for('userAggregateName', 'dot')(cf_ks_dot_completer)
+completer_for('userAggregateName', 'udaname')(uda_name_completer)
+
 @completer_for('orderByClause', 'ordercol')
 def select_order_column_completer(ctxt, cass):
     prev_order_cols = ctxt.get_binding('ordercol', ())
@@ -1034,13 +1107,13 @@ syntax_rules += r'''
 
 <createAggregateStatement> ::= "CREATE" ("OR" "REPLACE")? "AGGREGATE"
                             ("IF" "NOT" "EXISTS")?
-                            <userFunctionName>
+                            <userAggregateName>
                             ( "("
                                  ( <storageType> ( "," <storageType> )* )?
                               ")" )?
-                            "SFUNC" <identifier>
+                            "SFUNC" <refUserFunctionName>
                             "STYPE" <storageType>
-                            ( "FINALFUNC" <identifier> )?
+                            ( "FINALFUNC" <refUserFunctionName> )?
                             ( "INITCOND" <term> )?
                          ;
 
@@ -1078,7 +1151,7 @@ syntax_rules += r'''
 <dropFunctionStatement> ::= "DROP" "FUNCTION" ( "IF" "EXISTS" )? <userFunctionName>
                           ;
 
-<dropAggregateStatement> ::= "DROP" "AGGREGATE" ( "IF" "EXISTS" )? <userFunctionName>
+<dropAggregateStatement> ::= "DROP" "AGGREGATE" ( "IF" "EXISTS" )? <userAggregateName>
                           ;
 
 '''
@@ -1246,7 +1319,11 @@ syntax_rules += r'''
                  ;
 
 <functionResource> ::= ( "ALL" "FUNCTIONS" ("IN KEYSPACE" <keyspaceName>)? )
-                     | ("FUNCTION" <userFunctionName>)
+                     | ( "FUNCTION" <functionAggregateName>
+                           ( "(" ( newcol=<cident> <storageType>
+                             ( "," [newcolname]=<cident> <storageType> )* )?
+                           ")" )
+                       )
                      ;
 '''
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cfab9dac/pylib/cqlshlib/helptopics.py
----------------------------------------------------------------------
diff --git a/pylib/cqlshlib/helptopics.py b/pylib/cqlshlib/helptopics.py
index b38b235..860a582 100644
--- a/pylib/cqlshlib/helptopics.py
+++ b/pylib/cqlshlib/helptopics.py
@@ -183,6 +183,8 @@ class CQLHelpTopics(object):
           HELP DROP_KEYSPACE;
           HELP DROP_TABLE;
           HELP DROP_INDEX;
+          HELP DROP_FUNCTION;
+          HELP DROP_AGGREGATE;
         """
 
     def help_drop_keyspace(self):
@@ -211,6 +213,37 @@ class CQLHelpTopics(object):
         A DROP INDEX statement is used to drop an existing secondary index.
         """
 
+    def help_drop_function(self):
+        print """
+        DROP FUNCTION ( IF EXISTS )?
+                         ( <keyspace> '.' )? <function-name>
+                         ( '(' <arg-type> ( ',' <arg-type> )* ')' )?
+
+        DROP FUNCTION statement removes a function created using CREATE FUNCTION.
+        You must specify the argument types (signature) of the function to drop if there
+        are multiple functions with the same name but a different signature
+        (overloaded functions).
+
+        DROP FUNCTION with the optional IF EXISTS keywords drops a function if it exists.
+        """
+
+    def help_drop_aggregate(self):
+        print """
+        DROP AGGREGATE ( IF EXISTS )?
+                         ( <keyspace> '.' )? <aggregate-name>
+                         ( '(' <arg-type> ( ',' <arg-type> )* ')' )?
+
+        The DROP AGGREGATE statement removes an aggregate created using CREATE AGGREGATE.
+        You must specify the argument types of the aggregate to drop if there are multiple
+        aggregates with the same name but a different signature (overloaded aggregates).
+
+        DROP AGGREGATE with the optional IF EXISTS keywords drops an aggregate if it exists,
+        and does nothing if a function with the signature does not exist.
+
+        Signatures for user-defined aggregates follow the same rules as for
+        user-defined functions.
+        """
+
     def help_truncate(self):
         print """
         TRUNCATE <tablename>;
@@ -227,6 +260,8 @@ class CQLHelpTopics(object):
           HELP CREATE_KEYSPACE;
           HELP CREATE_TABLE;
           HELP CREATE_INDEX;
+          HELP CREATE_FUNCTION;
+          HELP CREATE_AGGREGATE;
         """
 
     def help_use(self):
@@ -243,6 +278,96 @@ class CQLHelpTopics(object):
         number, it can be quoted using double quotes.
         """
 
+    def help_create_aggregate(self):
+        print """
+        CREATE ( OR REPLACE )? AGGREGATE ( IF NOT EXISTS )?
+                            ( <keyspace> '.' )? <aggregate-name>
+                            '(' <arg-type> ( ',' <arg-type> )* ')'
+                            SFUNC ( <keyspace> '.' )? <state-functionname>
+                            STYPE <state-type>
+                            ( FINALFUNC ( <keyspace> '.' )? <final-functionname>
)?
+                            ( INITCOND <init-cond> )?
+
+        CREATE AGGREGATE creates or replaces a user-defined aggregate.
+
+        CREATE AGGREGATE with the optional OR REPLACE keywords either creates an aggregate
+        or replaces an existing one with the same signature. A CREATE AGGREGATE without
+        OR REPLACE fails if an aggregate with the same signature already exists.
+
+        CREATE AGGREGATE with the optional IF NOT EXISTS keywords either creates an aggregate
+        if it does not already exist.
+
+        OR REPLACE and IF NOT EXIST cannot be used together.
+
+        Aggregates belong to a keyspace. If no keyspace is specified in <aggregate-name>,
the
+        current keyspace is used (i.e. the keyspace specified using the USE statement). It
is
+        not possible to create a user-defined aggregate in one of the system keyspaces.
+
+        Signatures for user-defined aggregates follow the same rules as for
+        user-defined functions.
+
+        STYPE defines the type of the state value and must be specified.
+
+        The optional INITCOND defines the initial state value for the aggregate. It defaults
+        to null. A non-null INITCOND must be specified for state functions that are declared
+        with RETURNS NULL ON NULL INPUT.
+
+        SFUNC references an existing function to be used as the state modifying function.
The
+        type of first argument of the state function must match STYPE. The remaining argument
+        types of the state function must match the argument types of the aggregate function.
+        State is not updated for state functions declared with RETURNS NULL ON NULL INPUT
and
+        called with null.
+
+        The optional FINALFUNC is called just before the aggregate result is returned. It
must
+        take only one argument with type STYPE. The return type of the FINALFUNC may be a
+        different type. A final function declared with RETURNS NULL ON NULL INPUT means that
+        the aggregate's return value will be null, if the last state is null.
+
+        If no FINALFUNC is defined, the overall return type of the aggregate function is
STYPE.
+        If a FINALFUNC is defined, it is the return type of that function.
+        """
+
+    def help_create_function(self):
+        print """
+        CREATE ( OR REPLACE )? FUNCTION ( IF NOT EXISTS )?
+                            ( <keyspace> '.' )? <function-name>
+                            '(' <arg-name> <arg-type> ( ',' <arg-name>
<arg-type> )* ')'
+                            ( CALLED | RETURNS NULL ) ON NULL INPUT
+                            RETURNS <type>
+                            LANGUAGE <language>
+                            AS <body>
+
+        CREATE FUNCTION creates or replaces a user-defined function.
+
+        Signatures are used to distinguish individual functions. The signature consists of:
+
+        The fully qualified function name - i.e keyspace plus function-name
+        The concatenated list of all argument types
+
+        Note that keyspace names, function names and argument types are subject to the default
+        naming conventions and case-sensitivity rules.
+
+        CREATE FUNCTION with the optional OR REPLACE keywords either creates a function or
+        replaces an existing one with the same signature. A CREATE FUNCTION without OR REPLACE
+        fails if a function with the same signature already exists.
+
+        Behavior on invocation with null values must be defined for each function. There
are
+        two options:
+
+        RETURNS NULL ON NULL INPUT declares that the function will always return null if
any
+        of the input arguments is null. CALLED ON NULL INPUT declares that the function will
+        always be executed.
+
+        If the optional IF NOT EXISTS keywords are used, the function will only be created
if
+        another function with the same signature does not exist.
+
+        OR REPLACE and IF NOT EXIST cannot be used together.
+
+        Functions belong to a keyspace. If no keyspace is specified in <function-name>,
the
+        current keyspace is used (i.e. the keyspace specified using the USE statement).
+        It is not possible to create a user-defined function in one of the system keyspaces.
+        """
+
     def help_create_table(self):
         print """
         CREATE TABLE <cfname> ( <colname> <type> PRIMARY KEY [,

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cfab9dac/src/java/org/apache/cassandra/cql3/Cql.g
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Cql.g b/src/java/org/apache/cassandra/cql3/Cql.g
index d49dbd3..094b72e 100644
--- a/src/java/org/apache/cassandra/cql3/Cql.g
+++ b/src/java/org/apache/cassandra/cql3/Cql.g
@@ -171,6 +171,8 @@ options {
 
     public Set<Permission> filterPermissions(Set<Permission> permissions, IResource
resource)
     {
+        if (resource == null)
+            return Collections.emptySet();
         Set<Permission> filtered = new HashSet<>(permissions);
         filtered.retainAll(resource.applicablePermissions());
         if (filtered.isEmpty())


Mime
View raw message