cqlsh: update recognized syntax for cql3

Patch by paul cannon, reviewed by brandonwilliams for CASSANDRA-4198
This commit is contained in:
Brandon Williams 2012-05-18 10:22:23 -05:00
parent fd92c09d95
commit 38748b43d8
5 changed files with 1480 additions and 794 deletions

487
bin/cqlsh
View File

@ -60,7 +60,6 @@ if os.path.isdir(cqlshlibdir):
sys.path.insert(0, cqlshlibdir)
from cqlshlib import cqlhandling, cql3handling, pylexotron, wcwidth
from cqlshlib.cqlhandling import cql_typename
try:
import readline
@ -159,7 +158,7 @@ else:
debug_completion = bool(os.environ.get('CQLSH_DEBUG_COMPLETION', '') == 'YES')
# we want the cql parser to understand our cqlsh-specific commands too
cqlhandling.commands_end_with_newline.update((
my_commands_ending_with_newline = (
'help',
'?',
'describe',
@ -171,9 +170,16 @@ cqlhandling.commands_end_with_newline.update((
'debug',
'exit',
'quit'
))
)
cqlhandling.CqlRuleSet.append_rules(r'''
cqlsh_syntax_completers = []
def cqlsh_syntax_completer(rulename, termname):
def registrator(f):
cqlsh_syntax_completers.append((rulename, termname, f))
return f
return registrator
cqlsh_extra_syntax_rules = r'''
<cqlshCommand> ::= <CQL_Statement>
| <specialCommand> ( ";" | "\n" )
;
@ -187,9 +193,10 @@ cqlhandling.CqlRuleSet.append_rules(r'''
| <exitCommand>
;
<describeCommand> ::= ( "DESCRIBE" | "DESC" ) ( "KEYSPACE" ksname=<name>?
| "COLUMNFAMILY" cfname=<name>
| "COLUMNFAMILIES"
<describeCommand> ::= ( "DESCRIBE" | "DESC" )
( "KEYSPACE" ksname=<keyspaceName>?
| ( "COLUMNFAMILY" | "TABLE" ) cf=<columnFamilyName>
| ( "COLUMNFAMILIES" | "TABLES" )
| "SCHEMA"
| "CLUSTER" )
;
@ -197,13 +204,13 @@ cqlhandling.CqlRuleSet.append_rules(r'''
<showCommand> ::= "SHOW" what=( "VERSION" | "HOST" | "ASSUMPTIONS" )
;
<assumeCommand> ::= "ASSUME" ( ks=<name> "." )? cf=<name> <assumeTypeDef>
( "," <assumeTypeDef> )*
<assumeCommand> ::= "ASSUME" cf=<columnFamilyName> <assumeTypeDef>
( "," <assumeTypeDef> )*
;
<assumeTypeDef> ::= "NAMES" "ARE" names=<storageType>
| "VALUES" "ARE" values=<storageType>
| "(" colname=<name> ")" "VALUES" "ARE" colvalues=<storageType>
| "(" colname=<colname> ")" "VALUES" "ARE" colvalues=<storageType>
;
<sourceCommand> ::= "SOURCE" fname=<stringLiteral>
@ -212,7 +219,8 @@ cqlhandling.CqlRuleSet.append_rules(r'''
<captureCommand> ::= "CAPTURE" ( fname=( <stringLiteral> | "OFF" ) )?
;
<debugCommand> ::= "DEBUG"
# avoiding just "DEBUG" so that this rule doesn't get treated as a terminal
<debugCommand> ::= "DEBUG" "THINGS"?
;
<helpCommand> ::= ( "HELP" | "?" ) [topic]=( <identifier> | <stringLiteral> )*
@ -222,35 +230,16 @@ cqlhandling.CqlRuleSet.append_rules(r'''
;
<qmark> ::= "?" ;
''')
'''
@cqlhandling.cql_add_completer('helpCommand', 'topic')
@cqlsh_syntax_completer('helpCommand', 'topic')
def complete_help(ctxt, cqlsh):
helpfuncs = [n[5:].upper() for n in cqlsh.get_names() if n.startswith('help_')]
funcs_with_docstrings = [n[3:].upper() for n in cqlsh.get_names()
if n.startswith('do_') and getattr(cqlsh, n, None).__doc__]
return sorted(helpfuncs + funcs_with_docstrings)
@cqlhandling.cql_add_completer('describeCommand', 'ksname')
def complete_describe_ks(ctxt, cqlsh):
return map(cqlsh.cql_protect_name, cqlsh.get_keyspace_names())
@cqlhandling.cql_add_completer('describeCommand', 'cfname')
def complete_describe_cf(ctxt, cqlsh):
return map(cqlsh.cql_protect_name, cqlsh.get_columnfamily_names())
@cqlhandling.cql_add_completer('assumeCommand', 'ks')
def complete_assume_ks(ctxt, cqlsh):
return [cqlsh.cql_protect_name(ks) + '.' for ks in cqlsh.get_keyspace_names()]
@cqlhandling.cql_add_completer('assumeCommand', 'cf')
def complete_assume_cf(ctxt, cqlsh):
ks = ctxt.get_binding('ks', None)
if ks is not None:
ks = cqlsh.cql_unprotect_name(ks)
return map(cqlsh.cql_protect_name, cqlsh.get_columnfamily_names(ks))
@cqlhandling.cql_add_completer('assumeTypeDef', 'colname')
@cqlsh_syntax_completer('assumeTypeDef', 'colname')
def complete_assume_col(ctxt, cqlsh):
ks = ctxt.get_binding('ks', None)
ks = cqlsh.cql_unprotect_name(ks) if ks is not None else None
@ -277,9 +266,9 @@ def complete_source_quoted_filename(ctxt, cqlsh):
annotated.append(match)
return annotated
cqlhandling.cql_add_completer('sourceCommand', 'fname') \
cqlsh_syntax_completer('sourceCommand', 'fname') \
(complete_source_quoted_filename)
cqlhandling.cql_add_completer('captureCommand', 'fname') \
cqlsh_syntax_completer('captureCommand', 'fname') \
(complete_source_quoted_filename)
class NoKeyspaceError(Exception):
@ -310,6 +299,13 @@ class DecodeError(Exception):
def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, self.message())
def full_cql_version(ver):
while ver.count('.') < 2:
ver += '.0'
ver_parts = ver.split('-', 1) + ['']
vertuple = tuple(map(int, ver_parts[0].split('.')) + [ver_parts[1]])
return ver, vertuple
def trim_if_present(s, prefix):
if s.startswith(prefix):
return s[len(prefix):]
@ -441,7 +437,7 @@ def format_value(val, casstype, output_encoding, addcolor=False, time_format='',
elif casstype in ('DecimalType', 'UUIDType', 'BooleanType'):
# let python do these for us
bval = str(val)
color = colormap[cql_typename(casstype)]
color = colormap[cqlruleset.cql_typename(casstype)]
elif casstype == 'BytesType':
bval = ''.join('%02x' % ord(c) for c in val)
color = colormap['hex']
@ -527,12 +523,10 @@ class Shell(cmd.Cmd):
self.query_out = sys.stdout
def set_expanded_cql_version(self, ver):
while ver.count('.') < 2:
ver += '.0'
ver, vertuple = full_cql_version(ver)
self.set_cql_version(ver)
self.cql_version = ver
ver_parts = ver.split('-', 1) + ['']
self.cql_ver_tuple = tuple(map(int, ver_parts[0].split('.')) + [ver_parts[1]])
self.cql_ver_tuple = vertuple
def cqlver_atleast(self, major, minor=0, patch=0):
return self.cql_ver_tuple[:3] >= (major, minor, patch)
@ -581,14 +575,14 @@ class Shell(cmd.Cmd):
cf = self.cql_protect_name(cf)
if override.default_name_type:
print 'ASSUME %s NAMES ARE %s;' \
% (cf, cql_typename(override.default_name_type))
% (cf, cqlruleset.cql_typename(override.default_name_type))
if override.default_value_type:
print 'ASSUME %s VALUES ARE %s;' \
% (cf, cql_typename(override.default_value_type))
% (cf, cqlruleset.cql_typename(override.default_value_type))
for colname, vtype in override.value_types.items():
colname = self.cql_protect_name(colname)
print 'ASSUME %s(%s) VALUES ARE %s;' \
% (cf, colname, cql_typename(vtype))
% (cf, colname, cqlruleset.cql_typename(vtype))
print
def get_cluster_versions(self):
@ -647,8 +641,7 @@ class Shell(cmd.Cmd):
indnames.append(md.index_name)
return indnames
def filterable_column_names(self, cfname, ksname=None):
cfdef = self.get_columnfamily(cfname, ksname=ksname)
def filterable_column_names(self, cfdef):
filterable = set()
if cfdef.key_alias is not None and cfdef.key_alias != 'KEY':
filterable.add(cfdef.key_alias)
@ -705,6 +698,8 @@ class Shell(cmd.Cmd):
# ===== cql3-dependent parts =====
def get_columnfamily_layout(self, ksname, cfname):
if ksname is None:
ksname = self.current_keyspace
self.cursor.execute("""select * from system.schema_columnfamilies
where "keyspace"=:ks and "columnfamily"=:cf""",
{'ks': ksname, 'cf': cfname})
@ -792,7 +787,7 @@ class Shell(cmd.Cmd):
"""
try:
statements, in_batch = cqlhandling.cql_split_statements(statementtext)
statements, in_batch = cqlruleset.cql_split_statements(statementtext)
except pylexotron.LexingError, e:
if self.show_line_nums:
self.printerr('Invalid syntax at char %d' % (e.charnum,))
@ -837,20 +832,20 @@ class Shell(cmd.Cmd):
cmdword = 'help'
custom_handler = getattr(self, 'do_' + cmdword.lower(), None)
if custom_handler:
parsed = cqlhandling.cql_whole_parse_tokens(tokens, srcstr=srcstr,
startsymbol='cqlshCommand')
parsed = cqlruleset.cql_whole_parse_tokens(tokens, srcstr=srcstr,
startsymbol='cqlshCommand')
if parsed and not parsed.remainder:
# successful complete parse
return custom_handler(parsed)
else:
return self.handle_parse_error(cmdword, tokens, parsed, srcstr)
return self.perform_statement(cqlhandling.cql_extract_orig(tokens, srcstr))
return self.perform_statement(cqlruleset.cql_extract_orig(tokens, srcstr))
def handle_parse_error(self, cmdword, tokens, parsed, srcstr):
if cmdword.lower() == 'select':
# hey, maybe they know about some new syntax we don't. type
# assumptions won't work, but maybe the query will.
return self.perform_statement(cqlhandling.cql_extract_orig(tokens, srcstr))
return self.perform_statement(cqlruleset.cql_extract_orig(tokens, srcstr))
if parsed:
self.printerr('Improper %s command (problem at %r).' % (cmdword, parsed.remainder[0]))
else:
@ -861,13 +856,14 @@ class Shell(cmd.Cmd):
USE <keyspacename>;
Tells cqlsh and the connected Cassandra instance that you will be
working in the given keyspace. All subsequent operations on column
families or indexes will be in the context of this keyspace, unless
otherwise specified, until another USE command is issued or the
connection terminates.
working in the given keyspace. All subsequent operations on tables
or indexes will be in the context of this keyspace, unless otherwise
specified, until another USE command is issued or the connection
terminates.
As always, when a keyspace name does not work as a normal identifier or
number, it can be enclosed in quotes and expressed as a string literal.
number, it can be quoted using single quotes (CQL 2) or double quotes
(CQL 3).
"""
ksname = parsed.get_binding('ksname')
if self.perform_statement(parsed.extract_orig()):
@ -876,19 +872,22 @@ class Shell(cmd.Cmd):
def do_select(self, parsed):
"""
SELECT [FIRST n] [REVERSED] <selectExpr>
FROM [<keyspace>.]<columnFamily>
FROM [<keyspace>.]<table>
[USING CONSISTENCY <consistencylevel>]
[WHERE <clause>]
[ORDER BY <colname> [DESC]]
[LIMIT m];
SELECT is used to read one or more records from a Cassandra column
family. It returns a result-set of rows, where each row consists of a
key and a collection of columns corresponding to the query.
SELECT is used to read one or more records from a CQL table. It returns
a set of rows matching the selection criteria specified.
Note that FIRST and REVERSED are only supported in CQL 2, and ORDER BY
is only supported in CQL 3 and higher.
For more information, see one of the following:
HELP SELECT_EXPR
HELP SELECT_COLUMNFAMILY
HELP SELECT_TABLE
HELP SELECT_WHERE
HELP SELECT_LIMIT
HELP CONSISTENCYLEVEL
@ -1053,29 +1052,27 @@ class Shell(cmd.Cmd):
begidx = readline.get_begidx() + len(prevlines)
endidx = readline.get_endidx() + len(prevlines)
stuff_to_complete = wholestmt[:begidx]
return cqlhandling.cql_complete(stuff_to_complete, text, cassandra_conn=self,
debug=debug_completion, startsymbol='cqlshCommand')
return cqlruleset.cql_complete(stuff_to_complete, text, cassandra_conn=self,
debug=debug_completion, startsymbol='cqlshCommand')
def set_prompt(self, prompt):
if self.prompt != '':
self.prompt = prompt
def cql_protect_name(self, name):
if self.cqlver_atleast(3):
return cql3handling.maybe_cql3_escape_name(name)
else:
return cqlhandling.maybe_cql_escape(name)
return cqlruleset.maybe_escape_name(name)
def cql_protect_value(self, value):
return cqlhandling.cql_escape(value)
return cqlruleset.escape_value(value)
def cql_unprotect_name(self, namestr):
if namestr is not None:
return cql3handling.cql3_dequote_name(namestr)
if namestr is None:
return
return cqlruleset.dequote_name(namestr)
def cql_unprotect_value(self, valstr):
if valstr is not None:
return cqlhandling.cql_dequote(valstr)
return cqlruleset.dequote_value(valstr)
def print_recreate_keyspace(self, ksdef, out):
stratclass = trim_if_present(ksdef.strategy_class, 'org.apache.cassandra.locator.')
@ -1127,23 +1124,23 @@ class Shell(cmd.Cmd):
def print_recreate_columnfamily_from_cfdef(self, cfdef, out):
cfname = self.cql_protect_name(cfdef.name)
out.write("CREATE COLUMNFAMILY %s (\n" % cfname)
out.write("CREATE TABLE %s (\n" % cfname)
alias = self.cql_protect_name(cfdef.key_alias) if cfdef.key_alias else 'KEY'
keytype = cql_typename(cfdef.key_validation_class)
keytype = cqlruleset.cql_typename(cfdef.key_validation_class)
out.write(" %s %s PRIMARY KEY" % (alias, keytype))
indexed_columns = []
for col in cfdef.column_metadata:
colname = self.cql_protect_name(col.name)
out.write(",\n %s %s" % (colname, cql_typename(col.validation_class)))
out.write(",\n %s %s" % (colname, cqlruleset.cql_typename(col.validation_class)))
if col.index_name is not None:
indexed_columns.append(col)
cf_opts = []
for (option, thriftname) in cqlhandling.columnfamily_options:
for (option, thriftname) in cqlruleset.columnfamily_options:
optval = getattr(cfdef, thriftname or option, None)
if optval is None:
continue
if option in ('comparator', 'default_validation'):
optval = cql_typename(optval)
optval = cqlruleset.cql_typename(optval)
else:
if option == 'row_cache_provider':
optval = trim_if_present(optval, 'org.apache.cassandra.cache.')
@ -1151,7 +1148,7 @@ class Shell(cmd.Cmd):
optval = trim_if_present(optval, 'org.apache.cassandra.db.compaction.')
optval = self.cql_protect_value(optval)
cf_opts.append((option, optval))
for option, thriftname, _ in cqlhandling.columnfamily_map_options:
for option, thriftname, _ in cqlruleset.columnfamily_map_options:
optmap = getattr(cfdef, thriftname or option, {})
for k, v in optmap.items():
if option == 'compression_parameters' and k == 'sstable_compression':
@ -1173,7 +1170,7 @@ class Shell(cmd.Cmd):
def print_recreate_columnfamily_from_layout(self, layout, out):
cfname = self.cql_protect_name(layout.name)
out.write("CREATE COLUMNFAMILY %s (\n" % cfname)
out.write("CREATE TABLE %s (\n" % cfname)
keycol = layout.columns[0]
out.write(" %s %s" % (self.cql_protect_name(keycol.name), keycol.cqltype))
if len(layout.key_components) == 1:
@ -1196,7 +1193,7 @@ class Shell(cmd.Cmd):
joiner = 'AND'
cf_opts = []
for option in cql3handling.columnfamily_options:
for option in cqlruleset.columnfamily_layout_options:
optval = getattr(layout, option, None)
if optval is None:
continue
@ -1205,7 +1202,7 @@ class Shell(cmd.Cmd):
elif option == 'compaction_strategy_class':
optval = trim_if_present(optval, 'org.apache.cassandra.db.compaction.')
cf_opts.append((option, self.cql_protect_value(optval)))
for option, _ in cql3handling.columnfamily_map_options:
for option, _ in cqlruleset.columnfamily_layout_map_options:
optmap = getattr(layout, option, {})
for k, v in optmap.items():
if option == 'compression_parameters' and k == 'sstable_compression':
@ -1228,9 +1225,11 @@ class Shell(cmd.Cmd):
self.print_recreate_keyspace(self.get_keyspace(ksname), sys.stdout)
print
def describe_columnfamily(self, cfname):
def describe_columnfamily(self, ksname, cfname):
if ksname is None:
ksname = self.current_keyspace
print
self.print_recreate_columnfamily(self.current_keyspace, cfname, sys.stdout)
self.print_recreate_columnfamily(ksname, cfname, sys.stdout)
print
def describe_columnfamilies(self, ksname):
@ -1277,23 +1276,23 @@ class Shell(cmd.Cmd):
DESCRIBE KEYSPACE [<keyspacename>]
Output CQL commands that could be used to recreate the given
keyspace, and the columnfamilies in it. In some cases, as the CQL
interface matures, there will be some metadata about a keyspace that
is not representable with CQL. That metadata will not be shown.
keyspace, and the tables in it. In some cases, as the CQL interface
matures, there will be some metadata about a keyspace that is not
representable with CQL. That metadata will not be shown.
The '<keyspacename>' argument may be omitted when using a non-system
keyspace; in that case, the current keyspace will be described.
DESCRIBE COLUMNFAMILIES
DESCRIBE TABLES
Output the names of all column families in the current keyspace, or
in all keyspaces if there is no current keyspace.
Output the names of all tables in the current keyspace, or in all
keyspaces if there is no current keyspace.
DESCRIBE COLUMNFAMILY <columnfamilyname>
DESCRIBE TABLE <tablename>
Output CQL commands that could be used to recreate the given
columnfamily. In some cases, as above, there may be columnfamily
metadata which is not representable and which will not be shown.
Output CQL commands that could be used to recreate the given table.
In some cases, as above, there may be table metadata which is not
representable and which will not be shown.
DESCRIBE CLUSTER
@ -1318,10 +1317,11 @@ class Shell(cmd.Cmd):
self.printerr('Not in any keyspace.')
return
self.describe_keyspace(ksname)
elif what == 'columnfamily':
cfname = self.cql_unprotect_name(parsed.get_binding('cfname'))
self.describe_columnfamily(cfname)
elif what == 'columnfamilies':
elif what in ('columnfamily', 'table'):
ks = self.cql_unprotect_name(parsed.get_binding('ksname', None))
cf = self.cql_unprotect_name(parsed.get_binding('cfname'))
self.describe_columnfamily(ks, cf)
elif what in ('columnfamilies', 'tables'):
self.describe_columnfamilies(self.current_keyspace)
elif what == 'cluster':
self.describe_cluster()
@ -1369,62 +1369,62 @@ class Shell(cmd.Cmd):
Instruct cqlsh to consider certain column names or values to be of a
specified type, even if that type information is not specified in
the columnfamily's metadata. Data will be deserialized according to
the given type, and displayed appropriately when retrieved.
the table's metadata. Data will be deserialized according to the
given type, and displayed appropriately when retrieved.
Use thus:
ASSUME [<keyspace>.]<columnfamily> NAMES ARE <type>;
ASSUME [<keyspace>.]<tablename> NAMES ARE <type>;
Treat all column names in the given columnfamily as being of the
Treat all column names in the given table as being of the
given type.
ASSUME [<keyspace>.]<columnfamily> VALUES ARE <type>;
ASSUME [<keyspace>.]<tablename> VALUES ARE <type>;
Treat all column values in the given columnfamily as being of the
Treat all column values in the given table as being of the
given type, unless there is more information about the specific
column being deserialized. That is, a column-specific ASSUME will
take precedence here, as will column-specific metadata in the
columnfamily's definition.
table's definition.
ASSUME [<keyspace>.]<columnfamily>(<colname>) VALUES ARE <type>;
ASSUME [<keyspace>.]<tablename>(<colname>) VALUES ARE <type>;
Treat all values in the given column in the given columnfamily as
Treat all values in the given column in the given table as
being of the specified type. This overrides any other information
about the type of a value.
Assign multiple overrides at once for the same columnfamily by
Assign multiple overrides at once for the same table by
separating with commas:
ASSUME ks.cf NAMES ARE uuid, VALUES ARE int, (col) VALUES ARE ascii
ASSUME ks.table NAMES ARE uuid, VALUES ARE int, (col) VALUES ARE ascii
See HELP TYPES for information on the supported data storage types.
"""
ks = self.cql_unprotect_name(parsed.get_binding('ksname', None))
cf = self.cql_unprotect_name(parsed.get_binding('cfname'))
colname = self.cql_unprotect_name(parsed.get_binding('colname', None))
params = {}
for paramname in ('ks', 'cf', 'colname'):
val = parsed.get_binding(paramname, None)
params[paramname] = self.cql_unprotect_name(val)
for paramname in ('names', 'values', 'colvalues'):
val = parsed.get_binding(paramname, None)
params[paramname] = self.cql_unprotect_value(val)
if params['ks'] is None:
if ks is None:
if self.current_keyspace is None:
self.printerr('Error: not in any keyspace.')
return
params['ks'] = self.current_keyspace
ks = self.current_keyspace
for overridetype in ('names', 'values', 'colvalues'):
cqltype = params[overridetype]
if cqltype is None:
continue
try:
validator_class = cqlhandling.find_validator_class(cqltype)
validator_class = cqlruleset.find_validator_class(cqltype)
except KeyError:
self.printerr('Error: validator type %s not found.' % cqltype)
else:
self.add_assumption(params['ks'], params['cf'], params['colname'],
overridetype, validator_class)
self.add_assumption(ks, cf, colname, overridetype, validator_class)
def do_source(self, parsed):
"""
@ -1441,7 +1441,7 @@ class Shell(cmd.Cmd):
That is, the path to the file to be executed must be given inside a
string literal. The path is interpreted relative to the current working
directory. The tilde shorthand notation ("~/mydir") is supported for
directory. The tilde shorthand notation ('~/mydir') is supported for
referring to $HOME.
See also the --file option to cqlsh.
@ -1473,9 +1473,9 @@ class Shell(cmd.Cmd):
CAPTURE OFF;
CAPTURE;
That is, the path to the file to be executed must be given inside a
That is, the path to the file to be appended to must be given inside a
string literal. The path is interpreted relative to the current working
directory. The tilde shorthand notation ("~/mydir") is supported for
directory. The tilde shorthand notation ('~/mydir') is supported for
referring to $HOME.
Only query result output is captured. Errors and output from cqlsh-only
@ -1562,7 +1562,7 @@ class Shell(cmd.Cmd):
def help_types(self):
print "\n CQL types recognized by this version of cqlsh:\n"
for t in cqlhandling.cql_types:
for t in cqlruleset.cql_types:
print ' ' + t
print """
For information on the various recognizable input formats for these
@ -1696,20 +1696,22 @@ class Shell(cmd.Cmd):
value is the number of rows from the pre-aggregation resultset.
Currently, COUNT is the only function supported by CQL.
** [FIRST n] and [REVERSED] are no longer supported in CQL 3.
"""
def help_select_columnfamily(self):
def help_select_table(self):
print """
SELECT: Specifying Column Family
SELECT: Specifying Table
SELECT ... FROM [<keyspace>.]<columnFamily> ...
SELECT ... FROM [<keyspace>.]<tablename> ...
The FROM clause is used to specify the Cassandra column family
applicable to a SELECT query. The keyspace in which the column family
exists can optionally be specified along with the column family name,
separated by a dot (.). This will not change the current keyspace of
the session (see HELP USE).
The FROM clause is used to specify the CQL table applicable to a SELECT
query. The keyspace in which the table exists can optionally be
specified along with the table name, separated by a dot (.). This will
not change the current keyspace of the session (see HELP USE).
"""
help_select_columnfamily = help_select_table
def help_select_where(self):
print """
@ -1758,6 +1760,8 @@ class Shell(cmd.Cmd):
* ANY
* ONE (default)
* TWO
* THREE
* QUORUM
* ALL
* LOCAL_QUORUM
@ -1769,18 +1773,18 @@ class Shell(cmd.Cmd):
def help_insert(self):
print """
INSERT INTO <columnFamily>
( <keyname>, <colname1> [, <colname2> [, ...]] )
VALUES ( <keyval>, <colval1> [, <colval2> [, ...]] )
INSERT INTO [<keyspace>.]<tablename>
( <colname1>, <colname2> [, <colname3> [, ...]] )
VALUES ( <colval1>, <colval2> [, <colval3> [, ...]] )
[USING CONSISTENCY <consistencylevel>
[AND TIMESTAMP <timestamp>]
[AND TTL <timeToLive]];
An INSERT is used to write one or more columns to a record in a
Cassandra column family. No results are returned.
CQL table. No results are returned.
The first column name in the INSERT list must be the name of the
column family key. Also, there must be more than one column name
Values for all component columns in the table's primary key must
be given. Also, there must be at least one non-primary-key column
specified (Cassandra rows are not considered to exist with only
a key and no associated columns).
@ -1796,17 +1800,19 @@ class Shell(cmd.Cmd):
def help_update(self):
print """
UPDATE <columnFamily> [USING CONSISTENCY <consistencylevel>
UPDATE [<keyspace>.]<columnFamily>
[USING CONSISTENCY <consistencylevel>
[AND TIMESTAMP <timestamp>]
[AND TTL <timeToLive>]]
SET name1 = value1, name2 = value2 WHERE <KEY> = keyname;
SET name1 = value1, name2 = value2 WHERE <keycol> = keyval;
An UPDATE is used to write one or more columns to a record in a
Cassandra column family. No results are returned. Key can be given
using the KEY keyword or by an alias set per columnfamily.
An UPDATE is used to write one or more columns to a record in a table.
No results are returned. The record's primary key must be completely
and uniquely specified; that is, if the primary key includes multiple
columns, all must be explicitly given in the WHERE clause.
Statements begin with the UPDATE keyword followed by a Cassandra
column family name.
Statements begin with the UPDATE keyword followed by the name of the
table to be updated.
For more information, see one of the following:
@ -1832,10 +1838,11 @@ class Shell(cmd.Cmd):
UPDATE ... USING TTL 43200 AND CONSISTENCY LOCAL_QUORUM;
<timestamp> defines the optional timestamp for the new column value(s).
It must be an integer.
It must be an integer. Cassandra timestamps are generally specified
using milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC).
<timeToLive> defines the optional time to live (TTL) for the new column
value(s). It must be an integer.
<timeToLive> defines the optional time to live (TTL) in seconds for the
new column value(s). It must be an integer.
"""
def help_update_set(self):
@ -1870,17 +1877,18 @@ class Shell(cmd.Cmd):
UPDATE ... WHERE <keyname> = <keyval>;
UPDATE ... WHERE <keyname> IN (<keyval1>, <keyval2>, ...);
UPDATE ... WHERE <keycol1> = <keyval1> AND <keycol2> = <keyval2>;
Each update statement requires a precise set of keys to be specified
using a WHERE clause.
<keyname> can be the keyword KEY or the key alias for the column
family.
If the table's primary key consists of multiple columns, an explicit
value must be given for each for the UPDATE statement to make sense.
"""
def help_delete(self):
print """
DELETE [<col1> [, <col2>, ...] FROM <columnFamily>
DELETE [<col1> [, <col2>, ...] FROM [<keyspace>.]<tablename>
[USING CONSISTENCY <consistencylevel>
[AND TIMESTAMP <timestamp>]]
WHERE <keyname> = <keyvalue>;
@ -1911,33 +1919,39 @@ class Shell(cmd.Cmd):
DELETE ... CONSISTENCY LOCAL_QUORUM AND TIMESTAMP 1318452291034;
<timestamp> defines the optional timestamp for the new tombstone
record. It must be an integer.
record. It must be an integer. Cassandra timestamps are generally
specified using milliseconds since the Unix epoch (1970-01-01 00:00:00
UTC).
"""
def help_delete_columns(self):
print """
DELETE: specifying columns
DELETE col1, 'col2 name', 3 FROM ...
DELETE col1, col2, col3 FROM ...
Following the DELETE keyword is an optional comma-delimited list of
column name terms. When no column names are given, the remove applies
to the entire row(s) matched by the WHERE clause.
When column names do not parse as valid CQL identifiers, they can be
quoted in single quotes (CQL 2) or double quotes (CQL 3).
"""
def help_delete_where(self):
print """
DELETE: specifying rows
DELETE ... WHERE KEY = 'some_key_value';
DELETE ... WHERE keyalias IN (key1, key2);
DELETE ... WHERE keycol = 'some_key_value';
DELETE ... WHERE keycol1 = 'val1' AND keycol2 = 'val2';
DELETE ... WHERE keycol IN (key1, key2);
The WHERE clause is used to determine to which row(s) a DELETE
applies. The first form allows the specification of a single keyname
using the KEY keyword (or the key alias) and the = operator. The
second form allows a list of keyname terms to be specified using the
IN operator and a parenthesized list of comma-delimited keyname
terms.
applies. The first form allows the specification of a precise row
by specifying a particular primary key value (if the primary key has
multiple columns, values for each must be given). The second form
allows a list of key values to be specified using the IN operator
and a parenthesized list of comma-delimited key values.
"""
def help_create(self):
@ -1946,7 +1960,7 @@ class Shell(cmd.Cmd):
one of the following:
HELP CREATE_KEYSPACE;
HELP CREATE_COLUMNFAMILY;
HELP CREATE_TABLE;
HELP CREATE_INDEX;
"""
@ -1974,69 +1988,74 @@ class Shell(cmd.Cmd):
"strategy_options:replication_factor=3".
"""
def help_create_columnfamily(self):
def help_create_table(self):
print """
CREATE COLUMNFAMILY <cfname> ( <colname> <type> PRIMARY KEY [,
<colname> <type> [, ...]] )
CREATE TABLE <cfname> ( <colname> <type> PRIMARY KEY [,
<colname> <type> [, ...]] )
[WITH <optionname> = <val> [AND <optionname> = <val> [...]]];
CREATE COLUMNFAMILY statements create a new column family under the
current keyspace. Valid column family names are strings of alphanumeric
characters and underscores, which begin with a letter.
CREATE TABLE statements create a new CQL table under the current
keyspace. Valid table names are strings of alphanumeric characters and
underscores, which begin with a letter.
Each columnfamily requires at least one column definition and type,
which will correspond to the columnfamily key and key validator. It's
important to note that the key type you use must be compatible with the
partitioner in use. For example, OrderPreservingPartitioner and
CollatingOrderPreservingPartitioner both require UTF-8 keys. If you
use an identifier for the primary key name, instead of the KEY
keyword, a key alias will be set automatically.
Each table requires a primary key, which will correspond to the
underlying columnfamily key and key validator. It's important to
note that the key type you use must be compatible with the partitioner
in use. For example, OrderPreservingPartitioner and
CollatingOrderPreservingPartitioner both require UTF-8 keys.
In cql3 mode, a table can have multiple columns composing the primary
key (see HELP COMPOSITE_PRIMARY_KEYS).
For more information, see one of the following:
HELP CREATE_COLUMNFAMILY_TYPES;
HELP CREATE_COLUMNFAMILY_OPTIONS;
HELP CREATE_TABLE_TYPES;
HELP CREATE_TABLE_OPTIONS;
"""
help_create_columnfamily = help_create_table
def help_create_columnfamily_types(self):
def help_create_table_types(self):
print """
CREATE COLUMNFAMILY: Specifying column types
CREATE TABLE: Specifying column types
CREATE ... (KEY <type> PRIMARY KEY,
othercol <type>) ...
It is possible to assign columns a type during column family creation.
Columns configured with a type are validated accordingly when a write
occurs, and intelligent CQL drivers and interfaces will be able to
decode the column values correctly when receiving them. Column types
are specified as a parenthesized, comma-separated list of column term
and type pairs. See HELP TYPES; for the list of recognized types.
It is possible to assign columns a type during table creation. Columns
configured with a type are validated accordingly when a write occurs,
and intelligent CQL drivers and interfaces will be able to decode the
column values correctly when receiving them. Column types are specified
as a parenthesized, comma-separated list of column term and type pairs.
See HELP TYPES; for the list of recognized types.
"""
help_create_columnfamily_types = help_create_table_types
def help_create_columnfamily_options(self):
def help_create_table_options(self):
print """
CREATE COLUMNFAMILY: Specifying columnfamily options
CREATE TABLE: Specifying columnfamily options
CREATE COLUMNFAMILY blah (...)
CREATE TABLE blah (...)
WITH optionname = val AND otheroption = val2;
A number of optional keyword arguments can be supplied to control the
configuration of a new column family, such as the size of the
associated row and key caches. Consult your CQL reference for the
complete list of options and possible values.
configuration of a new CQL table, such as the size of the associated
row and key caches for the underlying Cassandra columnfamily. Consult
your CQL reference for the complete list of options and possible
values.
"""
help_create_columnfamily_options = help_create_table_options
def help_create_index(self):
print """
CREATE INDEX [<indexname>] ON <cfname> ( <colname> );
A CREATE INDEX statement is used to create a new, automatic secondary
index on the given column family, for the named column. A name for the
index on the given CQL table, for the named column. A name for the
index itself can be specified before the ON keyword, if desired. A
single column name must be specified inside the parentheses. It is not
necessary for the column to exist on any current rows (Cassandra is
schemaless), but the column must already have a type (specified during
the CREATE COLUMNFAMILY, or added afterwards with ALTER COLUMNFAMILY.
schema-optional), but the column must already have a type (specified
during the CREATE TABLE, or added afterwards with ALTER TABLE).
"""
def help_drop(self):
@ -2045,7 +2064,7 @@ class Shell(cmd.Cmd):
one of the following:
HELP DROP_KEYSPACE;
HELP DROP_COLUMNFAMILY;
HELP DROP_TABLE;
HELP DROP_INDEX;
"""
@ -2058,13 +2077,15 @@ class Shell(cmd.Cmd):
data contained in those column families.
"""
def help_drop_columnfamily(self):
def help_drop_table(self):
print """
DROP COLUMNFAMILY <columnfamilyname>;
DROP TABLE <tablename>;
A DROP COLUMNFAMILY statement results in the immediate, irreversible
removal of a column family, including all data contained in it.
A DROP TABLE statement results in the immediate, irreversible
removal of a CQL table and the underlying column family, including all
data contained in it.
"""
help_drop_columnfamily = help_drop_table
def help_drop_index(self):
print """
@ -2075,10 +2096,10 @@ class Shell(cmd.Cmd):
def help_truncate(self):
print """
TRUNCATE <columnfamilyname>;
TRUNCATE <tablename>;
TRUNCATE accepts a single argument for the column family name, and
permanently removes all data from said column family.
TRUNCATE accepts a single argument for the table name, and permanently
removes all data from it.
"""
def help_begin(self):
@ -2109,15 +2130,15 @@ class Shell(cmd.Cmd):
def help_alter(self):
print """
ALTER COLUMNFAMILY <cfname> ALTER <columnname> TYPE <type>;
ALTER COLUMNFAMILY <cfname> ADD <columnname> <type>;
ALTER COLUMNFAMILY <cfname> DROP <columnname>;
ALTER COLUMNFAMILY <cfname> WITH <optionname> = <val> [AND <optionname> = <val> [...]];
ALTER TABLE <tablename> ALTER <columnname> TYPE <type>;
ALTER TABLE <tablename> ADD <columnname> <type>;
ALTER TABLE <tablename> DROP <columnname>;
ALTER TABLE <tablename> WITH <optionname> = <val> [AND <optionname> = <val> [...]];
An ALTER statement is used to manipulate column family column
metadata. It allows you to add new columns, drop existing columns,
change the data storage type of existing columns, or change column
family properties. No results are returned.
An ALTER statement is used to manipulate table metadata. It allows you
to add new typed columns, drop existing columns, change the data
storage type of existing columns, or change table properties.
No results are returned.
See one of the following for more information:
@ -2129,26 +2150,26 @@ class Shell(cmd.Cmd):
def help_alter_alter(self):
print """
ALTER COLUMNFAMILY: altering existing typed columns
ALTER TABLE: altering existing typed columns
ALTER COLUMNFAMILY addamsFamily ALTER lastKnownLocation TYPE uuid;
ALTER TABLE addamsFamily ALTER lastKnownLocation TYPE uuid;
ALTER COLUMNFAMILY ... ALTER changes the expected storage type for a
column. The column must either be the key alias or already have a type
in the column family metadata. The column may or may not already exist
in current rows-- but be aware that no validation of existing data is
done. The bytes stored in values for that column will remain unchanged,
and if existing data is not deserializable according to the new type,
this may cause your CQL driver or interface to report errors.
ALTER TABLE ... ALTER changes the expected storage type for a column.
The column must already have a type in the column family metadata. The
column may or may not already exist in current rows-- but be aware that
no validation of existing data is done. The bytes stored in values for
that column will remain unchanged, and if existing data is not
deserializable according to the new type, this may cause your CQL
driver or interface to report errors.
"""
def help_alter_add(self):
print """
ALTER COLUMNFAMILY: adding a typed column
ALTER TABLE: adding a typed column
ALTER COLUMNFAMILY addamsFamily ADD gravesite varchar;
ALTER TABLE addamsFamily ADD gravesite varchar;
The ALTER COLUMNFAMILY ... ADD variant adds a typed column to a column
The ALTER TABLE ... ADD variant adds a typed column to a column
family. The column must not already have a type in the column family
metadata. See the warnings on HELP ALTER_ALTER regarding the lack of
validation of existing data; they apply here as well.
@ -2156,11 +2177,11 @@ class Shell(cmd.Cmd):
def help_alter_drop(self):
print """
ALTER COLUMNFAMILY: dropping a typed column
ALTER TABLE: dropping a typed column
ALTER COLUMNFAMILY addamsFamily DROP gender;
ALTER TABLE addamsFamily DROP gender;
An ALTER COLUMNFAMILY ... DROP statement removes the type of a column
An ALTER TABLE ... DROP statement removes the type of a column
from the column family metadata. Note that this does _not_ remove the
column from current rows; it just removes the metadata saying that the
bytes stored under that column are expected to be deserializable
@ -2169,15 +2190,15 @@ class Shell(cmd.Cmd):
def help_alter_with(self):
print """
ALTER COLUMNFAMILY: changing column family properties
ALTER TABLE: changing column family properties
ALTER COLUMNFAMILY addamsFamily WITH comment = 'Glad to be here!'
AND read_repair_chance = 0.2;
ALTER TABLE addamsFamily WITH comment = 'Glad to be here!'
AND read_repair_chance = 0.2;
An ALTER COLUMNFAMILY ... WITH statement makes adjustments to the
column family properties, as defined when the column family was created
(see HELP CREATE_COLUMNFAMILY_OPTIONS, and your Cassandra documentation
for information about the supported parameter names and values).
An ALTER TABLE ... WITH statement makes adjustments to the
table properties, as defined when the table was created (see
HELP CREATE_TABLE_OPTIONS and your Cassandra documentation for
information about the supported parameter names and values).
"""
def applycolor(self, text, color=None):
@ -2297,6 +2318,12 @@ def read_options(cmdlineargs, environment):
options.color = False
options.tty = False
options.cqlversion, cqlvertup = full_cql_version(options.cqlversion)
if cqlvertup[0] < 3:
options.cqlmodule = cqlhandling
else:
options.cqlmodule = cql3handling
try:
port = int(port)
except ValueError:
@ -2304,7 +2331,17 @@ def read_options(cmdlineargs, environment):
return options, hostname, port
def setup_cqlruleset(cqlmodule):
global cqlruleset
cqlruleset = cqlmodule.CqlRuleSet
cqlruleset.append_rules(cqlsh_extra_syntax_rules)
for rulename, termname, func in cqlsh_syntax_completers:
cqlruleset.completer_for(rulename, termname)(func)
cqlruleset.commands_end_with_newline.update(my_commands_ending_with_newline)
def main(options, hostname, port):
setup_cqlruleset(options.cqlmodule)
if os.path.exists(HISTORY) and readline is not None:
readline.read_history_file(HISTORY)
delims = readline.get_completer_delims()

View File

@ -16,7 +16,7 @@
import re
from warnings import warn
from .cqlhandling import cql_typename, cql_escape, cql_dequote
from .cqlhandling import CqlParsingRuleSet, Hint
try:
import json
@ -30,62 +30,638 @@ class UnexpectedTableStructure(UserWarning):
def __str__(self):
return 'Unexpected table structure; may not translate correctly to CQL. ' + self.msg
keywords = set((
'select', 'from', 'where', 'and', 'key', 'insert', 'update', 'with',
'limit', 'using', 'consistency', 'one', 'quorum', 'all', 'any',
'local_quorum', 'each_quorum', 'two', 'three', 'use', 'count', 'set',
'begin', 'apply', 'batch', 'truncate', 'delete', 'in', 'create',
'keyspace', 'schema', 'columnfamily', 'table', 'index', 'on', 'drop',
'primary', 'into', 'values', 'timestamp', 'ttl', 'alter', 'add', 'type',
'compact', 'storage', 'order', 'by', 'asc', 'desc'
))
class Cql3ParsingRuleSet(CqlParsingRuleSet):
keywords = set((
'select', 'from', 'where', 'and', 'key', 'insert', 'update', 'with',
'limit', 'using', 'consistency', 'one', 'quorum', 'all', 'any',
'local_quorum', 'each_quorum', 'two', 'three', 'use', 'count', 'set',
'begin', 'apply', 'batch', 'truncate', 'delete', 'in', 'create',
'keyspace', 'schema', 'columnfamily', 'table', 'index', 'on', 'drop',
'primary', 'into', 'values', 'timestamp', 'ttl', 'alter', 'add', 'type',
'compact', 'storage', 'order', 'by', 'asc', 'desc', 'clustering', 'token'
))
columnfamily_options = (
'comment',
'bloom_filter_fp_chance',
'caching',
'read_repair_chance',
# 'local_read_repair_chance', -- not yet a valid cql option
'gc_grace_seconds',
'min_compaction_threshold',
'max_compaction_threshold',
'replicate_on_write',
'compaction_strategy_class',
)
columnfamily_layout_options = (
'comment',
'bloom_filter_fp_chance',
'caching',
'read_repair_chance',
# 'local_read_repair_chance', -- not yet a valid cql option
'gc_grace_seconds',
'min_compaction_threshold',
'max_compaction_threshold',
'replicate_on_write',
'compaction_strategy_class',
)
columnfamily_map_options = (
('compaction_strategy_options',
()),
('compression_parameters',
('sstable_compression', 'chunk_length_kb', 'crc_check_chance')),
)
columnfamily_layout_map_options = (
('compaction_strategy_options',
()),
('compression_parameters',
('sstable_compression', 'chunk_length_kb', 'crc_check_chance')),
)
def cql3_escape_value(value):
return cql_escape(value)
@staticmethod
def token_dequote(tok):
if tok[0] == 'unclosedName':
# strip one quote
return tok[1][1:].replace('""', '"')
# cql2 version knows how to do everything else
return CqlParsingRuleSet.token_dequote(tok)
def cql3_escape_name(name):
return '"%s"' % name.replace('"', '""')
@classmethod
def cql3_dequote_value(cls, value):
return cls.cql2_dequote_value(value)
def cql3_dequote_value(value):
return cql_dequote(value)
def cql3_dequote_name(name):
name = name.strip()
if name == '':
@staticmethod
def cql3_dequote_name(name):
name = name.strip()
if name == '':
return name
if name[0] == '"':
name = name[1:-1].replace('""', '"')
return name
if name[0] == '"':
name = name[1:-1].replace('""', '"')
return name
valid_cql3_word_re = re.compile(r'^[a-z][0-9a-z_]*$', re.I)
@classmethod
def cql3_escape_value(cls, value):
return cls.cql2_escape_value(value)
@staticmethod
def cql3_escape_name(name):
return '"%s"' % name.replace('"', '""')
valid_cql3_word_re = re.compile(r'^[a-z][0-9a-z_]*$', re.I)
@classmethod
def is_valid_cql3_name(cls, s):
if s is None or s.lower() in cls.keywords:
return False
return cls.valid_cql3_word_re.match(s) is not None
@classmethod
def cql3_maybe_escape_name(cls, name):
if cls.is_valid_cql3_name(name):
return name
return cls.cql3_escape_name(name)
@classmethod
def dequote_any(cls, t):
if t[0] == '"':
return cls.cql3_dequote_name(t)
return CqlParsingRuleSet.dequote_any(t)
dequote_value = cql3_dequote_value
dequote_name = cql3_dequote_name
escape_value = cql3_escape_value
escape_name = cql3_escape_name
maybe_escape_name = cql3_maybe_escape_name
CqlRuleSet = Cql3ParsingRuleSet()
# convenience for remainder of module
shorthands = ('completer_for', 'explain_completion',
'dequote_value', 'dequote_name',
'escape_value', 'escape_name',
'maybe_escape_name', 'cql_typename')
for shorthand in shorthands:
globals()[shorthand] = getattr(CqlRuleSet, shorthand)
# BEGIN SYNTAX/COMPLETION RULE DEFINITIONS
syntax_rules = r'''
<Start> ::= <CQL_Statement>*
;
<CQL_Statement> ::= [statements]=<statementBody> ";"
;
# the order of these terminal productions is significant:
<endline> ::= /\n/ ;
JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ;
<stringLiteral> ::= /'([^']|'')*'/ ;
<quotedName> ::= /"([^"]|"")*"/ ;
<float> ::= /-?[0-9]+\.[0-9]+/ ;
<wholenumber> ::= /[0-9]+/ ;
<integer> ::= /-?[0-9]+/ ;
<uuid> ::= /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/ ;
<identifier> ::= /[a-z][a-z0-9_]*/ ;
<colon> ::= ":" ;
<star> ::= "*" ;
<endtoken> ::= ";" ;
<op> ::= /[-+=,().]/ ;
<cmp> ::= /[<>]=?/ ;
<unclosedString> ::= /'([^']|'')*/ ;
<unclosedName> ::= /"([^"]|"")*/ ;
<unclosedComment> ::= /[/][*][^\n]*$/ ;
<term> ::= <stringLiteral>
| <integer>
| <float>
| <uuid>
;
<extendedTerm> ::= token="TOKEN" "(" <term> ")"
| <term>
;
<cident> ::= <quotedName>
| <identifier>
| <unreservedKeyword>
;
<colname> ::= <cident> ; # just an alias
<statementBody> ::= <useStatement>
| <selectStatement>
| <dataChangeStatement>
| <schemaChangeStatement>
;
<dataChangeStatement> ::= <insertStatement>
| <updateStatement>
| <deleteStatement>
| <truncateStatement>
| <batchStatement>
;
<schemaChangeStatement> ::= <createKeyspaceStatement>
| <createColumnFamilyStatement>
| <createIndexStatement>
| <dropKeyspaceStatement>
| <dropColumnFamilyStatement>
| <dropIndexStatement>
| <alterTableStatement>
;
<consistencylevel> ::= <K_ONE>
| <K_QUORUM>
| <K_ALL>
| <K_ANY>
| <K_LOCAL_QUORUM>
| <K_EACH_QUORUM>
| <K_TWO>
| <K_THREE>
;
<storageType> ::= typename=( <identifier> | <stringLiteral> ) ;
<columnFamilyName> ::= ( ksname=<cfOrKsName> "." )? cfname=<cfOrKsName> ;
<keyspaceName> ::= ksname=<cfOrKsName> ;
<cfOrKsName> ::= <identifier>
| <quotedName>
| <unreservedKeyword>;
<unreservedKeyword> ::= nocomplete=
( <K_KEY>
| <K_CONSISTENCY>
| <K_CLUSTERING>
# | <K_COUNT> -- to get count(*) completion, treat count as reserved
| <K_TTL>
| <K_COMPACT>
| <K_STORAGE>
| <K_TYPE>
| <K_VALUES>
| <consistencylevel> )
;
'''
@completer_for('extendedTerm', 'token')
def token_word_completer(ctxt, cass):
return ['TOKEN(']
@completer_for('storageType', 'typename')
def storagetype_completer(ctxt, cass):
return CqlRuleSet.cql_types
@completer_for('keyspaceName', 'ksname')
def ks_name_completer(ctxt, cass):
return map(maybe_escape_name, cass.get_keyspace_names())
@completer_for('columnFamilyName', 'ksname')
def cf_ks_name_completer(ctxt, cass):
return [maybe_escape_name(ks) + '.' for ks in cass.get_keyspace_names()]
@completer_for('columnFamilyName', 'cfname')
def cf_name_completer(ctxt, cass):
ks = ctxt.get_binding('ksname', None)
if ks is not None:
ks = dequote_name(ks)
try:
cfnames = cass.get_columnfamily_names(ks)
except Exception:
if ks is None:
return ()
raise
return map(maybe_escape_name, cfnames)
@completer_for('unreservedKeyword', 'nocomplete')
def unreserved_keyword_completer(ctxt, cass):
# we never want to provide completions through this production;
# this is always just to allow use of some keywords as column
# names, CF names, property values, etc.
return ()
def get_cf_layout(ctxt, cass):
ks = ctxt.get_binding('ksname', None)
cf = ctxt.get_binding('cfname')
return cass.get_columnfamily_layout(ks, cf)
syntax_rules += r'''
<useStatement> ::= "USE" <keyspaceName>
;
<selectStatement> ::= "SELECT" <selectClause>
"FROM" cf=<columnFamilyName>
("USING" "CONSISTENCY" <consistencylevel>)?
("WHERE" <whereClause>)?
("ORDER" "BY" <orderByClause> ( "," <orderByClause> )* )?
("LIMIT" <wholenumber>)?
;
<whereClause> ::= <relation> ("AND" <relation>)*
;
<relation> ::= [rel_lhs]=<cident> ("=" | "<" | ">" | "<=" | ">=") <term>
| token="TOKEN" "(" rel_tokname=<cident> ")" ("=" | "<" | ">" | "<=" | ">=") <extendedTerm>
| [rel_lhs]=<cident> "IN" "(" <term> ( "," <term> )* ")"
;
<selectClause> ::= colname=<cident> ("," colname=<cident>)*
| "*"
| "COUNT" "(" star=( "*" | "1" ) ")"
;
<orderByClause> ::= [ordercol]=<cident> ( "ASC" | "DESC" )?
;
'''
@completer_for('orderByClause', 'ordercol')
def select_order_column_completer(ctxt, cass):
prev_order_cols = ctxt.get_binding('ordercol', ())
keyname = ctxt.get_binding('keyname')
if keyname is None:
keyname = ctxt.get_binding('rel_lhs', ())
if not keyname:
return [Hint("Can't ORDER BY here: need to specify partition key in WHERE clause")]
layout = get_cf_layout(ctxt, cass)
order_by_candidates = layout.key_components[1:] # can't order by first part of key
if len(order_by_candidates) > len(prev_order_cols):
return [maybe_escape_name(order_by_candidates[len(prev_order_cols)])]
return [Hint('No more orderable columns here.')]
@completer_for('relation', 'token')
def relation_token_word_completer(ctxt, cass):
return ['TOKEN(']
@completer_for('relation', 'rel_tokname')
def relation_token_subject_completer(ctxt, cass):
layout = get_cf_layout(ctxt, cass)
return [layout.key_components[0]]
@completer_for('relation', 'rel_lhs')
def select_relation_lhs_completer(ctxt, cass):
layout = get_cf_layout(ctxt, cass)
filterable = set(layout.key_components[:2])
already_filtered_on = ctxt.get_binding('rel_lhs')
for num in range(1, len(layout.key_components)):
if layout.key_components[num - 1] in already_filtered_on:
filterable.add(layout.key_components[num])
else:
break
for cd in layout.columns:
if cd.index_name is not None:
filterable.add(cd.name)
return map(maybe_escape_name, filterable)
@completer_for('selectClause', 'star')
def select_count_star_completer(ctxt, cass):
return ['*']
explain_completion('selectClause', 'colname')
syntax_rules += r'''
<insertStatement> ::= "INSERT" "INTO" cf=<columnFamilyName>
"(" keyname=<cident> ","
[colname]=<cident> ( "," [colname]=<cident> )* ")"
"VALUES" "(" <term> "," <term> ( "," <term> )* ")"
( "USING" [insertopt]=<usingOption>
( "AND" [insertopt]=<usingOption> )* )?
;
<usingOption> ::= "CONSISTENCY" <consistencylevel>
| "TIMESTAMP" <wholenumber>
| "TTL" <wholenumber>
;
'''
@completer_for('insertStatement', 'keyname')
def insert_keyname_completer(ctxt, cass):
layout = get_cf_layout(ctxt, cass)
return [layout.key_components[0]]
explain_completion('insertStatement', 'colname')
@completer_for('insertStatement', 'insertopt')
def insert_option_completer(ctxt, cass):
opts = set('CONSISTENCY TIMESTAMP TTL'.split())
for opt in ctxt.get_binding('insertopt', ()):
opts.discard(opt.split()[0])
return opts
syntax_rules += r'''
<updateStatement> ::= "UPDATE" cf=<columnFamilyName>
( "USING" [updateopt]=<usingOption>
( "AND" [updateopt]=<usingOption> )* )?
"SET" <assignment> ( "," <assignment> )*
"WHERE" <whereClause>
;
<assignment> ::= updatecol=<cident> "=" update_rhs=<cident>
( counterop=( "+" | "-" ) <wholenumber> )?
;
'''
@completer_for('updateStatement', 'updateopt')
def insert_option_completer(ctxt, cass):
opts = set('CONSISTENCY TIMESTAMP TTL'.split())
for opt in ctxt.get_binding('updateopt', ()):
opts.discard(opt.split()[0])
return opts
@completer_for('assignment', 'updatecol')
def update_col_completer(ctxt, cass):
layout = get_cf_layout(ctxt, cass)
return map(maybe_escape_name, [cm.name for cm in layout.columns])
@completer_for('assignment', 'update_rhs')
def update_countername_completer(ctxt, cass):
layout = get_cf_layout(ctxt, cass)
curcol = dequote_name(ctxt.get_binding('updatecol', ''))
return [maybe_escape_name(curcol)] if layout.is_counter_col(curcol) else [Hint('<term>')]
@completer_for('assignment', 'counterop')
def update_counterop_completer(ctxt, cass):
layout = get_cf_layout(ctxt, cass)
curcol = dequote_name(ctxt.get_binding('updatecol', ''))
return ['+', '-'] if layout.is_counter_col(curcol) else []
syntax_rules += r'''
<deleteStatement> ::= "DELETE" ( [delcol]=<cident> ( "," [delcol]=<cident> )* )?
"FROM" cf=<columnFamilyName>
( "USING" [delopt]=<deleteOption> ( "AND" [delopt]=<deleteOption> )* )?
"WHERE" <whereClause>
;
<deleteOption> ::= "CONSISTENCY" <consistencylevel>
| "TIMESTAMP" <wholenumber>
;
'''
@completer_for('deleteStatement', 'delopt')
def delete_opt_completer(ctxt, cass):
opts = set('CONSISTENCY TIMESTAMP'.split())
for opt in ctxt.get_binding('delopt', ()):
opts.discard(opt.split()[0])
return opts
explain_completion('deleteStatement', 'delcol', '<column_to_delete>')
syntax_rules += r'''
<batchStatement> ::= "BEGIN" "BATCH"
( "USING" [batchopt]=<usingOption>
( "AND" [batchopt]=<usingOption> )* )?
[batchstmt]=<batchStatementMember> ";"
( [batchstmt]=<batchStatementMember> ";" )*
"APPLY" "BATCH"
;
<batchStatementMember> ::= <insertStatement>
| <updateStatement>
| <deleteStatement>
;
'''
@completer_for('batchStatement', 'batchopt')
def batch_opt_completer(ctxt, cass):
opts = set('CONSISTENCY TIMESTAMP'.split())
for opt in ctxt.get_binding('batchopt', ()):
opts.discard(opt.split()[0])
return opts
syntax_rules += r'''
<truncateStatement> ::= "TRUNCATE" cf=<columnFamilyName>
;
'''
syntax_rules += r'''
<createKeyspaceStatement> ::= "CREATE" "KEYSPACE" ksname=<cfOrKsName>
"WITH" [optname]=<optionName> "=" [optval]=<optionVal>
( "AND" [optname]=<optionName> "=" [optval]=<optionVal> )*
;
<optionName> ::= <identifier> ( ":" ( <identifier> | <wholenumber> ) )?
;
<optionVal> ::= <stringLiteral>
| <identifier>
| <integer>
;
'''
explain_completion('createKeyspaceStatement', 'ksname', '<new_keyspace_name>')
@completer_for('createKeyspaceStatement', 'optname')
def create_ks_opt_completer(ctxt, cass):
exist_opts = ctxt.get_binding('optname', ())
try:
stratopt = exist_opts.index('strategy_class')
except ValueError:
return ['strategy_class =']
vals = ctxt.get_binding('optval')
stratclass = dequote_value(vals[stratopt])
if stratclass in ('SimpleStrategy', 'OldNetworkTopologyStrategy'):
return ['strategy_options:replication_factor =']
return [Hint('<strategy_option_name>')]
@completer_for('createKeyspaceStatement', 'optval')
def create_ks_optval_completer(ctxt, cass):
exist_opts = ctxt.get_binding('optname', (None,))
if exist_opts[-1] == 'strategy_class':
return map(escape_value, CqlRuleSet.replication_strategies)
return [Hint('<option_value>')]
syntax_rules += r'''
<createColumnFamilyStatement> ::= "CREATE" ( "COLUMNFAMILY" | "TABLE" )
( ks=<keyspaceName> "." )? cf=<cfOrKsName>
"(" ( <singleKeyCfSpec> | <compositeKeyCfSpec> ) ")"
( "WITH" [cfopt]=<cfOptionName> "=" [optval]=<cfOptionVal>
( "AND" [cfopt]=<cfOptionName> "=" [optval]=<cfOptionVal> )* )?
;
<singleKeyCfSpec> ::= keyalias=<cident> <storageType> "PRIMARY" "KEY"
( "," colname=<cident> <storageType> )*
;
<compositeKeyCfSpec> ::= [newcolname]=<cident> <storageType>
"," [newcolname]=<cident> <storageType>
( "," [newcolname]=<cident> <storageType> )*
"," "PRIMARY" k="KEY" p="(" [pkey]=<cident>
( c="," [pkey]=<cident> )* ")"
;
<cfOptionName> ::= cfoptname=<identifier> ( cfoptsep=":" cfsubopt=( <identifier> | <wholenumber> ) )?
;
<cfOptionVal> ::= <identifier>
| <stringLiteral>
| <integer>
| <float>
;
'''
explain_completion('createColumnFamilyStatement', 'cf', '<new_table_name>')
explain_completion('singleKeyCfSpec', 'keyalias', '<new_key_name>')
explain_completion('singleKeyCfSpec', 'colname', '<new_column_name>')
explain_completion('compositeKeyCfSpec', 'newcolname', '<new_column_name>')
@completer_for('compositeKeyCfSpec', 'pkey')
def create_cf_composite_key_declaration(ctxt, cass):
cols_declared = ctxt.get_binding('newcolname')
pieces_already = ctxt.get_binding('pkey', ())
while cols_declared[0] in pieces_already:
cols_declared = cols_declared[1:]
if len(cols_declared) < 2:
return ()
return [maybe_escape_name(cols_declared[0])]
@completer_for('compositeKeyCfSpec', 'k')
def create_cf_composite_primary_key_keyword_completer(ctxt, cass):
return ['KEY (']
@completer_for('compositeKeyCfSpec', 'p')
def create_cf_composite_primary_key_paren_completer(ctxt, cass):
return ['(']
@completer_for('compositeKeyCfSpec', 'c')
def create_cf_composite_primary_key_comma_completer(ctxt, cass):
cols_declared = ctxt.get_binding('newcolname')
pieces_already = ctxt.get_binding('pkey', ())
if len(pieces_already) >= len(cols_declared) - 1:
return ()
return [',']
@completer_for('cfOptionName', 'cfoptname')
def create_cf_option_completer(ctxt, cass):
return list(CqlRuleSet.columnfamily_layout_options) + \
[c[0] + ':' for c in CqlRuleSet.columnfamily_map_options]
@completer_for('cfOptionName', 'cfoptsep')
def create_cf_suboption_separator(ctxt, cass):
opt = ctxt.get_binding('cfoptname')
if any(opt == c[0] for c in CqlRuleSet.columnfamily_map_options):
return [':']
return ()
@completer_for('cfOptionName', 'cfsubopt')
def create_cf_suboption_completer(ctxt, cass):
opt = ctxt.get_binding('cfoptname')
if opt == 'compaction_strategy_options':
# try to determine the strategy class in use
prevopts = ctxt.get_binding('cfopt', ())
prevvals = ctxt.get_binding('optval', ())
for prevopt, prevval in zip(prevopts, prevvals):
if prevopt == 'compaction_strategy_class':
csc = dequote_value(prevval)
break
else:
layout = get_cf_layout(ctxt, cass)
try:
csc = layout.compaction_strategy
except Exception:
csc = ''
csc = csc.split('.')[-1]
if csc == 'SizeTieredCompactionStrategy':
return ['min_sstable_size']
elif csc == 'LeveledCompactionStrategy':
return ['sstable_size_in_mb']
for optname, _, subopts in CqlRuleSet.columnfamily_map_options:
if opt == optname:
return subopts
return ()
def create_cf_option_val_completer(ctxt, cass):
exist_opts = ctxt.get_binding('cfopt')
this_opt = exist_opts[-1]
if this_opt == 'compression_parameters:sstable_compression':
return map(escape_value, CqlRuleSet.available_compression_classes)
if this_opt == 'compaction_strategy_class':
return map(escape_value, CqlRuleSet.available_compaction_classes)
if any(this_opt == opt[0] for opt in CqlRuleSet.obsolete_cf_options):
return ["'<obsolete_option>'"]
if this_opt in ('comparator', 'default_validation'):
return CqlRuleSet.cql_types
if this_opt in ('read_repair_chance', 'bloom_filter_fp_chance'):
return [Hint('<float_between_0_and_1>')]
if this_opt == 'replicate_on_write':
return [Hint('<yes_or_no>')]
if this_opt in ('min_compaction_threshold', 'max_compaction_threshold', 'gc_grace_seconds'):
return [Hint('<integer>')]
return [Hint('<option_value>')]
completer_for('createColumnFamilyStatement', 'optval') \
(create_cf_option_val_completer)
syntax_rules += r'''
<createIndexStatement> ::= "CREATE" "INDEX" indexname=<identifier>? "ON"
cf=<columnFamilyName> "(" col=<cident> ")"
;
'''
explain_completion('createIndexStatement', 'indexname', '<new_index_name>')
@completer_for('createIndexStatement', 'col')
def create_index_col_completer(ctxt, cass):
layout = get_cf_layout(ctxt, cass)
colnames = [cd.name for cd in layout.columns if cd.index_name is None]
return map(maybe_escape_name, colnames)
syntax_rules += r'''
<dropKeyspaceStatement> ::= "DROP" "KEYSPACE" ksname=<keyspaceName>
;
<dropColumnFamilyStatement> ::= "DROP" ( "COLUMNFAMILY" | "TABLE" ) cf=<columnFamilyName>
;
<dropIndexStatement> ::= "DROP" "INDEX" indexname=<identifier>
;
'''
@completer_for('dropIndexStatement', 'indexname')
def drop_index_completer(ctxt, cass):
return map(maybe_escape_name, cass.get_index_names())
syntax_rules += r'''
<alterTableStatement> ::= "ALTER" ( "COLUMNFAMILY" | "TABLE" ) cf=<columnFamilyName>
<alterInstructions>
;
<alterInstructions> ::= "ALTER" existcol=<cident> "TYPE" <storageType>
| "ADD" newcol=<cident> <storageType>
| "DROP" existcol=<cident>
| "WITH" [cfopt]=<cfOptionName> "=" [optval]=<cfOptionVal>
( "AND" [cfopt]=<cfOptionName> "=" [optval]=<cfOptionVal> )*
;
'''
@completer_for('alterInstructions', 'existcol')
def alter_table_col_completer(ctxt, cass):
layout = get_cf_layout(ctxt, cass)
cols = [md.name for md in layout.columns]
return map(maybe_escape_name, cols)
explain_completion('alterInstructions', 'newcol', '<new_column_name>')
completer_for('alterInstructions', 'optval') \
(create_cf_option_val_completer)
# END SYNTAX/COMPLETION RULE DEFINITIONS
CqlRuleSet.append_rules(syntax_rules)
def is_valid_cql3_name(s):
return valid_cql3_word_re.match(s) is not None and s not in keywords
def maybe_cql3_escape_name(name):
if is_valid_cql3_name(name):
return name
return cql3_escape_name(name)
class CqlColumnDef:
index_name = None
@ -93,6 +669,7 @@ class CqlColumnDef:
def __init__(self, name, cqltype):
self.name = name
self.cqltype = cqltype
assert name is not None
@classmethod
def from_layout(cls, layout):
@ -128,6 +705,8 @@ class CqlTableDef:
setattr(cf, attr, json.loads(getattr(cf, attr)))
except AttributeError:
pass
if cf.key_alias is None:
cf.key_alias = 'KEY'
cf.key_components = [cf.key_alias.decode('ascii')] + list(cf.column_aliases)
cf.key_validator = cql_typename(cf.key_validator)
cf.default_validator = cql_typename(cf.default_validator)
@ -212,10 +791,15 @@ class CqlTableDef:
self.compact_storage = True
value_cols = [self.column_class(self.value_alias, self.default_validator)]
subtypes = subtypes[:len(self.key_components)]
keycols = map(self.column_class, self.key_components, subtypes)
normal_cols = map(self.column_class.from_layout, self.coldefs)
self.columns = keycols + value_cols + normal_cols
def is_counter_col(self, colname):
col_info = [cm for cm in self.columns if cm.name == colname]
return bool(col_info and col_info[0].cqltype == 'counter')
def __str__(self):
return '<%s %s.%s>' % (self.__class__.__name__, self.keyspace, self.name)
__repr__ = __str__

File diff suppressed because it is too large Load Diff

View File

@ -269,6 +269,28 @@ class case_match(text_match):
def pattern(self):
return re.escape(self.arg)
class word_match(text_match):
def pattern(self):
return r'\b' + text_match.pattern(self) + r'\b'
class case_word_match(case_match):
def pattern(self):
return r'\b' + case_match.pattern(self) + r'\b'
class terminal_type_matcher(matcher):
def __init__(self, tokentype, submatcher):
matcher.__init__(self, tokentype)
self.tokentype = tokentype
self.submatcher = submatcher
def match(self, ctxt, completions):
if ctxt.remainder:
if ctxt.remainder[0][0] == self.tokentype:
return [ctxt.with_match(1)]
elif completions is not None:
self.submatcher.match(ctxt, completions)
return []
class ParsingRuleSet:
RuleSpecScanner = SaferScanner([
(r'::=', lambda s,t: t),
@ -309,9 +331,10 @@ class ParsingRuleSet:
raise ValueError('Unexpected token %r; expected "::="' % (assign,))
name = t[1]
production = cls.read_rule_tokens_until(';', tokeniter)
rules[name] = production
if isinstance(production, terminal_matcher):
terminals.append((name, production))
production = terminal_type_matcher(name, production)
rules[name] = production
else:
raise ValueError('Unexpected token %r; expected name' % (t,))
return rules, terminals
@ -345,7 +368,10 @@ class ParsingRuleSet:
if t[0] == 'reference':
t = rule_reference(t[1])
elif t[0] == 'litstring':
t = text_match(t[1])
if t[1][1].isalnum() or t[1][1] == '_':
t = word_match(t[1])
else:
t = text_match(t[1])
elif t[0] == 'regex':
t = regex_rule(t[1])
elif t[0] == 'named_collector':

74
pylib/cqlshlib/util.py Normal file
View File

@ -0,0 +1,74 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from itertools import izip
def split_list(items, pred):
"""
Split up a list (or other iterable) on the elements which satisfy the
given predicate 'pred'. Elements for which 'pred' returns true start a new
sublist for subsequent elements, which will accumulate in the new sublist
until the next satisfying element.
>>> split_list([0, 1, 2, 5, 99, 8], lambda n: (n % 2) == 0)
[[0], [1, 2], [5, 99, 8], []]
"""
thisresult = []
results = [thisresult]
for i in items:
thisresult.append(i)
if pred(i):
thisresult = []
results.append(thisresult)
return results
def find_common_prefix(strs):
"""
Given a list (iterable) of strings, return the longest common prefix.
>>> find_common_prefix(['abracadabra', 'abracadero', 'abranch'])
'abra'
>>> find_common_prefix(['abracadabra', 'abracadero', 'mt. fuji'])
''
"""
common = []
for cgroup in izip(*strs):
if all(x == cgroup[0] for x in cgroup[1:]):
common.append(cgroup[0])
else:
break
return ''.join(common)
def list_bifilter(pred, iterable):
"""
Filter an iterable into two output lists: the first containing all
elements of the iterable for which 'pred' returns true, and the second
containing all others. Order of the elements is otherwise retained.
>>> list_bifilter(lambda x: isinstance(x, int), (4, 'bingo', 1.2, 6, True))
([4, 6], ['bingo', 1.2, True])
"""
yes_s = []
no_s = []
for i in iterable:
(yes_s if pred(i) else no_s).append(i)
return yes_s, no_s
def identity(x):
return x