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
This commit is contained in:
Naren Sreedhara 2024-08-01 10:43:46 -04:00 committed by Brad Schoening
parent 6111a60ad6
commit 91bc34fbf8
6 changed files with 136 additions and 21 deletions

View File

@ -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)

View File

@ -0,0 +1 @@
SELECT id, to_date(create_ts) FROM myTable

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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>'])