This is an automated email from the ASF dual-hosted git repository. brads pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/cassandra.git
commit 91bc34fbf86f65d8872c9610fdb7b63561190894 Author: Naren Sreedhara <naren.mys...@gmail.com> AuthorDate: Thu Aug 1 10:43:46 2024 -0400 Added tab-ahead support for new built-in functions patch by Brad Schoening, Naren Sreedhara; reviewed by Stefan Miklosovic, Bernardo Botella Corbi for CASSANDRA-19631 --- CHANGES.txt | 1 + doc/modules/cassandra/examples/CQL/to_date.cql | 1 + .../cassandra/pages/developing/cql/functions.adoc | 11 +++- pylib/cqlshlib/cql3handling.py | 77 +++++++++++++++++++--- pylib/cqlshlib/cqlshmain.py | 2 +- pylib/cqlshlib/test/test_cqlsh_completion.py | 65 +++++++++++++++--- 6 files changed, 136 insertions(+), 21 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 5a60651cfc..764246421a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 5.1 + * Add autocompletion in CQLSH for built-in functions (CASSANDRA-19631) * Introduce metadata serialization version V4 (CASSANDRA-19970) * Allow CMS reconfiguration to work around DOWN nodes (CASSANDRA-19943) * Make TableParams.Serializer set allowAutoSnapshots and incrementalBackups (CASSANDRA-19954) diff --git a/doc/modules/cassandra/examples/CQL/to_date.cql b/doc/modules/cassandra/examples/CQL/to_date.cql new file mode 100644 index 0000000000..160dcaab67 --- /dev/null +++ b/doc/modules/cassandra/examples/CQL/to_date.cql @@ -0,0 +1 @@ +SELECT id, to_date(create_ts) FROM myTable diff --git a/doc/modules/cassandra/pages/developing/cql/functions.adoc b/doc/modules/cassandra/pages/developing/cql/functions.adoc index 75786de271..d74beb1446 100644 --- a/doc/modules/cassandra/pages/developing/cql/functions.adoc +++ b/doc/modules/cassandra/pages/developing/cql/functions.adoc @@ -184,12 +184,12 @@ time where the function is invoked: |=== |Function name |Output type -| `current_timestamp` | `timestamp` - | `current_date` | `date` | `current_time` | `time` +| `current_timestamp` | `timestamp` + | `current_timeuuid` | `timeUUID` |=== @@ -223,6 +223,13 @@ A number of functions are provided to convert a `timeuuid`, a `timestamp` or a ` | `to_unix_timestamp` | `date` | Converts the `date` argument into a `bigInt` raw value |=== +For example, a timestamp can be converted to a date with the following: + +[source,cql] +---- +include::cassandra:example$CQL/to_date.cql[] +---- + ==== Blob conversion functions A number of functions are provided to convert the native types into diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py index 457582f3d3..101c39ca6c 100644 --- a/pylib/cqlshlib/cql3handling.py +++ b/pylib/cqlshlib/cql3handling.py @@ -602,7 +602,7 @@ def cf_prop_val_mapender_completer(ctxt, cass): @completer_for('tokenDefinition', 'token') def token_word_completer(ctxt, cass): - return ['token('] + return ['TOKEN'] @completer_for('simpleStorageType', 'typename') @@ -741,12 +741,13 @@ syntax_rules += r''' ; <whereClause> ::= <relation> ( "AND" <relation> )* ; -<relation> ::= [rel_lhs]=<cident> ( "[" <term> "]" )? ( "=" | "<" | ">" | "<=" | ">=" | "!=" | ( "NOT" )? "CONTAINS" ( "KEY" )? ) <term> +<relation> ::= [rel_lhs]=<cident> ( "[" <term> "]" )? ( "=" | "<" | ">" | "<=" | ">=" | "!=" | ( "NOT" )? "CONTAINS" ( "KEY" )? ) (<term> | <operandFunctions>) | token="TOKEN" "(" [rel_tokname]=<cident> ( "," [rel_tokname]=<cident> )* ")" ("=" | "<" | ">" | "<=" | ">=") <tokenDefinition> | [rel_lhs]=<cident> (( "NOT" )? "IN" ) "(" <term> ( "," <term> )* ")" | [rel_lhs]=<cident> "BETWEEN" <term> "AND" <term> + | <operandFunctions> ; <selectClause> ::= "DISTINCT"? <selector> ("AS" <cident>)? ("," <selector> ("AS" <cident>)?)* | "*" @@ -755,14 +756,20 @@ syntax_rules += r''' ; <selector> ::= [colname]=<cident> ( "[" ( <term> ( ".." <term> "]" )? | <term> ".." ) )? | <udtSubfieldSelection> - | "WRITETIME" "(" [colname]=<cident> ")" - | "MAXWRITETIME" "(" [colname]=<cident> ")" - | "TTL" "(" [colname]=<cident> ")" - | "COUNT" "(" star=( "*" | "1" ) ")" | "CAST" "(" <selector> "AS" <storageType> ")" + | "TTL" "(" [colname]=<cident> ")" + | "TOKEN" "(" [colname]=<cident> ")" + | <aggregateMathFunctions> + | <scalarMathFunctions> + | <collectionFunctions> + | <currentTimeFunctions> + | <maskFunctions> + | <timeConversionFunctions> + | <writetimeFunctions> | <functionName> <selectionFunctionArguments> | <term> ; + <selectionFunctionArguments> ::= "(" ( <selector> ( "," <selector> )* )? ")" ; <orderByClause> ::= [ordercol]=<cident> ( "ASC" | "DESC" )? @@ -775,6 +782,60 @@ syntax_rules += r''' <groupByFunctionArgument> ::= [groupcol]=<cident> | <term> ; + +<aggregateMathFunctions> ::= "COUNT" "(" star=( "*" | "1" ) ")" + | "AVG" "(" [colname]=<cident> ")" + | "MIN" "(" [colname]=<cident> ")" + | "MAX" "(" [colname]=<cident> ")" + | "SUM" "(" [colname]=<cident> ")" + ; + +<scalarMathFunctions> ::= "ABS" "(" [colname]=<cident> ")" + | "EXP" "(" [colname]=<cident> ")" + | "LOG" "(" [colname]=<cident> ")" + | "LOG10" "(" [colname]=<cident> ")" + | "ROUND" "(" [colname]=<cident> ")" + ; + +<collectionFunctions> ::= "MAP_KEYS" "(" [colname]=<cident> ")" + | "MAP_VALUES" "(" [colname]=<cident> ")" + | "COLLECTION_AVG" "(" [colname]=<cident> ")" + | "COLLECTION_COUNT" "(" [colname]=<cident> ")" + | "COLLECTION_MIN" "(" [colname]=<cident> ")" + | "COLLECTION_MAX" "(" [colname]=<cident> ")" + | "COLLECTION_SUM" "(" [colname]=<cident> ")" + ; + +<currentTimeFunctions> ::= "CURRENT_DATE()" + | "CURRENT_TIME()" + | "CURRENT_TIMESTAMP()" + | "CURRENT_TIMEUUID()" + ; + +<maskFunctions> ::= "MASK_DEFAULT" "(" [colname]=<cident> ")" + | "MASK_HASH" "(" [colname]=<cident> ")" + | "MASK_INNER" "(" [colname]=<cident> "," <wholenumber> "," <wholenumber> ")" + | "MASK_NULL" "(" [colname]=<cident> ")" + | "MASK_REPLACE" "(" [colname]=<cident> "," <propertyValue> ")" + | "MASK_OUTER" "(" [colname]=<cident> "," <wholenumber> "," <wholenumber> ")" + ; + +<timeConversionFunctions> ::= "TO_DATE" "(" [colname]=<cident> ")" + | "TO_TIMESTAMP" "(" [colname]=<cident> ")" + | "TO_UNIX_TIMESTAMP" "(" [colname]=<cident> ")" + ; + +<timeuuidFunctions> ::= "MAX_TIMEUUID" "(" [colname]=<cident> ")" + | "MIN_TIMEUUID" "(" [colname]=<cident> ")" + ; + +<writetimeFunctions> ::= "MAX_WRITETIME" "(" [colname]=<cident> ")" + | "MIN_WRITETIME" "(" [colname]=<cident> ")" + | "WRITETIME" "(" [colname]=<cident> ")" + ; +<operandFunctions> ::= <currentTimeFunctions> | <timeuuidFunctions> + ; + ''' @@ -867,7 +928,7 @@ def select_group_column_completer(ctxt, cass): @completer_for('relation', 'token') def relation_token_word_completer(ctxt, cass): - return ['TOKEN('] + return ['TOKEN'] @completer_for('relation', 'rel_tokname') @@ -1001,7 +1062,7 @@ syntax_rules += r''' @completer_for('updateStatement', 'updateopt') def update_option_completer(ctxt, cass): - opts = set('TIMESTAMP TTL'.split()) + opts = {'TIMESTAMP', 'TTL'} for opt in ctxt.get_binding('updateopt', ()): opts.discard(opt.split()[0]) return opts diff --git a/pylib/cqlshlib/cqlshmain.py b/pylib/cqlshlib/cqlshmain.py index 2cac58ef22..2bc79f3c37 100755 --- a/pylib/cqlshlib/cqlshmain.py +++ b/pylib/cqlshlib/cqlshmain.py @@ -383,7 +383,7 @@ class Shell(cmd.Cmd): baseversion = baseversion[0:extra] if baseversion != build_version: print("WARNING: cqlsh was built against {}, but this server is {}. All features may not work!" - .format(build_version, baseversion)) # ToDo: use file=sys.stderr) + .format(build_version, baseversion), file=sys.stderr) @property def batch_mode(self): diff --git a/pylib/cqlshlib/test/test_cqlsh_completion.py b/pylib/cqlshlib/test/test_cqlsh_completion.py index 112474e7c7..53ab1908ed 100644 --- a/pylib/cqlshlib/test/test_cqlsh_completion.py +++ b/pylib/cqlshlib/test/test_cqlsh_completion.py @@ -114,7 +114,8 @@ class CqlshCompletionCase(BaseTestCase): def _trycompletions_inner(self, inputstring, immediate='', choices=(), other_choices_ok=False, - split_completed_lines=True): + split_completed_lines=True, + ignore_system_keyspaces=False): """ Test tab completion in cqlsh. Enters in the text in inputstring, then simulates a tab keypress to see what is immediately completed (this @@ -132,17 +133,22 @@ class CqlshCompletionCase(BaseTestCase): self.assertEqual(completed, immediate, msg=msg) return + if ignore_system_keyspaces: + completed = list(filter(lambda s: not s.startswith('system'), completed)) + if other_choices_ok: self.assertEqual(set(choices), completed.intersection(choices)) else: self.assertEqual(set(choices), set(completed)) def trycompletions(self, inputstring, immediate='', choices=(), - other_choices_ok=False, split_completed_lines=True): + other_choices_ok=False, split_completed_lines=True, + ignore_system_keyspaces=False): try: self._trycompletions_inner(inputstring, immediate, choices, other_choices_ok=other_choices_ok, - split_completed_lines=split_completed_lines) + split_completed_lines=split_completed_lines, + ignore_system_keyspaces=ignore_system_keyspaces) finally: try: self.cqlsh.send(CTRL_C) # cancel any current line @@ -175,7 +181,43 @@ class TestCqlshCompletion(CqlshCompletionCase): pass def test_complete_in_select(self): - pass + self.trycompletions('SELECT ', + choices=('*', '<colname>', + '-', '<blobLiteral>', '<float>', '<wholenumber>', '<uuid>', + '<identifier>', '<pgStringLiteral>', '<quotedStringLiteral>', + 'ABS', 'AVG', 'CAST', 'COUNT', 'DISTINCT', + 'EXP', 'JSON', 'LOG', 'LOG10', + 'MAP_KEYS', 'MAP_VALUES', + 'MIN', 'MAX', + 'MIN_WRITETIME', 'MAX_WRITETIME', + 'ROUND', 'SUM', 'TOKEN', + 'TO_DATE', 'TO_TIMESTAMP', 'TO_UNIX_TIMESTAMP', + 'TTL', 'WRITETIME', + 'COLLECTION_AVG', 'COLLECTION_COUNT', 'COLLECTION_MAX', + 'COLLECTION_MIN', 'COLLECTION_SUM', + 'MASK_DEFAULT', 'MASK_HASH', 'MASK_INNER', 'MASK_NULL', + 'MASK_OUTER', 'MASK_REPLACE', + '[', '{', 'false', 'true', 'NULL' + ), + other_choices_ok=True + ) + + def test_complete_in_select_where(self): + self.trycompletions('SELECT * FROM system.peers WHERE ', + choices=('<identifier>', '<quotedName>', 'peer', 'CURRENT_DATE()', 'CURRENT_TIME()', + 'CURRENT_TIMEUUID()', 'CURRENT_TIMESTAMP()', 'TOKEN', + 'MIN_TIMEUUID', 'MAX_TIMEUUID') + ) + + def test_complete_in_select_where_equal(self): + self.trycompletions('SELECT * FROM system.peers WHERE rack = ', + choices=('-', '<blobLiteral>', '<float>', '<wholenumber>', '<uuid>', + '<identifier>', '<pgStringLiteral>', '<quotedStringLiteral>', + '[', '{', 'false', 'true', 'NULL', + 'TOKEN', 'MIN_TIMEUUID', 'MAX_TIMEUUID', + 'CURRENT_DATE()', 'CURRENT_TIME()', 'CURRENT_TIMEUUID()', 'CURRENT_TIMESTAMP()' + ) + ) def test_complete_in_insert(self): self.trycompletions('INSERT INTO ', @@ -376,7 +418,8 @@ class TestCqlshCompletion(CqlshCompletionCase): self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs'", choices=[',', 'WHERE']) self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE ", - choices=['TOKEN(', 'lonelykey']) + choices=['CURRENT_DATE()', 'CURRENT_TIME()', 'CURRENT_TIMESTAMP()', + 'CURRENT_TIMEUUID()', 'TOKEN', 'MIN_TIMEUUID', 'MAX_TIMEUUID', 'lonelykey']) self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE lonel", immediate='ykey ') @@ -385,7 +428,8 @@ class TestCqlshCompletion(CqlshCompletionCase): self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE lonelykey = 0.0 ", choices=['AND', 'IF', ';']) self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE lonelykey = 0.0 AND ", - choices=['TOKEN(', 'lonelykey']) + choices=['CURRENT_DATE()', 'CURRENT_TIME()', 'CURRENT_TIMESTAMP()', + 'CURRENT_TIMEUUID()', 'TOKEN', 'MIN_TIMEUUID', 'MAX_TIMEUUID', 'lonelykey']) self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE TOKEN(lonelykey ", choices=[',', ')']) @@ -397,7 +441,7 @@ class TestCqlshCompletion(CqlshCompletionCase): choices=['EXISTS', '<quotedName>', '<identifier>']) self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE TOKEN(lonelykey) <= TOKEN(13) IF EXISTS ", - choices=['>=', '!=', '<=', 'IN','[', ';', '=', '<', '>', '.', 'CONTAINS']) + choices=['>=', '!=', '<=', 'IN', '[', ';', '=', '<', '>', '.', 'CONTAINS']) self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE TOKEN(lonelykey) <= TOKEN(13) IF lonelykey ", choices=['>=', '!=', '<=', 'IN', '=', '<', '>', 'CONTAINS']) @@ -461,10 +505,11 @@ class TestCqlshCompletion(CqlshCompletionCase): self.trycompletions('DELETE FROM twenty_rows_composite_table USING TIMESTAMP 0 ', immediate='WHERE ') self.trycompletions('DELETE FROM twenty_rows_composite_table USING TIMESTAMP 0 WHERE ', - choices=['a', 'b', 'TOKEN(']) + choices=['a', 'b', 'CURRENT_DATE()', 'CURRENT_TIME()', 'CURRENT_TIMESTAMP()', + 'CURRENT_TIMEUUID()', 'MAX_TIMEUUID', 'MIN_TIMEUUID', 'TOKEN']) self.trycompletions('DELETE FROM twenty_rows_composite_table USING TIMESTAMP 0 WHERE a ', - choices=['<=', '>=', 'BETWEEN', 'CONTAINS', 'IN', 'NOT' , '[', '=', '<', '>', '!=']) + choices=['<=', '>=', 'BETWEEN', 'CONTAINS', 'IN', 'NOT', '[', '=', '<', '>', '!=']) self.trycompletions('DELETE FROM twenty_rows_composite_table USING TIMESTAMP 0 WHERE TOKEN(', immediate='a ') @@ -476,7 +521,7 @@ class TestCqlshCompletion(CqlshCompletionCase): choices=['>=', '<=', '=', '<', '>']) self.trycompletions('DELETE FROM twenty_rows_composite_table USING TIMESTAMP 0 WHERE TOKEN(a) >= ', choices=['false', 'true', '<pgStringLiteral>', - 'token(', '-', '<float>', 'TOKEN', + '-', '<float>', 'TOKEN', '<identifier>', '<uuid>', '{', '[', 'NULL', '<quotedStringLiteral>', '<blobLiteral>', '<wholenumber>']) --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org For additional commands, e-mail: commits-h...@cassandra.apache.org