From 2f979ed60fc4f9dab2db7ce9921ff2953acd714c Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 8 Oct 2012 11:10:09 -0500 Subject: [PATCH] cqlsh: update recognized syntax Patch by paul cannon and Aleksey Yeschenko, reviewed by brandonwilliams for CASSANDRA-4488 --- bin/cqlsh | 92 +++- pylib/cqlshlib/cql3handling.py | 888 +++++++++++++++++++++++++++------ pylib/cqlshlib/cqlhandling.py | 16 +- pylib/cqlshlib/pylexotron.py | 10 +- 4 files changed, 837 insertions(+), 169 deletions(-) diff --git a/bin/cqlsh b/bin/cqlsh index 0cc50c272d..9fad4c6f8a 100755 --- a/bin/cqlsh +++ b/bin/cqlsh @@ -441,6 +441,14 @@ class Shell(cmd.Cmd): self.cursor = self.conn.cursor() self.get_connection_versions() + # use 3.0.0-beta1 syntax if explicitly requested, or if using + # cassandra < 1.2. this only affects use of cql3; cql2 syntax + # in either case is the same. + if self.cassandraver_atleast(1, 2) and not self.is_cql3_beta(): + cql3handling.use_post_3_0_0_syntax() + else: + cql3handling.use_pre_3_0_0_syntax() + self.current_keyspace = keyspace self.color = color @@ -473,6 +481,9 @@ class Shell(cmd.Cmd): self.cql_version = ver self.cql_ver_tuple = vertuple + def is_cql3_beta(self): + return self.cql_ver_tuple == (3, 0, 0, 'beta1') + def cqlver_atleast(self, major, minor=0, patch=0): return self.cql_ver_tuple[:3] >= (major, minor, patch) @@ -596,6 +607,11 @@ class Shell(cmd.Cmd): raise ColumnFamilyNotFound("Unconfigured column family %r" % (cfname,)) def get_columnfamily_names(self, ksname=None): + if self.cqlver_atleast(3) and ksname not in SYSTEM_KEYSPACES: + # since cql3 tables may be left out of thrift results, but + # info on tables in system keyspaces still aren't included + # in system.schema_* + return self.get_columnfamily_names_cql3(ksname=ksname) return [c.name for c in self.get_columnfamilies(ksname)] def get_index_names(self, ksname=None): @@ -681,6 +697,18 @@ class Shell(cmd.Cmd): # ===== cql3-dependent parts ===== + def get_columnfamily_names_cql3(self, ksname=None): + if ksname is None: + ksname = self.current_keyspace + if self.cassandraver_atleast(1, 2): + cf_q = """select columnfamily_name from system.schema_columnfamilies + where keyspace_name=:ks""" + else: + cf_q = """select "columnfamily" from system.schema_columnfamilies + where "keyspace"=:ks""" + self.cursor.execute(cf_q, {'ks': ksname}) + return [row[0] for row in self.cursor.fetchall()] + def get_columnfamily_layout(self, ksname, cfname): if ksname is None: ksname = self.current_keyspace @@ -853,7 +881,9 @@ class Shell(cmd.Cmd): return self.perform_statement(cqlruleset.cql_extract_orig(tokens, srcstr)) def handle_parse_error(self, cmdword, tokens, parsed, srcstr): - if cmdword.lower() == 'select': + if cmdword.lower() in ('select', 'insert', 'update', 'delete', 'truncate', + 'create', 'drop', 'alter', 'grant', 'revoke', + 'batch', 'list'): # 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(cqlruleset.cql_extract_orig(tokens, srcstr)) @@ -1105,10 +1135,20 @@ class Shell(cmd.Cmd): def print_recreate_keyspace(self, ksdef, out): stratclass = trim_if_present(ksdef.strategy_class, 'org.apache.cassandra.locator.') ksname = self.cql_protect_name(ksdef.name) - out.write("CREATE KEYSPACE %s WITH strategy_class = %s" - % (ksname, self.cql_protect_value(stratclass))) - for opname, opval in ksdef.strategy_options.iteritems(): - out.write("\n AND strategy_options:%s = %s" % (opname, self.cql_protect_value(opval))) + if self.cqlver_atleast(3) and not self.is_cql3_beta(): + out.write("CREATE KEYSPACE %s WITH replication = {\n" % ksname) + out.write(" 'class': %s" % self.cql_protect_value(stratclass)) + for opname, opval in ksdef.strategy_options.iteritems(): + out.write(",\n %s: %s" % (self.cql_protect_value(opname), + self.cql_protect_value(opval))) + out.write("\n}") + if not ksdef.durable_writes: + out.write(" AND durable_writes = 'false'") + else: + out.write("CREATE KEYSPACE %s WITH strategy_class = %s" + % (ksname, self.cql_protect_value(stratclass))) + for opname, opval in ksdef.strategy_options.iteritems(): + out.write("\n AND strategy_options:%s = %s" % (opname, self.cql_protect_value(opval))) out.write(';\n') if ksdef.cf_defs: @@ -1224,24 +1264,42 @@ class Shell(cmd.Cmd): out.write(' WITH COMPACT STORAGE') joiner = 'AND' + # TODO: this should display CLUSTERING ORDER BY information too. + # work out how to determine that from a layout. + cf_opts = [] - for option in cqlruleset.columnfamily_layout_options: - optval = getattr(layout, option, None) + for cql3option, layoutoption in cqlruleset.columnfamily_layout_options: + if layoutoption is None: + layoutoption = cql3option + optval = getattr(layout, layoutoption, None) if optval is None: continue - if option == 'row_cache_provider': - optval = trim_if_present(optval, 'org.apache.cassandra.cache.') - elif option == 'compaction_strategy_class': + elif layoutoption == '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 cqlruleset.columnfamily_layout_map_options: - optmap = getattr(layout, option, {}) - for k, v in optmap.items(): - if option == 'compression_parameters' and k == 'sstable_compression': - v = trim_if_present(v, 'org.apache.cassandra.io.compress.') - cf_opts.append(('%s:%s' % (option, k.encode('ascii')), self.cql_protect_value(v))) + cf_opts.append((cql3option, self.cql_protect_value(optval))) + for cql3option, layoutoption, _ in cqlruleset.columnfamily_layout_map_options: + if layoutoption is None: + layoutoption = cql3option + optmap = getattr(layout, layoutoption, {}) + if layoutoption == 'compression_parameters': + compclass = optmap.get('sstable_compression') + if compclass is not None: + optmap['sstable_compression'] = \ + trim_if_present(compclass, 'org.apache.cassandra.io.compress.') + if self.cqlver_atleast(3) and not self.is_cql3_beta(): + cf_opts.append((cql3option, optmap)) + else: + for k, v in optmap.items(): + cf_opts.append(('%s:%s' % (cql3option, k.encode('ascii')), + self.cql_protect_value(v))) if cf_opts: for optname, optval in cf_opts: + if isinstance(optval, dict): + optval = '{%s}' % ', '.join(['%s: %s' % (self.cql_protect_value(k), + self.cql_protect_value(v)) + for (k, v) in optval.items()]) + if optval == '{}': + continue out.write(" %s\n %s=%s" % (joiner, optname, optval)) joiner = 'AND' out.write(";\n") diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py index 4fc771436f..8728f60fea 100644 --- a/pylib/cqlshlib/cql3handling.py +++ b/pylib/cqlshlib/cql3handling.py @@ -18,7 +18,10 @@ import re from warnings import warn from .cqlhandling import CqlParsingRuleSet, Hint from cql.cqltypes import (cql_types, lookup_casstype, CompositeType, UTF8Type, - ColumnToCollectionType) + ColumnToCollectionType, CounterColumnType) + +simple_cql_types = set(cql_types) +simple_cql_types.difference_update(('set', 'map', 'list')) try: import json @@ -32,6 +35,8 @@ class UnexpectedTableStructure(UserWarning): def __str__(self): return 'Unexpected table structure; may not translate correctly to CQL. ' + self.msg +SYSTEM_KEYSPACES = ('system', 'system_traces') + class Cql3ParsingRuleSet(CqlParsingRuleSet): keywords = set(( 'select', 'from', 'where', 'and', 'key', 'insert', 'update', 'with', @@ -40,7 +45,9 @@ class Cql3ParsingRuleSet(CqlParsingRuleSet): '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' + 'compact', 'storage', 'order', 'by', 'asc', 'desc', 'clustering', + 'token', 'writetime', 'map', 'list', 'to', 'grant', 'grants', 'revoke', + 'option', 'describe', 'for', 'full_access', 'no_access' )) columnfamily_options = ( @@ -54,24 +61,57 @@ class Cql3ParsingRuleSet(CqlParsingRuleSet): ('compaction_strategy_class', 'compaction_strategy'), ) - 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', - 'replicate_on_write', - 'compaction_strategy_class', + old_columnfamily_layout_options = ( + # (CQL3 option name, schema_columnfamilies column name (or None if same)) + ('comment', None), + ('bloom_filter_fp_chance', None), + ('caching', None), + ('read_repair_chance', None), + ('dclocal_read_repair_chance', 'local_read_repair_chance'), + ('gc_grace_seconds', None), + ('replicate_on_write', None), + ('compaction_strategy_class', None), ) - columnfamily_layout_map_options = ( - ('compaction_strategy_options', - ()), - ('compression_parameters', + new_columnfamily_layout_options = ( + ('comment', None), + ('bloom_filter_fp_chance', None), + ('caching', None), + ('read_repair_chance', None), + ('dclocal_read_repair_chance', 'local_read_repair_chance'), + ('gc_grace_seconds', None), + ('replicate_on_write', None), + ('default_read_consistency', None), + ('default_write_consistency', None), + ) + + old_columnfamily_layout_map_options = ( + # (CQL3 option prefix, schema_columnfamilies column name (or None if same), + # list of known suboptions) + ('compaction_strategy_options', None, + ('min_compaction_threshold', 'max_compaction_threshold')), + ('compression_parameters', None, ('sstable_compression', 'chunk_length_kb', 'crc_check_chance')), ) + new_columnfamily_layout_map_options = ( + # (CQL3 option name, schema_columnfamilies column name (or None if same), + # list of known map keys) + ('compaction', 'compaction_strategy_options', + ('min_threshold', 'max_threshold')), + ('compression', 'compression_parameters', + ('sstable_compression', 'chunk_length_kb', 'crc_check_chance')), + ) + + new_obsolete_cf_options = ( + 'compaction_strategy_class', + 'compaction_strategy_options', + 'min_compaction_threshold', + 'max_compaction_threshold', + 'compaction_parameters', + 'compression_parameters', + ) + @staticmethod def token_dequote(tok): if tok[0] == 'unclosedName': @@ -89,7 +129,7 @@ class Cql3ParsingRuleSet(CqlParsingRuleSet): name = name.strip() if name == '': return name - if name[0] == '"': + if name[0] == '"' and name[-1] == '"': name = name[1:-1].replace('""', '"') return name @@ -158,7 +198,6 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ; ::= /"([^"]|"")*"/ ; ::= /-?[0-9]+\.[0-9]+/ ; ::= /[0-9]+/ ; - ::= /-?[0-9]+/ ; ::= /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/ ; ::= /[a-z][a-z0-9_]*/ ; ::= ":" ; @@ -168,6 +207,8 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ; ::= /[<>]=?/ ; ::= /[][{}]/ ; + ::= "-"? ; + ::= /'([^']|'')*/ ; ::= /"([^"]|"")*/ ; ::= /[/][*][^\n]*$/ ; @@ -177,19 +218,36 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ; | | ; - ::= token="TOKEN" "(" ")" - | - ; + ::= token="TOKEN" "(" ( "," )* ")" + | + ; + ::= + | + ; ::= | | ; ::= ; # just an alias + ::= + | + | + ; + ::= "[" ( ( "," )* )? "]" + ; + ::= "{" ( ( "," )* )? "}" + ; + ::= "{" ":" ( "," ":" )* "}" + ; + ::= | | | + | + | + | ; ::= @@ -206,6 +264,7 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ; | | | + | ; ::= cl=( @@ -218,12 +277,22 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ; | ) ; - ::= typename=( | ) ; +# timestamp is included here, since it's also a keyword + ::= typename=( | | ) ; - ::= ( ksname= "." )? cfname= ; + ::= | ; + + ::= "map" "<" "," ">" + | "list" "<" ">" + | "set" "<" ">" + ; + + ::= ( ksname= dot="." )? cfname= ; ::= ksname= ; + ::= ksname= ; + ::= | | ; @@ -240,28 +309,387 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ; | | ) ; + +# will be defined once cqlsh determines whether we're using +# 3.0.0-beta1 or later. :/ + + ::= [propname]= propeq="=" [propval]= + ; + ::= propsimpleval=( + | + | + | + | ) + # we don't use here so we can get more targeted + # completions: + | propsimpleval="{" [propmapkey]= ":" [propmapval]= + ( ender="," [propmapkey]= ":" [propmapval]= )* + ender="}" + ; + + ::= [propname]= propeq="=" [optval]= + ; + ::= optname= ( optsep=":" subopt=( | ) )? + ; + ::= + | + | + | + ; ''' +def use_pre_3_0_0_syntax(): + # cassandra-1.1 support + CqlRuleSet.append_rules(''' + ::= ; + ''') + CqlRuleSet.columnfamily_layout_map_options = \ + CqlRuleSet.old_columnfamily_layout_map_options + CqlRuleSet.columnfamily_layout_options = \ + CqlRuleSet.old_columnfamily_layout_options + +def use_post_3_0_0_syntax(): + CqlRuleSet.append_rules(''' + ::= ; + ''') + CqlRuleSet.columnfamily_layout_map_options = \ + CqlRuleSet.new_columnfamily_layout_map_options + CqlRuleSet.columnfamily_layout_options = \ + CqlRuleSet.new_columnfamily_layout_options + CqlRuleSet.obsolete_cf_options += CqlRuleSet.new_obsolete_cf_options + +def prop_equals_completer(ctxt, cass): + if not working_on_keyspace(ctxt): + # we know if the thing in the property name position is "compact" or + # "clustering" that there won't actually be an equals sign, because + # there are no properties by those names. there are, on the other hand, + # table properties that start with those keywords which don't have + # equals signs at all. + curprop = ctxt.get_binding('propname')[-1].upper() + if curprop in ('COMPACT', 'CLUSTERING'): + return () + return ['='] + +completer_for('oldPropSpec', 'propeq')(prop_equals_completer) +completer_for('newPropSpec', 'propeq')(prop_equals_completer) + +@completer_for('newPropSpec', 'propname') +def new_prop_name_completer(ctxt, cass): + if working_on_keyspace(ctxt): + return ks_new_prop_name_completer(ctxt, cass) + else: + return cf_new_prop_name_completer(ctxt, cass) + +@completer_for('propertyValue', 'propsimpleval') +def new_prop_val_completer(ctxt, cass): + if working_on_keyspace(ctxt): + return ks_new_prop_val_completer(ctxt, cass) + else: + return cf_new_prop_val_completer(ctxt, cass) + +@completer_for('propertyValue', 'propmapkey') +def new_prop_val_mapkey_completer(ctxt, cass): + if working_on_keyspace(ctxt): + return ks_new_prop_val_mapkey_completer(ctxt, cass) + else: + return cf_new_prop_val_mapkey_completer(ctxt, cass) + +@completer_for('propertyValue', 'propmapval') +def new_prop_val_mapval_completer(ctxt, cass): + if working_on_keyspace(ctxt): + return ks_new_prop_val_mapval_completer(ctxt, cass) + else: + return cf_new_prop_val_mapval_completer(ctxt, cass) + +@completer_for('propertyValue', 'ender') +def new_prop_val_mapender_completer(ctxt, cass): + if working_on_keyspace(ctxt): + return ks_new_prop_val_mapender_completer(ctxt, cass) + else: + return cf_new_prop_val_mapender_completer(ctxt, cass) + +def ks_new_prop_name_completer(ctxt, cass): + optsseen = ctxt.get_binding('propname', ()) + if 'replication' not in optsseen: + return ['replication'] + return ["durable_writes"] + +def ks_new_prop_val_completer(ctxt, cass): + optname = ctxt.get_binding('propname')[-1] + if optname == 'durable_writes': + return ["'true'", "'false'"] + if optname == 'replication': + return ["{'class': '"] + return () + +def ks_new_prop_val_mapkey_completer(ctxt, cass): + optname = ctxt.get_binding('propname')[-1] + if optname != 'replication': + return () + keysseen = map(dequote_value, ctxt.get_binding('propmapkey', ())) + valsseen = map(dequote_value, ctxt.get_binding('propmapval', ())) + for k, v in zip(keysseen, valsseen): + if k == 'class': + repclass = v + break + else: + return ["'class'"] + if repclass in CqlRuleSet.replication_factor_strategies: + opts = set(('replication_factor',)) + elif repclass == 'NetworkTopologyStrategy': + return [Hint('')] + return map(escape_value, opts.difference(keysseen)) + +def ks_new_prop_val_mapval_completer(ctxt, cass): + optname = ctxt.get_binding('propname')[-1] + if optname != 'replication': + return () + currentkey = dequote_value(ctxt.get_binding('propmapkey')[-1]) + if currentkey == 'class': + return map(escape_value, CqlRuleSet.replication_strategies) + return [Hint('')] + +def ks_new_prop_val_mapender_completer(ctxt, cass): + optname = ctxt.get_binding('propname')[-1] + if optname != 'replication': + return [','] + keysseen = map(dequote_value, ctxt.get_binding('propmapkey', ())) + valsseen = map(dequote_value, ctxt.get_binding('propmapval', ())) + for k, v in zip(keysseen, valsseen): + if k == 'class': + repclass = v + break + else: + return [','] + if repclass in CqlRuleSet.replication_factor_strategies: + if 'replication_factor' not in keysseen: + return [','] + if repclass == 'NetworkTopologyStrategy' and len(keysseen) == 1: + return [','] + return ['}'] + +def cf_new_prop_name_completer(ctxt, cass): + return [c[0] for c in (CqlRuleSet.columnfamily_layout_options + + CqlRuleSet.columnfamily_layout_map_options)] + +def cf_new_prop_val_completer(ctxt, cass): + exist_opts = ctxt.get_binding('propname') + this_opt = exist_opts[-1] + if this_opt == 'compression': + return ["{'sstable_compression': '"] + if this_opt == 'compaction': + return ["{'class': '"] + if any(this_opt == opt[0] for opt in CqlRuleSet.obsolete_cf_options): + return ["''"] + if this_opt in ('read_repair_chance', 'bloom_filter_fp_chance', + 'dclocal_read_repair_chance'): + return [Hint('')] + if this_opt == 'replicate_on_write': + return ["'yes'", "'no'"] + if this_opt in ('min_compaction_threshold', 'max_compaction_threshold', + 'gc_grace_seconds'): + return [Hint('')] + if this_opt == 'default_read_consistency': + return [cl for cl in CqlRuleSet.consistency_levels if cl != 'ANY'] + if this_opt == 'default_write_consistency': + return CqlRuleSet.consistency_levels + return [Hint('')] + +def cf_new_prop_val_mapkey_completer(ctxt, cass): + optname = ctxt.get_binding('propname')[-1] + for cql3option, _, subopts in CqlRuleSet.columnfamily_layout_map_options: + if optname == cql3option: + break + else: + return () + keysseen = map(dequote_value, ctxt.get_binding('propmapkey', ())) + valsseen = map(dequote_value, ctxt.get_binding('propmapval', ())) + pairsseen = dict(zip(keysseen, valsseen)) + if optname == 'compression': + return map(escape_value, set(subopts).difference(keysseen)) + if optname == 'compaction': + opts = set(subopts) + try: + csc = pairsseen['class'] + except KeyError: + return ["'class'"] + csc = csc.split('.')[-1] + if csc == 'SizeTieredCompactionStrategy': + opts.add('min_sstable_size') + elif csc == 'LeveledCompactionStrategy': + opts.add('sstable_size_in_mb') + return map(escape_value, opts) + return () + +def cf_new_prop_val_mapval_completer(ctxt, cass): + opt = ctxt.get_binding('propname')[-1] + key = dequote_value(ctxt.get_binding('propmapkey')[-1]) + if opt == 'compaction': + if key == 'class': + return map(escape_value, CqlRuleSet.available_compaction_classes) + return [Hint('')] + elif opt == 'compression': + if key == 'sstable_compression': + return map(escape_value, CqlRuleSet.available_compression_classes) + return [Hint('')] + return () + +def cf_new_prop_val_mapender_completer(ctxt, cass): + return [',', '}'] + +@completer_for('optionName', 'optname') +def old_prop_name_completer(ctxt, cass): + if working_on_keyspace(ctxt): + return ks_old_prop_name_completer(ctxt, cass) + else: + return cf_old_prop_name_completer(ctxt, cass) + +@completer_for('oldPropSpec', 'optval') +def old_prop_val_completer(ctxt, cass): + if working_on_keyspace(ctxt): + return ks_old_prop_val_completer(ctxt, cass) + else: + return cf_old_prop_val_completer(ctxt, cass) + +@completer_for('optionName', 'optsep') +def old_prop_separator_completer(ctxt, cass): + if working_on_keyspace(ctxt): + return ks_old_prop_separator_completer(ctxt, cass) + else: + return cf_old_prop_separator_completer(ctxt, cass) + +@completer_for('optionName', 'subopt') +def old_prop_suboption_completer(ctxt, cass): + if working_on_keyspace(ctxt): + return ks_old_prop_suboption_completer(ctxt, cass) + else: + return cf_old_prop_suboption_completer(ctxt, cass) + +def ks_old_prop_name_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 CqlRuleSet.replication_factor_strategies: + return ['strategy_options:replication_factor ='] + return [Hint('')] + +def ks_old_prop_val_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('')] + +def ks_old_prop_separator_completer(ctxt, cass): + curopt = ctxt.get_binding('optname')[-1] + if curopt == 'strategy_options': + return [':'] + return () + +def ks_old_prop_suboption_completer(ctxt, cass): + exist_opts = ctxt.get_binding('optname') + if exist_opts[-1] != 'strategy_options': + return () + try: + stratopt = exist_opts.index('strategy_class') + except ValueError: + return () + vals = ctxt.get_binding('optval') + stratclass = dequote_value(vals[stratopt]) + if stratclass in CqlRuleSet.replication_factor_strategies: + return ['replication_factor ='] + return [Hint('')] + +def cf_old_prop_name_completer(ctxt, cass): + return list(CqlRuleSet.columnfamily_layout_options) + \ + [c[0] + ':' for c in CqlRuleSet.columnfamily_layout_map_options] + +def cf_old_prop_val_completer(ctxt, cass): + exist_opts = ctxt.get_binding('propname') + 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 ["''"] + if this_opt in ('comparator', 'default_validation'): + return simple_cql_types + if this_opt in ('read_repair_chance', 'bloom_filter_fp_chance'): + return [Hint('')] + if this_opt == 'replicate_on_write': + return [Hint('')] + if this_opt in ('min_compaction_threshold', 'max_compaction_threshold', 'gc_grace_seconds'): + return [Hint('')] + return [Hint('')] + +def cf_old_prop_separator_completer(ctxt, cass): + opt = ctxt.get_binding('optname') + if any(opt == c[0] for c in CqlRuleSet.columnfamily_layout_map_options): + return [':'] + return () + +def cf_old_prop_suboption_completer(ctxt, cass): + opt = ctxt.get_binding('optname') + if opt == 'compaction_strategy_options': + # try to determine the strategy class in use + prevopts = ctxt.get_binding('propname', ()) + 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_layout_map_options: + if opt == optname: + return subopts + return () + @completer_for('consistencylevel', 'cl') def consistencylevel_cl_completer(ctxt, cass): return CqlRuleSet.consistency_levels -@completer_for('extendedTerm', 'token') +@completer_for('tokenDefinition', 'token') def token_word_completer(ctxt, cass): return ['TOKEN('] -@completer_for('storageType', 'typename') +@completer_for('simpleStorageType', 'typename') def storagetype_completer(ctxt, cass): - return cql_types + return simple_cql_types @completer_for('keyspaceName', 'ksname') def ks_name_completer(ctxt, cass): return map(maybe_escape_name, cass.get_keyspace_names()) +@completer_for('nonSystemKeyspaceName', 'ksname') +def ks_name_completer(ctxt, cass): + ksnames = [n for n in cass.get_keyspace_names() if n not in SYSTEM_KEYSPACES] + return map(maybe_escape_name, ksnames) + @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', 'dot') +def cf_ks_dot_completer(ctxt, cass): + name = dequote_name(ctxt.get_binding('ksname')) + if name in cass.get_keyspace_names(): + return ['.'] + return [] + @completer_for('columnFamilyName', 'cfname') def cf_name_completer(ctxt, cass): ks = ctxt.get_binding('ksname', None) @@ -283,10 +711,18 @@ def unreserved_keyword_completer(ctxt, cass): return () def get_cf_layout(ctxt, cass): - ks = dequote_name(ctxt.get_binding('ksname', None)) + ks = ctxt.get_binding('ksname', None) + if ks is not None: + ks = dequote_name(ks) cf = dequote_name(ctxt.get_binding('cfname')) return cass.get_columnfamily_layout(ks, cf) +def working_on_keyspace(ctxt): + wat = ctxt.get_binding('wat').upper() + if wat in ('KEYSPACE', 'SCHEMA'): + return True + return False + syntax_rules += r''' ::= "USE" ; @@ -300,13 +736,19 @@ syntax_rules += r''' ::= ("AND" )* ; ::= [rel_lhs]= ("=" | "<" | ">" | "<=" | ">=") - | token="TOKEN" "(" rel_tokname= ")" ("=" | "<" | ">" | "<=" | ">=") + | token="TOKEN" "(" [rel_tokname]= + ( "," [rel_tokname]= )* + ")" ("=" | "<" | ">" | "<=" | ">=") | [rel_lhs]= "IN" "(" ( "," )* ")" ; - ::= colname= ("," colname=)* + ::= ("," )* | "*" | "COUNT" "(" star=( "*" | "1" ) ")" ; + ::= [colname]= + | "WRITETIME" "(" [colname]= ")" + | "TTL" "(" [colname]= ")" + ; ::= [ordercol]= ( "ASC" | "DESC" )? ; ''' @@ -362,13 +804,14 @@ def select_relation_lhs_completer(ctxt, cass): def select_count_star_completer(ctxt, cass): return ['*'] -explain_completion('selectClause', 'colname') +explain_completion('selector', 'colname') syntax_rules += r''' ::= "INSERT" "INTO" cf= - "(" keyname= "," - [colname]= ( "," [colname]= )* ")" - "VALUES" "(" "," ( "," )* ")" + "(" [colname]= "," [colname]= + ( "," [colname]= )* ")" + "VALUES" "(" [newval]= valcomma="," [newval]= + ( valcomma="," [newval]= )* valcomma=")" ( "USING" [insertopt]= ( "AND" [insertopt]= )* )? ; @@ -378,12 +821,42 @@ syntax_rules += r''' ; ''' -@completer_for('insertStatement', 'keyname') -def insert_keyname_completer(ctxt, cass): +@completer_for('insertStatement', 'colname') +def insert_colname_completer(ctxt, cass): layout = get_cf_layout(ctxt, cass) - return [layout.primary_key_components[0]] + colnames = set(map(dequote_name, ctxt.get_binding('colname', ()))) + keycols = layout.primary_key_components + for k in keycols: + if k not in colnames: + return [maybe_escape_name(k)] + normalcols = set([c.name for c in layout.columns]) - set(keycols) - colnames + return map(maybe_escape_name, normalcols) -explain_completion('insertStatement', 'colname') +@completer_for('insertStatement', 'newval') +def insert_newval_completer(ctxt, cass): + layout = get_cf_layout(ctxt, cass) + insertcols = map(dequote_name, ctxt.get_binding('colname')) + valuesdone = ctxt.get_binding('newval', ()) + if len(valuesdone) >= len(insertcols): + return [] + curcol = insertcols[len(valuesdone)] + cqltype = layout.get_column(curcol).cqltype + coltype = cqltype.typename + if coltype in ('map', 'set'): + return ['{'] + if coltype == 'list': + return ['['] + return [Hint('' % (maybe_escape_name(curcol), + cqltype.cql_parameterized_type()))] + +@completer_for('insertStatement', 'valcomma') +def insert_valcomma_completer(ctxt, cass): + layout = get_cf_layout(ctxt, cass) + numcols = len(ctxt.get_binding('colname', ())) + numvals = len(ctxt.get_binding('newval', ())) + if numcols > numvals: + return [','] + return [')'] @completer_for('insertStatement', 'insertopt') def insert_option_completer(ctxt, cass): @@ -399,8 +872,11 @@ syntax_rules += r''' "SET" ( "," )* "WHERE" ; - ::= updatecol= "=" update_rhs= - ( counterop=( "+" | "-" ) )? + ::= updatecol= + ( "=" update_rhs=( | ) + ( counterop=( "+" | "-" ) inc= + | listadder="+" listcol= ) + | indexbracket="[" "]" "=" ) ; ''' @@ -414,13 +890,23 @@ def insert_option_completer(ctxt, cass): @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]) + normals = set([cm.name for cm in layout.columns]) \ + - set(layout.primary_key_components) + return map(maybe_escape_name, normals) @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('')] + cqltype = layout.get_column(curcol).cqltype + coltype = cqltype.typename + if coltype == 'counter': + return maybe_escape_name(curcol) + if coltype in ('map', 'set'): + return ["{"] + if coltype == 'list': + return ["["] + return [Hint('' % cqltype.cql_parameterized_type())] @completer_for('assignment', 'counterop') def update_counterop_completer(ctxt, cass): @@ -428,12 +914,45 @@ def update_counterop_completer(ctxt, cass): curcol = dequote_name(ctxt.get_binding('updatecol', '')) return ['+', '-'] if layout.is_counter_col(curcol) else [] +@completer_for('assignment', 'inc') +def update_counter_inc_completer(ctxt, cass): + layout = get_cf_layout(ctxt, cass) + curcol = dequote_name(ctxt.get_binding('updatecol', '')) + if layout.is_counter_col(curcol): + return Hint('') + return [] + +@completer_for('assignment', 'listadder') +def update_listadder_completer(ctxt, cass): + rhs = ctxt.get_binding('update_rhs') + if rhs.startswith('['): + return ['+'] + +@completer_for('assignment', 'listcol') +def update_listcol_completer(ctxt, cass): + rhs = ctxt.get_binding('update_rhs') + if rhs.startswith('['): + colname = dequote_name(ctxt.get_binding('updatecol')) + return [maybe_escape_name(colname)] + return [] + +@completer_for('assignment', 'indexbracket') +def update_indexbracket_completer(ctxt, cass): + layout = get_cf_layout(ctxt, cass) + curcol = dequote_name(ctxt.get_binding('updatecol', '')) + coltype = layout.get_column(curcol).cqltype.typename + if coltype in ('map', 'list'): + return ['['] + return [] + syntax_rules += r''' - ::= "DELETE" ( [delcol]= ( "," [delcol]= )* )? + ::= "DELETE" ( ( "," )* )? "FROM" cf= ( "USING" [delopt]= ( "AND" [delopt]= )* )? "WHERE" ; + ::= delcol= ( memberbracket="[" memberselector= "]" )? + ; ::= "CONSISTENCY" | "TIMESTAMP" ; @@ -446,7 +965,12 @@ def delete_opt_completer(ctxt, cass): opts.discard(opt.split()[0]) return opts -explain_completion('deleteStatement', 'delcol', '') +@completer_for('deleteSelector', 'delcol') +def delete_delcol_completer(ctxt, cass): + layout = get_cf_layout(ctxt, cass) + cols = set([c.name for c in layout.columns + if c not in layout.primary_key_components]) + return map(maybe_escape_name, cols) syntax_rules += r''' ::= "BEGIN" ( "UNLOGGED" | "COUNTER" )? "BATCH" @@ -475,21 +999,19 @@ syntax_rules += r''' ''' syntax_rules += r''' - ::= "CREATE" "KEYSPACE" ksname= - "WITH" [optname]= "=" [optval]= - ( "AND" [optname]= "=" [optval]= )* + ::= "CREATE" wat=( "KEYSPACE" | "SCHEMA" ) ksname= + "WITH" ( "AND" )* ; - ::= ( ":" ( | ) )? - ; - ::= - | - | - ; ''' -explain_completion('createKeyspaceStatement', 'ksname', '') +@completer_for('createKeyspaceStatement', 'wat') +def create_ks_wat_completer(ctxt, cass): + # would prefer to get rid of the "schema" nomenclature in cql3 + if ctxt.get_binding('partial', '') == '': + return ['KEYSPACE'] + return ['KEYSPACE', 'SCHEMA'] -@completer_for('createKeyspaceStatement', 'optname') +@completer_for('oldPropSpec', 'optname') def create_ks_opt_completer(ctxt, cass): exist_opts = ctxt.get_binding('optname', ()) try: @@ -498,58 +1020,155 @@ def create_ks_opt_completer(ctxt, cass): return ['strategy_class ='] vals = ctxt.get_binding('optval') stratclass = dequote_value(vals[stratopt]) - if stratclass in ('SimpleStrategy', - 'org.apache.cassandra.locator.SimpleStrategy', - 'OldNetworkTopologyStrategy', - 'org.apache.cassandra.locator.OldNetworkTopologyStrategy'): + if stratclass in CqlRuleSet.replication_factor_strategies: return ['strategy_options:replication_factor ='] return [Hint('')] -@completer_for('createKeyspaceStatement', 'optval') +@completer_for('oldPropSpec', '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('')] +@completer_for('newPropSpec', 'propname') +def keyspace_properties_option_name_completer(ctxt, cass): + optsseen = ctxt.get_binding('propname', ()) + if 'replication' not in optsseen: + return ['replication'] + return ["durable_writes"] + +@completer_for('propertyValue', 'propsimpleval') +def property_value_completer(ctxt, cass): + optname = ctxt.get_binding('propname')[-1] + if optname == 'durable_writes': + return ["'true'", "'false'"] + if optname == 'replication': + return ["{'class': '"] + return () + +@completer_for('propertyValue', 'propmapkey') +def keyspace_properties_map_key_completer(ctxt, cass): + optname = ctxt.get_binding('propname')[-1] + if optname != 'replication': + return () + keysseen = map(dequote_value, ctxt.get_binding('propmapkey', ())) + valsseen = map(dequote_value, ctxt.get_binding('propmapval', ())) + for k, v in zip(keysseen, valsseen): + if k == 'class': + repclass = v + break + else: + return ["'class'"] + if repclass in CqlRuleSet.replication_factor_strategies: + opts = set(('replication_factor',)) + elif repclass == 'NetworkTopologyStrategy': + return [Hint('')] + return map(escape_value, opts.difference(keysseen)) + +@completer_for('propertyValue', 'propmapval') +def keyspace_properties_map_value_completer(ctxt, cass): + optname = ctxt.get_binding('propname')[-1] + if optname != 'replication': + return () + currentkey = dequote_value(ctxt.get_binding('propmapkey')[-1]) + if currentkey == 'class': + return map(escape_value, CqlRuleSet.replication_strategies) + return [Hint('')] + +@completer_for('propertyValue', 'ender') +def keyspace_properties_map_ender_completer(ctxt, cass): + optname = ctxt.get_binding('propname')[-1] + if optname != 'replication': + return [','] + keysseen = map(dequote_value, ctxt.get_binding('propmapkey', ())) + valsseen = map(dequote_value, ctxt.get_binding('propmapval', ())) + for k, v in zip(keysseen, valsseen): + if k == 'class': + repclass = v + break + else: + return [','] + if repclass in CqlRuleSet.replication_factor_strategies: + opts = set(('replication_factor',)) + if 'replication_factor' not in keysseen: + return [','] + if repclass == 'NetworkTopologyStrategy' and len(keysseen) == 1: + return [','] + return ['}'] + syntax_rules += r''' - ::= "CREATE" ( "COLUMNFAMILY" | "TABLE" ) - ( ks= "." )? cf= + ::= "CREATE" wat=( "COLUMNFAMILY" | "TABLE" ) + ( ks= dot="." )? cf= "(" ( | ) ")" - ( "WITH" [cfopt]= "=" [optval]= - ( "AND" [cfopt]= "=" [optval]= )* )? + ( "WITH" ( "AND" )* )? ; - ::= keyalias= "PRIMARY" "KEY" - ( "," colname= )* + ::= + | "COMPACT" "STORAGE" + | "CLUSTERING" "ORDER" "BY" "(" + ( "," )* ")" + ; + + ::= [ordercol]= ( "ASC" | "DESC" ) + ; + + ::= [newcolname]= "PRIMARY" "KEY" + ( "," [newcolname]= )* ; - ::= [newcolname]= + ::= [newcolname]= "," [newcolname]= ( "," [newcolname]= )* - "," "PRIMARY" k="KEY" p="(" [pkey]= + "," "PRIMARY" k="KEY" p="(" ( partkey= | [pkey]= ) ( c="," [pkey]= )* ")" ; - ::= cfoptname= ( cfoptsep=":" cfsubopt=( | ) )? - ; - - ::= - | - | - | - ; + ::= "(" [ptkey]= "," [ptkey]= + ( "," [ptkey]= )* ")" + ; ''' +@completer_for('cfamOrdering', 'ordercol') +def create_cf_clustering_order_colname_completer(ctxt, cass): + colnames = map(dequote_name, ctxt.get_binding('newcolname', ())) + # Definitely some of these aren't valid for ordering, but I'm not sure + # precisely which are. This is good enough for now + return colnames + +@completer_for('createColumnFamilyStatement', 'wat') +def create_cf_wat_completer(ctxt, cass): + # would prefer to get rid of the "columnfamily" nomenclature in cql3 + if ctxt.get_binding('partial', '') == '': + return ['TABLE'] + return ['TABLE', 'COLUMNFAMILY'] + explain_completion('createColumnFamilyStatement', 'cf', '') -explain_completion('singleKeyCfSpec', 'keyalias', '') -explain_completion('singleKeyCfSpec', 'colname', '') explain_completion('compositeKeyCfSpec', 'newcolname', '') -@completer_for('compositeKeyCfSpec', 'pkey') -def create_cf_composite_key_declaration(ctxt, cass): +@completer_for('createColumnFamilyStatement', 'dot') +def create_cf_ks_dot_completer(ctxt, cass): + ks = dequote_name(ctxt.get_binding('ks')) + if ks in cass.get_keyspace_names(): + return ['.'] + return [] + +@completer_for('pkDef', 'ptkey') +def create_cf_pkdef_declaration_completer(ctxt, cass): cols_declared = ctxt.get_binding('newcolname') - pieces_already = ctxt.get_binding('pkey', ()) + pieces_already = ctxt.get_binding('ptkey', ()) + pieces_already = map(dequote_name, pieces_already) + 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', 'pkey') +def create_cf_composite_key_declaration_completer(ctxt, cass): + cols_declared = ctxt.get_binding('newcolname') + pieces_already = ctxt.get_binding('ptkey', ()) + ctxt.get_binding('pkey', ()) + pieces_already = map(dequote_name, pieces_already) while cols_declared[0] in pieces_already: cols_declared = cols_declared[1:] if len(cols_declared) < 2: @@ -572,67 +1191,6 @@ def create_cf_composite_primary_key_comma_completer(ctxt, cass): 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 ["''"] - if this_opt in ('comparator', 'default_validation'): - return cql_types - if this_opt in ('read_repair_chance', 'bloom_filter_fp_chance'): - return [Hint('')] - if this_opt == 'replicate_on_write': - return [Hint('')] - if this_opt in ('min_compaction_threshold', 'max_compaction_threshold', 'gc_grace_seconds'): - return [Hint('')] - return [Hint('')] - -completer_for('createColumnFamilyStatement', 'optval') \ - (create_cf_option_val_completer) - syntax_rules += r''' ::= "CREATE" "INDEX" indexname=? "ON" cf= "(" col= ")" @@ -648,7 +1206,7 @@ def create_index_col_completer(ctxt, cass): return map(maybe_escape_name, colnames) syntax_rules += r''' - ::= "DROP" "KEYSPACE" ksname= + ::= "DROP" "KEYSPACE" ksname= ; ::= "DROP" ( "COLUMNFAMILY" | "TABLE" ) cf= @@ -669,8 +1227,7 @@ syntax_rules += r''' ::= "ALTER" existcol= "TYPE" | "ADD" newcol= | "DROP" existcol= - | "WITH" [cfopt]= "=" [optval]= - ( "AND" [cfopt]= "=" [optval]= )* + | "WITH" ( "AND" )* ; ''' @@ -682,8 +1239,44 @@ def alter_table_col_completer(ctxt, cass): explain_completion('alterInstructions', 'newcol', '') -completer_for('alterInstructions', 'optval') \ - (create_cf_option_val_completer) +syntax_rules += r''' + ::= "ALTER" ( "KEYSPACE" | "SCHEMA" ) ks= + "WITH" ( "AND" )* + ; + + ::= "GRANT" "ON" cf= + "TO" + ( "WITH" "GRANT" "OPTION" )? + ; + + ::= "REVOKE" "ON" cf= + "FROM" + ; + + ::= "LIST" "GRANTS" "FOR" ; + + ::= "DESCRIBE" + | "USE" + | "CREATE" + | "ALTER" + | "DROP" + | "SELECT" + | "INSERT" + | "UPDATE" + | "DELETE" + | "FULL_ACCESS" + | "NO_ACCESS" + ; + + ::= user=( | ) + ; +''' + +@completer_for('username', 'user') +def username_user_completer(ctxt, cass): + # with I could see a way to do this usefully, but I don't. I don't know + # how any Authorities other than AllowAllAuthority work :/ + return [Hint('')] # END SYNTAX/COMPLETION RULE DEFINITIONS @@ -811,11 +1404,14 @@ class CqlTableDef: """ try: cfname = layout[u'columnfamily_name'] + ksname = layout[u'keyspace_name'] except KeyError: cfname = layout[u'columnfamily'] + ksname = layout[u'keyspace'] cf = cls(name=cfname) for attr, val in layout.items(): setattr(cf, attr.encode('ascii'), val) + cf.keyspace = ksname for attr in cls.json_attrs: try: setattr(cf, attr, json.loads(getattr(cf, attr))) @@ -928,7 +1524,7 @@ class CqlTableDef: def is_counter_col(self, colname): try: - return bool(self.get_column(colname).cqltype == 'counter') + return bool(self.get_column(colname).cqltype is CounterColumnType) except KeyError: return False diff --git a/pylib/cqlshlib/cqlhandling.py b/pylib/cqlshlib/cqlhandling.py index 681dbfc8af..58b1a74118 100644 --- a/pylib/cqlshlib/cqlhandling.py +++ b/pylib/cqlshlib/cqlhandling.py @@ -24,6 +24,8 @@ from cql import cqltypes Hint = pylexotron.Hint +SYSTEM_KEYSPACES = ('system',) + class CqlParsingRuleSet(pylexotron.ParsingRuleSet): keywords = set(( 'select', 'from', 'where', 'and', 'key', 'insert', 'update', 'with', @@ -84,6 +86,13 @@ class CqlParsingRuleSet(pylexotron.ParsingRuleSet): 'NetworkTopologyStrategy' ) + replication_factor_strategies = ( + 'SimpleStrategy', + 'org.apache.cassandra.locator.SimpleStrategy', + 'OldNetworkTopologyStrategy', + 'org.apache.cassandra.locator.OldNetworkTopologyStrategy' + ) + consistency_levels = ( 'ANY', 'ONE', @@ -385,7 +394,7 @@ class CqlParsingRuleSet(pylexotron.ParsingRuleSet): cqlword = cqlword.strip() if cqlword == '': return cqlword - if cqlword[0] == "'": + if cqlword[0] == "'" and cqlword[-1] == "'": cqlword = cqlword[1:-1].replace("''", "'") return cqlword @@ -736,10 +745,7 @@ def create_ks_opt_completer(ctxt, cass): return ['strategy_class ='] vals = ctxt.get_binding('optval') stratclass = dequote_value(vals[stratopt]) - if stratclass in ('SimpleStrategy', - 'org.apache.cassandra.locator.SimpleStrategy', - 'OldNetworkTopologyStrategy', - 'org.apache.cassandra.locator.OldNetworkTopologyStrategy'): + if stratclass in CqlRuleSet.replication_factor_strategies: return ['strategy_options:replication_factor ='] return [Hint('')] diff --git a/pylib/cqlshlib/pylexotron.py b/pylib/cqlshlib/pylexotron.py index e66d2a0cb6..ad283dfaba 100644 --- a/pylib/cqlshlib/pylexotron.py +++ b/pylib/cqlshlib/pylexotron.py @@ -118,19 +118,24 @@ class matcher: @staticmethod def try_registered_completion(ctxt, symname, completions): + debugging = ctxt.get_binding('*DEBUG*', False) if ctxt.remainder or completions is None: return False try: completer = ctxt.get_completer(symname) except KeyError: return False + if debugging: + print "Trying completer %r with %r" % (completer, ctxt) try: new_compls = completer(ctxt) except Exception: - if ctxt.get_binding('*DEBUG*', False): + if debugging: import traceback traceback.print_exc() return False + if debugging: + print "got %r" % (new_compls,) completions.update(new_compls) return True @@ -291,6 +296,9 @@ class terminal_type_matcher(matcher): self.submatcher.match(ctxt, completions) return [] + def __repr__(self): + return '%s(%r, %r)' % (self.__class__.__name__, self.tokentype, self.submatcher) + class ParsingRuleSet: RuleSpecScanner = SaferScanner([ (r'::=', lambda s,t: t),