| # 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 cassandra.metadata import maybe_escape_name |
| from cqlshlib import helptopics |
| from cqlshlib.cqlhandling import CqlParsingRuleSet, Hint |
| |
| simple_cql_types = {'ascii', 'bigint', 'blob', 'boolean', 'counter', 'date', 'decimal', 'double', 'duration', 'float', |
| 'inet', 'int', 'smallint', 'text', 'time', 'timestamp', 'timeuuid', 'tinyint', 'uuid', 'varchar', |
| 'varint'} |
| simple_cql_types.difference_update(('set', 'map', 'list')) |
| |
| cqldocs = helptopics.CQL3HelpTopics() |
| |
| |
| class UnexpectedTableStructure(UserWarning): |
| |
| def __init__(self, msg): |
| self.msg = msg |
| |
| def __str__(self): |
| return 'Unexpected table structure; may not translate correctly to CQL. ' + self.msg |
| |
| |
| SYSTEM_KEYSPACES = ('system', 'system_schema', 'system_traces', 'system_auth', 'system_distributed', 'system_views', |
| 'system_virtual_schema') |
| NONALTERBALE_KEYSPACES = ('system', 'system_schema', 'system_views', 'system_virtual_schema') |
| |
| |
| class Cql3ParsingRuleSet(CqlParsingRuleSet): |
| |
| columnfamily_layout_options = ( |
| ('bloom_filter_fp_chance', None), |
| ('comment', None), |
| ('gc_grace_seconds', None), |
| ('min_index_interval', None), |
| ('max_index_interval', None), |
| ('default_time_to_live', None), |
| ('speculative_retry', None), |
| ('additional_write_policy', None), |
| ('memtable_flush_period_in_ms', None), |
| ('cdc', None), |
| ('read_repair', None), |
| ) |
| |
| columnfamily_layout_map_options = ( |
| # (CQL3 option name, schema_columnfamilies column name (or None if same), |
| # list of known map keys) |
| ('compaction', 'compaction_strategy_options', |
| ('class', 'max_threshold', 'tombstone_compaction_interval', 'tombstone_threshold', 'enabled', |
| 'unchecked_tombstone_compaction', 'only_purge_repaired_tombstones', 'provide_overlapping_tombstones')), |
| ('compression', 'compression_parameters', |
| ('sstable_compression', 'chunk_length_kb', 'crc_check_chance')), |
| ('caching', None, |
| ('rows_per_partition', 'keys')), |
| ) |
| |
| obsolete_cf_options = () |
| |
| consistency_levels = ( |
| 'ANY', |
| 'ONE', |
| 'TWO', |
| 'THREE', |
| 'QUORUM', |
| 'ALL', |
| 'LOCAL_QUORUM', |
| 'EACH_QUORUM', |
| 'SERIAL' |
| ) |
| |
| size_tiered_compaction_strategy_options = ( |
| 'min_sstable_size', |
| 'min_threshold', |
| 'bucket_high', |
| 'bucket_low' |
| ) |
| |
| leveled_compaction_strategy_options = ( |
| 'sstable_size_in_mb', |
| 'fanout_size' |
| ) |
| |
| date_tiered_compaction_strategy_options = ( |
| 'base_time_seconds', |
| 'max_sstable_age_days', |
| 'min_threshold', |
| 'max_window_size_seconds', |
| 'timestamp_resolution' |
| ) |
| |
| time_window_compaction_strategy_options = ( |
| 'compaction_window_unit', |
| 'compaction_window_size', |
| 'min_threshold', |
| 'timestamp_resolution' |
| ) |
| |
| @classmethod |
| def escape_value(cls, value): |
| if value is None: |
| return 'NULL' # this totally won't work |
| if isinstance(value, bool): |
| value = str(value).lower() |
| elif isinstance(value, float): |
| return '%f' % value |
| elif isinstance(value, int): |
| return str(value) |
| return "'%s'" % value.replace("'", "''") |
| |
| @classmethod |
| def escape_name(cls, name): |
| if name is None: |
| return 'NULL' |
| return "'%s'" % name.replace("'", "''") |
| |
| @staticmethod |
| def dequote_name(name): |
| name = name.strip() |
| if name == '': |
| return name |
| if name[0] == '"' and name[-1] == '"': |
| return name[1:-1].replace('""', '"') |
| else: |
| return name.lower() |
| |
| @staticmethod |
| def dequote_value(cqlword): |
| cqlword = cqlword.strip() |
| if cqlword == '': |
| return cqlword |
| if cqlword[0] == "'" and cqlword[-1] == "'": |
| cqlword = cqlword[1:-1].replace("''", "'") |
| return cqlword |
| |
| |
| CqlRuleSet = Cql3ParsingRuleSet() |
| |
| # convenience for remainder of module |
| completer_for = CqlRuleSet.completer_for |
| explain_completion = CqlRuleSet.explain_completion |
| dequote_value = CqlRuleSet.dequote_value |
| dequote_name = CqlRuleSet.dequote_name |
| escape_value = CqlRuleSet.escape_value |
| |
| # 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> ::= <quotedStringLiteral> |
| | <pgStringLiteral> ; |
| <quotedStringLiteral> ::= /'([^']|'')*'/ ; |
| <pgStringLiteral> ::= /\$\$(?:(?!\$\$).)*\$\$/; |
| <quotedName> ::= /"([^"]|"")*"/ ; |
| <float> ::= /-?[0-9]+\.[0-9]+/ ; |
| <uuid> ::= /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/ ; |
| <blobLiteral> ::= /0x[0-9a-f]+/ ; |
| <wholenumber> ::= /[0-9]+/ ; |
| <identifier> ::= /[a-z][a-z0-9_]*/ ; |
| <colon> ::= ":" ; |
| <star> ::= "*" ; |
| <endtoken> ::= ";" ; |
| <op> ::= /[-+=%/,().]/ ; |
| <cmp> ::= /[<>!]=?/ ; |
| <brackets> ::= /[][{}]/ ; |
| |
| <integer> ::= "-"? <wholenumber> ; |
| <boolean> ::= "true" |
| | "false" |
| ; |
| |
| <unclosedPgString>::= /\$\$(?:(?!\$\$).)*/ ; |
| <unclosedString> ::= /'([^']|'')*/ ; |
| <unclosedName> ::= /"([^"]|"")*/ ; |
| <unclosedComment> ::= /[/][*].*$/ ; |
| |
| <term> ::= <stringLiteral> |
| | <integer> |
| | <float> |
| | <uuid> |
| | <boolean> |
| | <blobLiteral> |
| | <collectionLiteral> |
| | <functionLiteral> <functionArguments> |
| | "NULL" |
| ; |
| |
| <functionLiteral> ::= (<identifier> ( "." <identifier> )?) |
| | "TOKEN" |
| ; |
| |
| <functionArguments> ::= "(" ( <term> ( "," <term> )* )? ")" |
| ; |
| |
| <tokenDefinition> ::= token="TOKEN" "(" <term> ( "," <term> )* ")" |
| | <term> |
| ; |
| <cident> ::= <quotedName> |
| | <identifier> |
| | <unreservedKeyword> |
| ; |
| <colname> ::= <cident> ; # just an alias |
| |
| <collectionLiteral> ::= <listLiteral> |
| | <setLiteral> |
| | <mapLiteral> |
| ; |
| <listLiteral> ::= "[" ( <term> ( "," <term> )* )? "]" |
| ; |
| <setLiteral> ::= "{" ( <term> ( "," <term> )* )? "}" |
| ; |
| <mapLiteral> ::= "{" <term> ":" <term> ( "," <term> ":" <term> )* "}" |
| ; |
| |
| <anyFunctionName> ::= ( ksname=<cfOrKsName> dot="." )? udfname=<cfOrKsName> ; |
| |
| <userFunctionName> ::= ( ksname=<nonSystemKeyspaceName> dot="." )? udfname=<cfOrKsName> ; |
| |
| <refUserFunctionName> ::= udfname=<cfOrKsName> ; |
| |
| <userAggregateName> ::= ( ksname=<nonSystemKeyspaceName> dot="." )? udaname=<cfOrKsName> ; |
| |
| <functionAggregateName> ::= ( ksname=<nonSystemKeyspaceName> dot="." )? functionname=<cfOrKsName> ; |
| |
| <aggregateName> ::= <userAggregateName> |
| ; |
| |
| <functionName> ::= <functionAggregateName> |
| | "TOKEN" |
| ; |
| |
| <statementBody> ::= <useStatement> |
| | <selectStatement> |
| | <dataChangeStatement> |
| | <schemaChangeStatement> |
| | <authenticationStatement> |
| | <authorizationStatement> |
| ; |
| |
| <dataChangeStatement> ::= <insertStatement> |
| | <updateStatement> |
| | <deleteStatement> |
| | <truncateStatement> |
| | <batchStatement> |
| ; |
| |
| <schemaChangeStatement> ::= <createKeyspaceStatement> |
| | <createColumnFamilyStatement> |
| | <createIndexStatement> |
| | <createMaterializedViewStatement> |
| | <createUserTypeStatement> |
| | <createFunctionStatement> |
| | <createAggregateStatement> |
| | <createTriggerStatement> |
| | <dropKeyspaceStatement> |
| | <dropColumnFamilyStatement> |
| | <dropIndexStatement> |
| | <dropMaterializedViewStatement> |
| | <dropUserTypeStatement> |
| | <dropFunctionStatement> |
| | <dropAggregateStatement> |
| | <dropTriggerStatement> |
| | <alterTableStatement> |
| | <alterKeyspaceStatement> |
| | <alterUserTypeStatement> |
| ; |
| |
| <authenticationStatement> ::= <createUserStatement> |
| | <alterUserStatement> |
| | <dropUserStatement> |
| | <listUsersStatement> |
| | <createRoleStatement> |
| | <alterRoleStatement> |
| | <dropRoleStatement> |
| | <listRolesStatement> |
| ; |
| |
| <authorizationStatement> ::= <grantStatement> |
| | <grantRoleStatement> |
| | <revokeStatement> |
| | <revokeRoleStatement> |
| | <listPermissionsStatement> |
| ; |
| |
| # timestamp is included here, since it's also a keyword |
| <simpleStorageType> ::= typename=( <identifier> | <stringLiteral> | "timestamp" ) ; |
| |
| <userType> ::= utname=<cfOrKsName> ; |
| |
| <storageType> ::= <simpleStorageType> | <collectionType> | <frozenCollectionType> | <userType> ; |
| |
| # Note: autocomplete for frozen collection types does not handle nesting past depth 1 properly, |
| # but that's a lot of work to fix for little benefit. |
| <collectionType> ::= "map" "<" <simpleStorageType> "," ( <simpleStorageType> | <userType> ) ">" |
| | "list" "<" ( <simpleStorageType> | <userType> ) ">" |
| | "set" "<" ( <simpleStorageType> | <userType> ) ">" |
| ; |
| |
| <frozenCollectionType> ::= "frozen" "<" "map" "<" <storageType> "," <storageType> ">" ">" |
| | "frozen" "<" "list" "<" <storageType> ">" ">" |
| | "frozen" "<" "set" "<" <storageType> ">" ">" |
| ; |
| |
| <columnFamilyName> ::= ( ksname=<cfOrKsName> dot="." )? cfname=<cfOrKsName> ; |
| |
| <materializedViewName> ::= ( ksname=<cfOrKsName> dot="." )? mvname=<cfOrKsName> ; |
| |
| <userTypeName> ::= ( ksname=<cfOrKsName> dot="." )? utname=<cfOrKsName> ; |
| |
| <keyspaceName> ::= ksname=<cfOrKsName> ; |
| |
| <nonSystemKeyspaceName> ::= ksname=<cfOrKsName> ; |
| |
| <alterableKeyspaceName> ::= ksname=<cfOrKsName> ; |
| |
| <cfOrKsName> ::= <identifier> |
| | <quotedName> |
| | <unreservedKeyword>; |
| |
| <unreservedKeyword> ::= nocomplete= |
| ( "key" |
| | "clustering" |
| # | "count" -- to get count(*) completion, treat count as reserved |
| | "ttl" |
| | "compact" |
| | "storage" |
| | "type" |
| | "values" ) |
| ; |
| |
| <property> ::= [propname]=<cident> propeq="=" [propval]=<propertyValue> |
| ; |
| <propertyValue> ::= propsimpleval=( <stringLiteral> |
| | <identifier> |
| | <integer> |
| | <float> |
| | <unreservedKeyword> ) |
| # we don't use <mapLiteral> here so we can get more targeted |
| # completions: |
| | propsimpleval="{" [propmapkey]=<term> ":" [propmapval]=<term> |
| ( ender="," [propmapkey]=<term> ":" [propmapval]=<term> )* |
| ender="}" |
| ; |
| |
| ''' |
| |
| |
| 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('property', 'propeq')(prop_equals_completer) |
| |
| |
| @completer_for('property', 'propname') |
| def prop_name_completer(ctxt, cass): |
| if working_on_keyspace(ctxt): |
| return ks_prop_name_completer(ctxt, cass) |
| elif 'MATERIALIZED' == ctxt.get_binding('wat', '').upper(): |
| props = cf_prop_name_completer(ctxt, cass) |
| props.remove('default_time_to_live') |
| props.remove('gc_grace_seconds') |
| return props |
| else: |
| return cf_prop_name_completer(ctxt, cass) |
| |
| |
| @completer_for('propertyValue', 'propsimpleval') |
| def prop_val_completer(ctxt, cass): |
| if working_on_keyspace(ctxt): |
| return ks_prop_val_completer(ctxt, cass) |
| else: |
| return cf_prop_val_completer(ctxt, cass) |
| |
| |
| @completer_for('propertyValue', 'propmapkey') |
| def prop_val_mapkey_completer(ctxt, cass): |
| if working_on_keyspace(ctxt): |
| return ks_prop_val_mapkey_completer(ctxt, cass) |
| else: |
| return cf_prop_val_mapkey_completer(ctxt, cass) |
| |
| |
| @completer_for('propertyValue', 'propmapval') |
| def prop_val_mapval_completer(ctxt, cass): |
| if working_on_keyspace(ctxt): |
| return ks_prop_val_mapval_completer(ctxt, cass) |
| else: |
| return cf_prop_val_mapval_completer(ctxt, cass) |
| |
| |
| @completer_for('propertyValue', 'ender') |
| def prop_val_mapender_completer(ctxt, cass): |
| if working_on_keyspace(ctxt): |
| return ks_prop_val_mapender_completer(ctxt, cass) |
| else: |
| return cf_prop_val_mapender_completer(ctxt, cass) |
| |
| |
| def ks_prop_name_completer(ctxt, cass): |
| optsseen = ctxt.get_binding('propname', ()) |
| if 'replication' not in optsseen: |
| return ['replication'] |
| return ["durable_writes"] |
| |
| |
| def ks_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_prop_val_mapkey_completer(ctxt, cass): |
| optname = ctxt.get_binding('propname')[-1] |
| if optname != 'replication': |
| return () |
| keysseen = list(map(dequote_value, ctxt.get_binding('propmapkey', ()))) |
| valsseen = list(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 == 'SimpleStrategy': |
| opts = {'replication_factor'} |
| elif repclass == 'NetworkTopologyStrategy': |
| return [Hint('<dc_name>')] |
| return list(map(escape_value, opts.difference(keysseen))) |
| |
| |
| def ks_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 list(map(escape_value, CqlRuleSet.replication_strategies)) |
| return [Hint('<term>')] |
| |
| |
| def ks_prop_val_mapender_completer(ctxt, cass): |
| optname = ctxt.get_binding('propname')[-1] |
| if optname != 'replication': |
| return [','] |
| keysseen = list(map(dequote_value, ctxt.get_binding('propmapkey', ()))) |
| valsseen = list(map(dequote_value, ctxt.get_binding('propmapval', ()))) |
| for k, v in zip(keysseen, valsseen): |
| if k == 'class': |
| repclass = v |
| break |
| else: |
| return [','] |
| if repclass == 'SimpleStrategy': |
| if 'replication_factor' not in keysseen: |
| return [','] |
| if repclass == 'NetworkTopologyStrategy' and len(keysseen) == 1: |
| return [','] |
| return ['}'] |
| |
| |
| def cf_prop_name_completer(ctxt, cass): |
| return [c[0] for c in (CqlRuleSet.columnfamily_layout_options |
| + CqlRuleSet.columnfamily_layout_map_options)] |
| |
| |
| def cf_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 this_opt == 'caching': |
| return ["{'keys': '"] |
| if any(this_opt == opt[0] for opt in CqlRuleSet.obsolete_cf_options): |
| return ["'<obsolete_option>'"] |
| if this_opt == 'bloom_filter_fp_chance': |
| return [Hint('<float_between_0_and_1>')] |
| if this_opt in ('min_compaction_threshold', 'max_compaction_threshold', |
| 'gc_grace_seconds', 'min_index_interval', 'max_index_interval'): |
| return [Hint('<integer>')] |
| if this_opt in ('cdc'): |
| return [Hint('<true|false>')] |
| if this_opt in ('read_repair'): |
| return [Hint('<\'none\'|\'blocking\'>')] |
| return [Hint('<option_value>')] |
| |
| |
| def cf_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 = list(map(dequote_value, ctxt.get_binding('propmapkey', ()))) |
| valsseen = list(map(dequote_value, ctxt.get_binding('propmapval', ()))) |
| pairsseen = dict(list(zip(keysseen, valsseen))) |
| if optname == 'compression': |
| return list(map(escape_value, set(subopts).difference(keysseen))) |
| if optname == 'caching': |
| return list(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 = opts.union(set(CqlRuleSet.size_tiered_compaction_strategy_options)) |
| elif csc == 'LeveledCompactionStrategy': |
| opts = opts.union(set(CqlRuleSet.leveled_compaction_strategy_options)) |
| elif csc == 'DateTieredCompactionStrategy': |
| opts = opts.union(set(CqlRuleSet.date_tiered_compaction_strategy_options)) |
| elif csc == 'TimeWindowCompactionStrategy': |
| opts = opts.union(set(CqlRuleSet.time_window_compaction_strategy_options)) |
| |
| return list(map(escape_value, opts)) |
| return () |
| |
| |
| def cf_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 list(map(escape_value, CqlRuleSet.available_compaction_classes)) |
| if key == 'provide_overlapping_tombstones': |
| return [Hint('<NONE|ROW|CELL>')] |
| return [Hint('<option_value>')] |
| elif opt == 'compression': |
| if key == 'sstable_compression': |
| return list(map(escape_value, CqlRuleSet.available_compression_classes)) |
| return [Hint('<option_value>')] |
| elif opt == 'caching': |
| if key == 'rows_per_partition': |
| return ["'ALL'", "'NONE'", Hint('#rows_per_partition')] |
| elif key == 'keys': |
| return ["'ALL'", "'NONE'"] |
| return () |
| |
| |
| def cf_prop_val_mapender_completer(ctxt, cass): |
| return [',', '}'] |
| |
| |
| @completer_for('tokenDefinition', 'token') |
| def token_word_completer(ctxt, cass): |
| return ['token('] |
| |
| |
| @completer_for('simpleStorageType', 'typename') |
| def storagetype_completer(ctxt, cass): |
| return simple_cql_types |
| |
| |
| @completer_for('keyspaceName', 'ksname') |
| def ks_name_completer(ctxt, cass): |
| return list(map(maybe_escape_name, cass.get_keyspace_names())) |
| |
| |
| @completer_for('nonSystemKeyspaceName', 'ksname') |
| def non_system_ks_name_completer(ctxt, cass): |
| ksnames = [n for n in cass.get_keyspace_names() if n not in SYSTEM_KEYSPACES] |
| return list(map(maybe_escape_name, ksnames)) |
| |
| |
| @completer_for('alterableKeyspaceName', 'ksname') |
| def alterable_ks_name_completer(ctxt, cass): |
| ksnames = [n for n in cass.get_keyspace_names() if n not in NONALTERBALE_KEYSPACES] |
| return list(map(maybe_escape_name, ksnames)) |
| |
| |
| def cf_ks_name_completer(ctxt, cass): |
| return [maybe_escape_name(ks) + '.' for ks in cass.get_keyspace_names()] |
| |
| |
| completer_for('columnFamilyName', 'ksname')(cf_ks_name_completer) |
| completer_for('materializedViewName', 'ksname')(cf_ks_name_completer) |
| |
| |
| 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', 'dot')(cf_ks_dot_completer) |
| completer_for('materializedViewName', 'dot')(cf_ks_dot_completer) |
| |
| |
| @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 list(map(maybe_escape_name, cfnames)) |
| |
| |
| @completer_for('materializedViewName', 'mvname') |
| def mv_name_completer(ctxt, cass): |
| ks = ctxt.get_binding('ksname', None) |
| if ks is not None: |
| ks = dequote_name(ks) |
| try: |
| mvnames = cass.get_materialized_view_names(ks) |
| except Exception: |
| if ks is None: |
| return () |
| raise |
| return list(map(maybe_escape_name, mvnames)) |
| |
| |
| completer_for('userTypeName', 'ksname')(cf_ks_name_completer) |
| |
| completer_for('userTypeName', 'dot')(cf_ks_dot_completer) |
| |
| |
| def ut_name_completer(ctxt, cass): |
| ks = ctxt.get_binding('ksname', None) |
| if ks is not None: |
| ks = dequote_name(ks) |
| try: |
| utnames = cass.get_usertype_names(ks) |
| except Exception: |
| if ks is None: |
| return () |
| raise |
| return list(map(maybe_escape_name, utnames)) |
| |
| |
| completer_for('userTypeName', 'utname')(ut_name_completer) |
| completer_for('userType', 'utname')(ut_name_completer) |
| |
| |
| @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_table_meta(ctxt, cass): |
| 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_table_meta(ks, cf) |
| |
| |
| def get_ut_layout(ctxt, cass): |
| ks = ctxt.get_binding('ksname', None) |
| if ks is not None: |
| ks = dequote_name(ks) |
| ut = dequote_name(ctxt.get_binding('utname')) |
| return cass.get_usertype_layout(ks, ut) |
| |
| |
| def working_on_keyspace(ctxt): |
| wat = ctxt.get_binding('wat', '').upper() |
| if wat in ('KEYSPACE', 'SCHEMA'): |
| return True |
| return False |
| |
| |
| syntax_rules += r''' |
| <useStatement> ::= "USE" <keyspaceName> |
| ; |
| <selectStatement> ::= "SELECT" ( "JSON" )? <selectClause> |
| "FROM" (cf=<columnFamilyName> | mv=<materializedViewName>) |
| ( "WHERE" <whereClause> )? |
| ( "GROUP" "BY" <groupByClause> ( "," <groupByClause> )* )? |
| ( "ORDER" "BY" <orderByClause> ( "," <orderByClause> )* )? |
| ( "PER" "PARTITION" "LIMIT" perPartitionLimit=<wholenumber> )? |
| ( "LIMIT" limit=<wholenumber> )? |
| ( "ALLOW" "FILTERING" )? |
| ; |
| <whereClause> ::= <relation> ( "AND" <relation> )* |
| ; |
| <relation> ::= [rel_lhs]=<cident> ( "[" <term> "]" )? ( "=" | "<" | ">" | "<=" | ">=" | "CONTAINS" ( "KEY" )? ) <term> |
| | token="TOKEN" "(" [rel_tokname]=<cident> |
| ( "," [rel_tokname]=<cident> )* |
| ")" ("=" | "<" | ">" | "<=" | ">=") <tokenDefinition> |
| | [rel_lhs]=<cident> "IN" "(" <term> ( "," <term> )* ")" |
| ; |
| <selectClause> ::= "DISTINCT"? <selector> ("AS" <cident>)? ("," <selector> ("AS" <cident>)?)* |
| | "*" |
| ; |
| <udtSubfieldSelection> ::= <identifier> "." <identifier> |
| ; |
| <selector> ::= [colname]=<cident> ( "[" ( <term> ( ".." <term> "]" )? | <term> ".." ) )? |
| | <udtSubfieldSelection> |
| | "WRITETIME" "(" [colname]=<cident> ")" |
| | "TTL" "(" [colname]=<cident> ")" |
| | "COUNT" "(" star=( "*" | "1" ) ")" |
| | "CAST" "(" <selector> "AS" <storageType> ")" |
| | <functionName> <selectionFunctionArguments> |
| | <term> |
| ; |
| <selectionFunctionArguments> ::= "(" ( <selector> ( "," <selector> )* )? ")" |
| ; |
| <orderByClause> ::= [ordercol]=<cident> ( "ASC" | "DESC" )? |
| ; |
| <groupByClause> ::= [groupcol]=<cident> |
| | <functionName><groupByFunctionArguments> |
| ; |
| <groupByFunctionArguments> ::= "(" ( <groupByFunctionArgument> ( "," <groupByFunctionArgument> )* )? ")" |
| ; |
| <groupByFunctionArgument> ::= [groupcol]=<cident> |
| | <term> |
| ; |
| ''' |
| |
| |
| def udf_name_completer(ctxt, cass): |
| ks = ctxt.get_binding('ksname', None) |
| if ks is not None: |
| ks = dequote_name(ks) |
| try: |
| udfnames = cass.get_userfunction_names(ks) |
| except Exception: |
| if ks is None: |
| return () |
| raise |
| return list(map(maybe_escape_name, udfnames)) |
| |
| |
| def uda_name_completer(ctxt, cass): |
| ks = ctxt.get_binding('ksname', None) |
| if ks is not None: |
| ks = dequote_name(ks) |
| try: |
| udanames = cass.get_useraggregate_names(ks) |
| except Exception: |
| if ks is None: |
| return () |
| raise |
| return list(map(maybe_escape_name, udanames)) |
| |
| |
| def udf_uda_name_completer(ctxt, cass): |
| ks = ctxt.get_binding('ksname', None) |
| if ks is not None: |
| ks = dequote_name(ks) |
| try: |
| functionnames = cass.get_userfunction_names(ks) + cass.get_useraggregate_names(ks) |
| except Exception: |
| if ks is None: |
| return () |
| raise |
| return list(map(maybe_escape_name, functionnames)) |
| |
| |
| def ref_udf_name_completer(ctxt, cass): |
| try: |
| udanames = cass.get_userfunction_names(None) |
| except Exception: |
| return () |
| return list(map(maybe_escape_name, udanames)) |
| |
| |
| completer_for('functionAggregateName', 'ksname')(cf_ks_name_completer) |
| completer_for('functionAggregateName', 'dot')(cf_ks_dot_completer) |
| completer_for('functionAggregateName', 'functionname')(udf_uda_name_completer) |
| completer_for('anyFunctionName', 'ksname')(cf_ks_name_completer) |
| completer_for('anyFunctionName', 'dot')(cf_ks_dot_completer) |
| completer_for('anyFunctionName', 'udfname')(udf_name_completer) |
| completer_for('userFunctionName', 'ksname')(cf_ks_name_completer) |
| completer_for('userFunctionName', 'dot')(cf_ks_dot_completer) |
| completer_for('userFunctionName', 'udfname')(udf_name_completer) |
| completer_for('refUserFunctionName', 'udfname')(ref_udf_name_completer) |
| completer_for('userAggregateName', 'ksname')(cf_ks_name_completer) |
| completer_for('userAggregateName', 'dot')(cf_ks_dot_completer) |
| completer_for('userAggregateName', 'udaname')(uda_name_completer) |
| |
| |
| @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_table_meta(ctxt, cass) |
| order_by_candidates = [col.name for col in layout.clustering_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('groupByClause', 'groupcol') |
| def select_group_column_completer(ctxt, cass): |
| prev_group_cols = ctxt.get_binding('groupcol', ()) |
| layout = get_table_meta(ctxt, cass) |
| group_by_candidates = [col.name for col in layout.primary_key] |
| if len(group_by_candidates) > len(prev_group_cols): |
| return [maybe_escape_name(group_by_candidates[len(prev_group_cols)])] |
| return [Hint('No more 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_table_meta(ctxt, cass) |
| return [key.name for key in layout.partition_key] |
| |
| |
| @completer_for('relation', 'rel_lhs') |
| def select_relation_lhs_completer(ctxt, cass): |
| layout = get_table_meta(ctxt, cass) |
| filterable = set() |
| already_filtered_on = list(map(dequote_name, ctxt.get_binding('rel_lhs', ()))) |
| for num in range(0, len(layout.partition_key)): |
| if num == 0 or layout.partition_key[num - 1].name in already_filtered_on: |
| filterable.add(layout.partition_key[num].name) |
| else: |
| break |
| for num in range(0, len(layout.clustering_key)): |
| if num == 0 or layout.clustering_key[num - 1].name in already_filtered_on: |
| filterable.add(layout.clustering_key[num].name) |
| else: |
| break |
| for idx in layout.indexes.values(): |
| filterable.add(idx.index_options["target"]) |
| return list(map(maybe_escape_name, filterable)) |
| |
| |
| explain_completion('selector', 'colname') |
| |
| syntax_rules += r''' |
| <insertStatement> ::= "INSERT" "INTO" cf=<columnFamilyName> |
| ( ( "(" [colname]=<cident> ( "," [colname]=<cident> )* ")" |
| "VALUES" "(" [newval]=<term> ( valcomma="," [newval]=<term> )* valcomma=")") |
| | ("JSON" <stringLiteral>)) |
| ( "IF" "NOT" "EXISTS")? |
| ( "USING" [insertopt]=<usingOption> |
| ( "AND" [insertopt]=<usingOption> )* )? |
| ; |
| <usingOption> ::= "TIMESTAMP" <wholenumber> |
| | "TTL" <wholenumber> |
| ; |
| ''' |
| |
| |
| def regular_column_names(table_meta): |
| if not table_meta or not table_meta.columns: |
| return [] |
| regular_columns = list(set(table_meta.columns.keys()) |
| - set([key.name for key in table_meta.partition_key]) |
| - set([key.name for key in table_meta.clustering_key])) |
| return regular_columns |
| |
| |
| @completer_for('insertStatement', 'colname') |
| def insert_colname_completer(ctxt, cass): |
| layout = get_table_meta(ctxt, cass) |
| colnames = set(map(dequote_name, ctxt.get_binding('colname', ()))) |
| keycols = layout.primary_key |
| for k in keycols: |
| if k.name not in colnames: |
| return [maybe_escape_name(k.name)] |
| normalcols = set(regular_column_names(layout)) - colnames |
| return list(map(maybe_escape_name, normalcols)) |
| |
| |
| @completer_for('insertStatement', 'newval') |
| def insert_newval_completer(ctxt, cass): |
| layout = get_table_meta(ctxt, cass) |
| insertcols = list(map(dequote_name, ctxt.get_binding('colname'))) |
| valuesdone = ctxt.get_binding('newval', ()) |
| if len(valuesdone) >= len(insertcols): |
| return [] |
| curcol = insertcols[len(valuesdone)] |
| coltype = layout.columns[curcol].cql_type |
| if coltype in ('map', 'set'): |
| return ['{'] |
| if coltype == 'list': |
| return ['['] |
| if coltype == 'boolean': |
| return ['true', 'false'] |
| |
| return [Hint('<value for %s (%s)>' % (maybe_escape_name(curcol), |
| coltype))] |
| |
| |
| @completer_for('insertStatement', 'valcomma') |
| def insert_valcomma_completer(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): |
| opts = set('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> |
| ( "IF" ( "EXISTS" | <conditions> ))? |
| ; |
| <assignment> ::= updatecol=<cident> |
| (( "=" update_rhs=( <term> | <cident> ) |
| ( counterop=( "+" | "-" ) inc=<wholenumber> |
| | listadder="+" listcol=<cident> )? ) |
| | ( indexbracket="[" <term> "]" "=" <term> ) |
| | ( udt_field_dot="." udt_field=<identifier> "=" <term> )) |
| ; |
| <conditions> ::= <condition> ( "AND" <condition> )* |
| ; |
| <condition_op_and_rhs> ::= (("=" | "<" | ">" | "<=" | ">=" | "!=" | "CONTAINS" ( "KEY" )? ) <term>) |
| | ("IN" "(" <term> ( "," <term> )* ")" ) |
| ; |
| <condition> ::= conditioncol=<cident> |
| ( (( indexbracket="[" <term> "]" ) |
| |( udt_field_dot="." udt_field=<identifier> )) )? |
| <condition_op_and_rhs> |
| ; |
| ''' |
| |
| |
| @completer_for('updateStatement', 'updateopt') |
| def update_option_completer(ctxt, cass): |
| opts = set('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_table_meta(ctxt, cass) |
| return list(map(maybe_escape_name, regular_column_names(layout))) |
| |
| |
| @completer_for('assignment', 'update_rhs') |
| def update_countername_completer(ctxt, cass): |
| layout = get_table_meta(ctxt, cass) |
| curcol = dequote_name(ctxt.get_binding('updatecol', '')) |
| coltype = layout.columns[curcol].cql_type |
| if coltype == 'counter': |
| return [maybe_escape_name(curcol)] |
| if coltype in ('map', 'set'): |
| return ["{"] |
| if coltype == 'list': |
| return ["["] |
| return [Hint('<term (%s)>' % coltype)] |
| |
| |
| @completer_for('assignment', 'counterop') |
| def update_counterop_completer(ctxt, cass): |
| layout = get_table_meta(ctxt, cass) |
| curcol = dequote_name(ctxt.get_binding('updatecol', '')) |
| return ['+', '-'] if layout.columns[curcol].cql_type == 'counter' else [] |
| |
| |
| @completer_for('assignment', 'inc') |
| def update_counter_inc_completer(ctxt, cass): |
| layout = get_table_meta(ctxt, cass) |
| curcol = dequote_name(ctxt.get_binding('updatecol', '')) |
| if layout.columns[curcol].cql_type == 'counter': |
| return [Hint('<wholenumber>')] |
| return [] |
| |
| |
| @completer_for('assignment', 'listadder') |
| def update_listadder_completer(ctxt, cass): |
| rhs = ctxt.get_binding('update_rhs') |
| if rhs.startswith('['): |
| return ['+'] |
| 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_table_meta(ctxt, cass) |
| curcol = dequote_name(ctxt.get_binding('updatecol', '')) |
| coltype = layout.columns[curcol].cql_type |
| if coltype in ('map', 'list'): |
| return ['['] |
| return [] |
| |
| |
| @completer_for('assignment', 'udt_field_dot') |
| def update_udt_field_dot_completer(ctxt, cass): |
| layout = get_table_meta(ctxt, cass) |
| curcol = dequote_name(ctxt.get_binding('updatecol', '')) |
| return ["."] if _is_usertype(layout, curcol) else [] |
| |
| |
| @completer_for('assignment', 'udt_field') |
| def assignment_udt_field_completer(ctxt, cass): |
| layout = get_table_meta(ctxt, cass) |
| curcol = dequote_name(ctxt.get_binding('updatecol', '')) |
| return _usertype_fields(ctxt, cass, layout, curcol) |
| |
| |
| def _is_usertype(layout, curcol): |
| coltype = layout.columns[curcol].cql_type |
| return coltype not in simple_cql_types and coltype not in ('map', 'set', 'list') |
| |
| |
| def _usertype_fields(ctxt, cass, layout, curcol): |
| if not _is_usertype(layout, curcol): |
| return [] |
| |
| coltype = layout.columns[curcol].cql_type |
| ks = ctxt.get_binding('ksname', None) |
| if ks is not None: |
| ks = dequote_name(ks) |
| user_type = cass.get_usertype_layout(ks, coltype) |
| return [field_name for (field_name, field_type) in user_type] |
| |
| |
| @completer_for('condition', 'indexbracket') |
| def condition_indexbracket_completer(ctxt, cass): |
| layout = get_table_meta(ctxt, cass) |
| curcol = dequote_name(ctxt.get_binding('conditioncol', '')) |
| coltype = layout.columns[curcol].cql_type |
| if coltype in ('map', 'list'): |
| return ['['] |
| return [] |
| |
| |
| @completer_for('condition', 'udt_field_dot') |
| def condition_udt_field_dot_completer(ctxt, cass): |
| layout = get_table_meta(ctxt, cass) |
| curcol = dequote_name(ctxt.get_binding('conditioncol', '')) |
| return ["."] if _is_usertype(layout, curcol) else [] |
| |
| |
| @completer_for('condition', 'udt_field') |
| def condition_udt_field_completer(ctxt, cass): |
| layout = get_table_meta(ctxt, cass) |
| curcol = dequote_name(ctxt.get_binding('conditioncol', '')) |
| return _usertype_fields(ctxt, cass, layout, curcol) |
| |
| |
| syntax_rules += r''' |
| <deleteStatement> ::= "DELETE" ( <deleteSelector> ( "," <deleteSelector> )* )? |
| "FROM" cf=<columnFamilyName> |
| ( "USING" [delopt]=<deleteOption> )? |
| "WHERE" <whereClause> |
| ( "IF" ( "EXISTS" | <conditions> ) )? |
| ; |
| <deleteSelector> ::= delcol=<cident> |
| ( ( "[" <term> "]" ) |
| | ( "." <identifier> ) )? |
| ; |
| <deleteOption> ::= "TIMESTAMP" <wholenumber> |
| ; |
| ''' |
| |
| |
| @completer_for('deleteStatement', 'delopt') |
| def delete_opt_completer(ctxt, cass): |
| opts = set('TIMESTAMP'.split()) |
| for opt in ctxt.get_binding('delopt', ()): |
| opts.discard(opt.split()[0]) |
| return opts |
| |
| |
| @completer_for('deleteSelector', 'delcol') |
| def delete_delcol_completer(ctxt, cass): |
| layout = get_table_meta(ctxt, cass) |
| return list(map(maybe_escape_name, regular_column_names(layout))) |
| |
| |
| syntax_rules += r''' |
| <batchStatement> ::= "BEGIN" ( "UNLOGGED" | "COUNTER" )? "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('TIMESTAMP'.split()) |
| for opt in ctxt.get_binding('batchopt', ()): |
| opts.discard(opt.split()[0]) |
| return opts |
| |
| |
| syntax_rules += r''' |
| <truncateStatement> ::= "TRUNCATE" ("COLUMNFAMILY" | "TABLE")? cf=<columnFamilyName> |
| ; |
| ''' |
| |
| syntax_rules += r''' |
| <createKeyspaceStatement> ::= "CREATE" wat=( "KEYSPACE" | "SCHEMA" ) ("IF" "NOT" "EXISTS")? ksname=<cfOrKsName> |
| "WITH" <property> ( "AND" <property> )* |
| ; |
| ''' |
| |
| |
| @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'] |
| |
| |
| syntax_rules += r''' |
| <createColumnFamilyStatement> ::= "CREATE" wat=( "COLUMNFAMILY" | "TABLE" ) ("IF" "NOT" "EXISTS")? |
| ( ks=<nonSystemKeyspaceName> dot="." )? cf=<cfOrKsName> |
| "(" ( <singleKeyCfSpec> | <compositeKeyCfSpec> ) ")" |
| ( "WITH" <cfamProperty> ( "AND" <cfamProperty> )* )? |
| ; |
| |
| <cfamProperty> ::= <property> |
| | "COMPACT" "STORAGE" "CDC" |
| | "CLUSTERING" "ORDER" "BY" "(" <cfamOrdering> |
| ( "," <cfamOrdering> )* ")" |
| ; |
| |
| <cfamOrdering> ::= [ordercol]=<cident> ( "ASC" | "DESC" ) |
| ; |
| |
| <singleKeyCfSpec> ::= [newcolname]=<cident> <storageType> "PRIMARY" "KEY" |
| ( "," [newcolname]=<cident> <storageType> )* |
| ; |
| |
| <compositeKeyCfSpec> ::= [newcolname]=<cident> <storageType> |
| "," [newcolname]=<cident> <storageType> ( "static" )? |
| ( "," [newcolname]=<cident> <storageType> ( "static" )? )* |
| "," "PRIMARY" k="KEY" p="(" ( partkey=<pkDef> | [pkey]=<cident> ) |
| ( c="," [pkey]=<cident> )* ")" |
| ; |
| |
| <pkDef> ::= "(" [ptkey]=<cident> "," [ptkey]=<cident> |
| ( "," [ptkey]=<cident> )* ")" |
| ; |
| ''' |
| |
| |
| @completer_for('cfamOrdering', 'ordercol') |
| def create_cf_clustering_order_colname_completer(ctxt, cass): |
| colnames = list(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', '<new_table_name>') |
| explain_completion('compositeKeyCfSpec', 'newcolname', '<new_column_name>') |
| |
| |
| @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('ptkey', ()) |
| pieces_already = list(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 = list(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', '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 [','] |
| |
| |
| syntax_rules += r''' |
| |
| <idxName> ::= <identifier> |
| | <quotedName> |
| | <unreservedKeyword>; |
| |
| <createIndexStatement> ::= "CREATE" "CUSTOM"? "INDEX" ("IF" "NOT" "EXISTS")? indexname=<idxName>? "ON" |
| cf=<columnFamilyName> "(" ( |
| col=<cident> | |
| "keys(" col=<cident> ")" | |
| "full(" col=<cident> ")" |
| ) ")" |
| ( "USING" <stringLiteral> ( "WITH" "OPTIONS" "=" <mapLiteral> )? )? |
| ; |
| |
| |
| <colList> ::= "(" <cident> ( "," <cident> )* ")" |
| ; |
| |
| <createMaterializedViewStatement> ::= "CREATE" wat="MATERIALIZED" "VIEW" ("IF" "NOT" "EXISTS")? viewname=<materializedViewName>? |
| "AS" "SELECT" <selectClause> |
| "FROM" cf=<columnFamilyName> |
| "WHERE" <cident> "IS" "NOT" "NULL" ( "AND" <cident> "IS" "NOT" "NULL")* |
| "PRIMARY" "KEY" (<colList> | ( "(" <colList> ( "," <cident> )* ")" )) |
| ( "WITH" <cfamProperty> ( "AND" <cfamProperty> )* )? |
| ; |
| |
| <createUserTypeStatement> ::= "CREATE" "TYPE" ( ks=<nonSystemKeyspaceName> dot="." )? typename=<cfOrKsName> "(" newcol=<cident> <storageType> |
| ( "," [newcolname]=<cident> <storageType> )* |
| ")" |
| ; |
| |
| <createFunctionStatement> ::= "CREATE" ("OR" "REPLACE")? "FUNCTION" |
| ("IF" "NOT" "EXISTS")? |
| <userFunctionName> |
| ( "(" ( newcol=<cident> <storageType> |
| ( "," [newcolname]=<cident> <storageType> )* )? |
| ")" )? |
| ("RETURNS" "NULL" | "CALLED") "ON" "NULL" "INPUT" |
| "RETURNS" <storageType> |
| "LANGUAGE" <cident> "AS" <stringLiteral> |
| ; |
| |
| <createAggregateStatement> ::= "CREATE" ("OR" "REPLACE")? "AGGREGATE" |
| ("IF" "NOT" "EXISTS")? |
| <userAggregateName> |
| ( "(" |
| ( <storageType> ( "," <storageType> )* )? |
| ")" )? |
| "SFUNC" <refUserFunctionName> |
| "STYPE" <storageType> |
| ( "FINALFUNC" <refUserFunctionName> )? |
| ( "INITCOND" <term> )? |
| ; |
| |
| ''' |
| |
| explain_completion('createIndexStatement', 'indexname', '<new_index_name>') |
| explain_completion('createMaterializedViewStatement', 'viewname', '<new_view_name>') |
| explain_completion('createUserTypeStatement', 'typename', '<new_type_name>') |
| explain_completion('createUserTypeStatement', 'newcol', '<new_field_name>') |
| |
| |
| @completer_for('createIndexStatement', 'col') |
| def create_index_col_completer(ctxt, cass): |
| """ Return the columns for which an index doesn't exist yet. """ |
| layout = get_table_meta(ctxt, cass) |
| idx_targets = [idx.index_options["target"] for idx in layout.indexes.values()] |
| colnames = [cd.name for cd in list(layout.columns.values()) if cd.name not in idx_targets] |
| return list(map(maybe_escape_name, colnames)) |
| |
| |
| syntax_rules += r''' |
| <dropKeyspaceStatement> ::= "DROP" "KEYSPACE" ("IF" "EXISTS")? ksname=<nonSystemKeyspaceName> |
| ; |
| |
| <dropColumnFamilyStatement> ::= "DROP" ( "COLUMNFAMILY" | "TABLE" ) ("IF" "EXISTS")? cf=<columnFamilyName> |
| ; |
| |
| <indexName> ::= ( ksname=<idxOrKsName> dot="." )? idxname=<idxOrKsName> ; |
| |
| <idxOrKsName> ::= <identifier> |
| | <quotedName> |
| | <unreservedKeyword>; |
| |
| <dropIndexStatement> ::= "DROP" "INDEX" ("IF" "EXISTS")? idx=<indexName> |
| ; |
| |
| <dropMaterializedViewStatement> ::= "DROP" "MATERIALIZED" "VIEW" ("IF" "EXISTS")? mv=<materializedViewName> |
| ; |
| |
| <dropUserTypeStatement> ::= "DROP" "TYPE" ut=<userTypeName> |
| ; |
| |
| <dropFunctionStatement> ::= "DROP" "FUNCTION" ( "IF" "EXISTS" )? <userFunctionName> |
| ; |
| |
| <dropAggregateStatement> ::= "DROP" "AGGREGATE" ( "IF" "EXISTS" )? <userAggregateName> |
| ; |
| |
| ''' |
| |
| |
| @completer_for('indexName', 'ksname') |
| def idx_ks_name_completer(ctxt, cass): |
| return [maybe_escape_name(ks) + '.' for ks in cass.get_keyspace_names()] |
| |
| |
| @completer_for('indexName', 'dot') |
| def idx_ks_dot_completer(ctxt, cass): |
| name = dequote_name(ctxt.get_binding('ksname')) |
| if name in cass.get_keyspace_names(): |
| return ['.'] |
| return [] |
| |
| |
| @completer_for('indexName', 'idxname') |
| def idx_ks_idx_name_completer(ctxt, cass): |
| ks = ctxt.get_binding('ksname', None) |
| if ks is not None: |
| ks = dequote_name(ks) |
| try: |
| idxnames = cass.get_index_names(ks) |
| except Exception: |
| if ks is None: |
| return () |
| raise |
| return list(map(maybe_escape_name, idxnames)) |
| |
| |
| syntax_rules += r''' |
| <alterTableStatement> ::= "ALTER" wat=( "COLUMNFAMILY" | "TABLE" ) ("IF" "EXISTS")? cf=<columnFamilyName> |
| <alterInstructions> |
| ; |
| <alterInstructions> ::= "ADD" ("IF" "NOT" "EXISTS")? newcol=<cident> <storageType> ("static")? |
| | "DROP" ("IF" "EXISTS")? existcol=<cident> |
| | "WITH" <cfamProperty> ( "AND" <cfamProperty> )* |
| | "RENAME" ("IF" "EXISTS")? existcol=<cident> "TO" newcol=<cident> |
| ( "AND" existcol=<cident> "TO" newcol=<cident> )* |
| ; |
| |
| <alterUserTypeStatement> ::= "ALTER" "TYPE" ("IF" "EXISTS")? ut=<userTypeName> |
| <alterTypeInstructions> |
| ; |
| <alterTypeInstructions> ::= "ADD" ("IF" "NOT" "EXISTS")? newcol=<cident> <storageType> |
| | "RENAME" ("IF" "EXISTS")? existcol=<cident> "TO" newcol=<cident> |
| ( "AND" existcol=<cident> "TO" newcol=<cident> )* |
| ; |
| ''' |
| |
| |
| @completer_for('alterInstructions', 'existcol') |
| def alter_table_col_completer(ctxt, cass): |
| layout = get_table_meta(ctxt, cass) |
| cols = [str(md) for md in layout.columns] |
| return list(map(maybe_escape_name, cols)) |
| |
| |
| @completer_for('alterTypeInstructions', 'existcol') |
| def alter_type_field_completer(ctxt, cass): |
| layout = get_ut_layout(ctxt, cass) |
| fields = [atuple[0] for atuple in layout] |
| return list(map(maybe_escape_name, fields)) |
| |
| |
| explain_completion('alterInstructions', 'newcol', '<new_column_name>') |
| explain_completion('alterTypeInstructions', 'newcol', '<new_field_name>') |
| |
| |
| syntax_rules += r''' |
| <alterKeyspaceStatement> ::= "ALTER" wat=( "KEYSPACE" | "SCHEMA" ) ("IF" "EXISTS")? ks=<alterableKeyspaceName> |
| "WITH" <property> ( "AND" <property> )* |
| ; |
| ''' |
| |
| syntax_rules += r''' |
| <username> ::= name=( <identifier> | <stringLiteral> ) |
| ; |
| |
| <createUserStatement> ::= "CREATE" "USER" ( "IF" "NOT" "EXISTS" )? <username> |
| ( "WITH" ("HASHED")? "PASSWORD" <stringLiteral> )? |
| ( "SUPERUSER" | "NOSUPERUSER" )? |
| ; |
| |
| <alterUserStatement> ::= "ALTER" "USER" ("IF" "EXISTS")? <username> |
| ( "WITH" "PASSWORD" <stringLiteral> )? |
| ( "SUPERUSER" | "NOSUPERUSER" )? |
| ; |
| |
| <dropUserStatement> ::= "DROP" "USER" ( "IF" "EXISTS" )? <username> |
| ; |
| |
| <listUsersStatement> ::= "LIST" "USERS" |
| ; |
| ''' |
| |
| syntax_rules += r''' |
| <rolename> ::= <identifier> |
| | <quotedName> |
| | <unreservedKeyword> |
| ; |
| |
| <createRoleStatement> ::= "CREATE" "ROLE" <rolename> |
| ( "WITH" <roleProperty> ("AND" <roleProperty>)*)? |
| ; |
| |
| <alterRoleStatement> ::= "ALTER" "ROLE" ("IF" "EXISTS")? <rolename> |
| ( "WITH" <roleProperty> ("AND" <roleProperty>)*)? |
| ; |
| |
| <roleProperty> ::= (("HASHED")? "PASSWORD") "=" <stringLiteral> |
| | "OPTIONS" "=" <mapLiteral> |
| | "SUPERUSER" "=" <boolean> |
| | "LOGIN" "=" <boolean> |
| | "ACCESS" "TO" "DATACENTERS" <setLiteral> |
| | "ACCESS" "TO" "ALL" "DATACENTERS" |
| ; |
| |
| <dropRoleStatement> ::= "DROP" "ROLE" <rolename> |
| ; |
| |
| <grantRoleStatement> ::= "GRANT" <rolename> "TO" <rolename> |
| ; |
| |
| <revokeRoleStatement> ::= "REVOKE" <rolename> "FROM" <rolename> |
| ; |
| |
| <listRolesStatement> ::= "LIST" "ROLES" |
| ( "OF" <rolename> )? "NORECURSIVE"? |
| ; |
| ''' |
| |
| syntax_rules += r''' |
| <grantStatement> ::= "GRANT" <permissionExpr> "ON" <resource> "TO" <rolename> |
| ; |
| |
| <revokeStatement> ::= "REVOKE" <permissionExpr> "ON" <resource> "FROM" <rolename> |
| ; |
| |
| <listPermissionsStatement> ::= "LIST" <permissionExpr> |
| ( "ON" <resource> )? ( "OF" <rolename> )? "NORECURSIVE"? |
| ; |
| |
| <permission> ::= "AUTHORIZE" |
| | "CREATE" |
| | "ALTER" |
| | "DROP" |
| | "SELECT" |
| | "MODIFY" |
| | "DESCRIBE" |
| | "EXECUTE" |
| ; |
| |
| <permissionExpr> ::= ( [newpermission]=<permission> "PERMISSION"? ( "," [newpermission]=<permission> "PERMISSION"? )* ) |
| | ( "ALL" "PERMISSIONS"? ) |
| ; |
| |
| <resource> ::= <dataResource> |
| | <roleResource> |
| | <functionResource> |
| | <jmxResource> |
| ; |
| |
| <dataResource> ::= ( "ALL" "KEYSPACES" ) |
| | ( "KEYSPACE" <keyspaceName> ) |
| | ( "ALL" "TABLES" "IN" "KEYSPACE" <keyspaceName> ) |
| | ( "TABLE"? <columnFamilyName> ) |
| ; |
| |
| <roleResource> ::= ("ALL" "ROLES") |
| | ("ROLE" <rolename>) |
| ; |
| |
| <functionResource> ::= ( "ALL" "FUNCTIONS" ("IN KEYSPACE" <keyspaceName>)? ) |
| | ( "FUNCTION" <functionAggregateName> |
| ( "(" ( newcol=<cident> <storageType> |
| ( "," [newcolname]=<cident> <storageType> )* )? |
| ")" ) |
| ) |
| ; |
| |
| <jmxResource> ::= ( "ALL" "MBEANS") |
| | ( ( "MBEAN" | "MBEANS" ) <stringLiteral> ) |
| ; |
| |
| ''' |
| |
| |
| @completer_for('permissionExpr', 'newpermission') |
| def permission_completer(ctxt, _): |
| new_permissions = set([permission.upper() for permission in ctxt.get_binding('newpermission')]) |
| all_permissions = set([permission.arg for permission in ctxt.ruleset['permission'].arg]) |
| suggestions = all_permissions - new_permissions |
| if len(suggestions) == 0: |
| return [Hint('No more permissions here.')] |
| return suggestions |
| |
| |
| @completer_for('username', 'name') |
| def username_name_completer(ctxt, cass): |
| def maybe_quote(name): |
| if CqlRuleSet.is_valid_cql3_name(name): |
| return name |
| return "'%s'" % name |
| |
| # disable completion for CREATE USER. |
| if ctxt.matched[0][1].upper() == 'CREATE': |
| return [Hint('<username>')] |
| |
| session = cass.session |
| return [maybe_quote(list(row.values())[0].replace("'", "''")) for row in session.execute("LIST USERS")] |
| |
| |
| @completer_for('rolename', 'role') |
| def rolename_completer(ctxt, cass): |
| def maybe_quote(name): |
| if CqlRuleSet.is_valid_cql3_name(name): |
| return name |
| return "'%s'" % name |
| |
| # disable completion for CREATE ROLE. |
| if ctxt.matched[0][1].upper() == 'CREATE': |
| return [Hint('<rolename>')] |
| |
| session = cass.session |
| return [maybe_quote(row[0].replace("'", "''")) for row in session.execute("LIST ROLES")] |
| |
| |
| syntax_rules += r''' |
| <createTriggerStatement> ::= "CREATE" "TRIGGER" ( "IF" "NOT" "EXISTS" )? <cident> |
| "ON" cf=<columnFamilyName> "USING" class=<stringLiteral> |
| ; |
| <dropTriggerStatement> ::= "DROP" "TRIGGER" ( "IF" "EXISTS" )? triggername=<cident> |
| "ON" cf=<columnFamilyName> |
| ; |
| ''' |
| explain_completion('createTriggerStatement', 'class', '\'fully qualified class name\'') |
| |
| |
| def get_trigger_names(ctxt, cass): |
| ks = ctxt.get_binding('ksname', None) |
| if ks is not None: |
| ks = dequote_name(ks) |
| return cass.get_trigger_names(ks) |
| |
| |
| @completer_for('dropTriggerStatement', 'triggername') |
| def drop_trigger_completer(ctxt, cass): |
| names = get_trigger_names(ctxt, cass) |
| return list(map(maybe_escape_name, names)) |
| |
| |
| # END SYNTAX/COMPLETION RULE DEFINITIONS |
| |
| CqlRuleSet.append_rules(syntax_rules) |