mirror of https://github.com/apache/cassandra
Update cqlsh for UDFs
patch by Robert Stupp; reviewed by Aleksey Yeschenko for CASSANDRA-7556
This commit is contained in:
parent
6e1bdeb142
commit
cfab9dac19
|
|
@ -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)
|
||||
|
|
|
|||
116
bin/cqlsh
116
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
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -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> ;
|
||||
|
||||
<functionName> ::= <userFunctionName>
|
||||
<userFunctionName> ::= ( ksname=<nonSystemKeyspaceName> dot="." )? udfname=<cfOrKsName> ;
|
||||
|
||||
<refUserFunctionName> ::= udfname=<cfOrKsName> ;
|
||||
|
||||
<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> )* )?
|
||||
")" )
|
||||
)
|
||||
;
|
||||
'''
|
||||
|
||||
|
|
|
|||
|
|
@ -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 [,
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
Loading…
Reference in New Issue