Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cassandra 19631: Add support for tab-ahead for new built-in functions and new unit tests #3949

Open
wants to merge 2 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/modules/cassandra/examples/CQL/to_date.cql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SELECT id, to_date(create_ts) FROM myTable
11 changes: 9 additions & 2 deletions doc/modules/cassandra/pages/developing/cql/functions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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`
|===

Expand Down Expand Up @@ -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
Expand Down
77 changes: 69 additions & 8 deletions pylib/cqlshlib/cql3handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -741,12 +741,13 @@ def working_on_keyspace(ctxt):
;
<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>)?)*
| "*"
Expand All @@ -755,14 +756,20 @@ def working_on_keyspace(ctxt):
;
<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" )?
Expand All @@ -775,6 +782,60 @@ def working_on_keyspace(ctxt):
<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>
;

'''


Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -1001,7 +1062,7 @@ def insert_option_completer(ctxt, cass):

@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
Expand Down
2 changes: 1 addition & 1 deletion pylib/cqlshlib/cqlshmain.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ def check_build_versions(self):
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):
Expand Down
65 changes: 55 additions & 10 deletions pylib/cqlshlib/test/test_cqlsh_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ def _get_completions(self, inputstring, split_completed_lines=True):

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
Expand All @@ -132,17 +133,22 @@ def _trycompletions_inner(self, inputstring, immediate='', choices=(),
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
Expand Down Expand Up @@ -175,7 +181,43 @@ def test_complete_in_uuid(self):
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 ',
Expand Down Expand Up @@ -376,7 +418,8 @@ def test_complete_in_update(self):
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 ')
Expand All @@ -385,7 +428,8 @@ def test_complete_in_update(self):
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=[',', ')'])
Expand All @@ -397,7 +441,7 @@ def test_complete_in_update(self):
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'])
Expand Down Expand Up @@ -461,10 +505,11 @@ def test_complete_in_delete(self):
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 ')
Expand All @@ -476,7 +521,7 @@ def test_complete_in_delete(self):
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>'])
Expand Down