kaqing 2.0.145__py3-none-any.whl → 2.0.189__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kaqing might be problematic. Click here for more details.

Files changed (209) hide show
  1. adam/__init__.py +0 -2
  2. adam/app_session.py +9 -12
  3. adam/apps.py +18 -4
  4. adam/batch.py +4 -4
  5. adam/checks/check_utils.py +16 -46
  6. adam/checks/cpu.py +7 -1
  7. adam/checks/cpu_metrics.py +52 -0
  8. adam/checks/disk.py +2 -3
  9. adam/columns/columns.py +3 -1
  10. adam/columns/cpu.py +3 -1
  11. adam/columns/cpu_metrics.py +22 -0
  12. adam/columns/memory.py +3 -4
  13. adam/commands/__init__.py +24 -0
  14. adam/commands/alter_tables.py +33 -48
  15. adam/commands/app/__init__.py +0 -0
  16. adam/commands/app/app.py +38 -0
  17. adam/commands/{app_ping.py → app/app_ping.py} +7 -13
  18. adam/commands/app/show_app_actions.py +49 -0
  19. adam/commands/{show → app}/show_app_id.py +8 -11
  20. adam/commands/{show → app}/show_app_queues.py +7 -14
  21. adam/commands/app/utils_app.py +106 -0
  22. adam/commands/audit/audit.py +21 -40
  23. adam/commands/audit/audit_repair_tables.py +14 -19
  24. adam/commands/audit/audit_run.py +14 -22
  25. adam/commands/audit/completions_l.py +15 -0
  26. adam/commands/audit/show_last10.py +4 -19
  27. adam/commands/audit/show_slow10.py +4 -18
  28. adam/commands/audit/show_top10.py +4 -16
  29. adam/commands/audit/utils_show_top10.py +15 -3
  30. adam/commands/bash/__init__.py +5 -0
  31. adam/commands/bash/bash.py +7 -104
  32. adam/commands/bash/utils_bash.py +16 -0
  33. adam/commands/cat.py +7 -27
  34. adam/commands/cd.py +7 -11
  35. adam/commands/check.py +15 -24
  36. adam/commands/cli_commands.py +8 -4
  37. adam/commands/clipboard_copy.py +87 -0
  38. adam/commands/code.py +21 -24
  39. adam/commands/command.py +207 -42
  40. adam/commands/commands_utils.py +25 -27
  41. adam/commands/cql/completions_c.py +28 -0
  42. adam/commands/cql/cqlsh.py +9 -33
  43. adam/commands/cql/{cql_utils.py → utils_cql.py} +111 -15
  44. adam/commands/deploy/code_start.py +7 -10
  45. adam/commands/deploy/code_stop.py +4 -21
  46. adam/commands/deploy/code_utils.py +3 -3
  47. adam/commands/deploy/deploy.py +4 -27
  48. adam/commands/deploy/deploy_frontend.py +14 -17
  49. adam/commands/deploy/deploy_pg_agent.py +3 -6
  50. adam/commands/deploy/deploy_pod.py +64 -68
  51. adam/commands/deploy/undeploy.py +4 -27
  52. adam/commands/deploy/undeploy_frontend.py +4 -7
  53. adam/commands/deploy/undeploy_pg_agent.py +5 -8
  54. adam/commands/deploy/undeploy_pod.py +9 -12
  55. adam/commands/devices/device.py +124 -2
  56. adam/commands/devices/device_app.py +41 -24
  57. adam/commands/devices/device_auit_log.py +10 -4
  58. adam/commands/devices/device_cass.py +48 -14
  59. adam/commands/devices/device_export.py +13 -12
  60. adam/commands/devices/device_postgres.py +105 -54
  61. adam/commands/download_file.py +47 -0
  62. adam/commands/exit.py +1 -4
  63. adam/commands/export/clean_up_all_export_sessions.py +37 -0
  64. adam/commands/export/clean_up_export_sessions.py +9 -10
  65. adam/commands/export/completions_x.py +11 -0
  66. adam/commands/export/download_export_session.py +40 -0
  67. adam/commands/export/drop_export_database.py +7 -26
  68. adam/commands/export/drop_export_databases.py +5 -14
  69. adam/commands/export/export.py +6 -52
  70. adam/commands/export/export_databases.py +108 -32
  71. adam/commands/export/export_select.py +8 -59
  72. adam/commands/export/export_sessions.py +209 -0
  73. adam/commands/export/export_use.py +14 -20
  74. adam/commands/export/export_x_select.py +48 -0
  75. adam/commands/export/exporter.py +135 -167
  76. adam/commands/export/import_files.py +44 -0
  77. adam/commands/export/import_session.py +11 -35
  78. adam/commands/export/importer.py +19 -5
  79. adam/commands/export/importer_athena.py +112 -44
  80. adam/commands/export/importer_sqlite.py +42 -22
  81. adam/commands/export/show_column_counts.py +13 -31
  82. adam/commands/export/show_export_databases.py +7 -7
  83. adam/commands/export/show_export_session.py +8 -20
  84. adam/commands/export/show_export_sessions.py +6 -16
  85. adam/commands/export/utils_export.py +64 -11
  86. adam/commands/find_files.py +51 -0
  87. adam/commands/find_processes.py +76 -0
  88. adam/commands/head.py +36 -0
  89. adam/commands/help.py +2 -2
  90. adam/commands/intermediate_command.py +52 -0
  91. adam/commands/issues.py +11 -43
  92. adam/commands/kubectl.py +3 -6
  93. adam/commands/login.py +22 -24
  94. adam/commands/logs.py +3 -6
  95. adam/commands/ls.py +9 -10
  96. adam/commands/medusa/medusa.py +4 -22
  97. adam/commands/medusa/medusa_backup.py +20 -27
  98. adam/commands/medusa/medusa_restore.py +49 -46
  99. adam/commands/medusa/medusa_show_backupjobs.py +16 -18
  100. adam/commands/medusa/medusa_show_restorejobs.py +13 -18
  101. adam/commands/medusa/utils_medusa.py +15 -0
  102. adam/commands/nodetool.py +7 -21
  103. adam/commands/param_get.py +11 -14
  104. adam/commands/param_set.py +8 -12
  105. adam/commands/postgres/completions_p.py +22 -0
  106. adam/commands/postgres/postgres.py +34 -57
  107. adam/commands/postgres/postgres_databases.py +270 -0
  108. adam/commands/postgres/postgres_ls.py +4 -8
  109. adam/commands/postgres/postgres_preview.py +5 -9
  110. adam/commands/postgres/utils_postgres.py +79 -0
  111. adam/commands/preview_table.py +8 -45
  112. adam/commands/pwd.py +13 -16
  113. adam/commands/reaper/reaper.py +4 -27
  114. adam/commands/reaper/reaper_forward.py +49 -56
  115. adam/commands/reaper/reaper_forward_session.py +6 -0
  116. adam/commands/reaper/reaper_forward_stop.py +10 -16
  117. adam/commands/reaper/reaper_restart.py +7 -14
  118. adam/commands/reaper/reaper_run_abort.py +8 -33
  119. adam/commands/reaper/reaper_runs.py +43 -58
  120. adam/commands/reaper/reaper_runs_abort.py +29 -49
  121. adam/commands/reaper/reaper_schedule_activate.py +14 -33
  122. adam/commands/reaper/reaper_schedule_start.py +9 -33
  123. adam/commands/reaper/reaper_schedule_stop.py +9 -33
  124. adam/commands/reaper/reaper_schedules.py +4 -14
  125. adam/commands/reaper/reaper_status.py +8 -16
  126. adam/commands/reaper/utils_reaper.py +203 -0
  127. adam/commands/repair/repair.py +4 -22
  128. adam/commands/repair/repair_log.py +5 -11
  129. adam/commands/repair/repair_run.py +27 -34
  130. adam/commands/repair/repair_scan.py +32 -40
  131. adam/commands/repair/repair_stop.py +5 -12
  132. adam/commands/report.py +27 -29
  133. adam/commands/restart.py +25 -26
  134. adam/commands/rollout.py +19 -24
  135. adam/commands/shell.py +12 -4
  136. adam/commands/show/show.py +11 -27
  137. adam/commands/show/show_adam.py +3 -3
  138. adam/commands/show/show_cassandra_repairs.py +37 -0
  139. adam/commands/show/show_cassandra_status.py +47 -51
  140. adam/commands/show/show_cassandra_version.py +5 -18
  141. adam/commands/show/show_cli_commands.py +56 -0
  142. adam/commands/show/show_host.py +1 -1
  143. adam/commands/show/show_login.py +20 -27
  144. adam/commands/show/show_params.py +2 -5
  145. adam/commands/show/show_processes.py +18 -21
  146. adam/commands/show/show_storage.py +11 -20
  147. adam/commands/watch.py +26 -29
  148. adam/config.py +5 -16
  149. adam/embedded_params.py +1 -1
  150. adam/log.py +4 -4
  151. adam/pod_exec_result.py +3 -3
  152. adam/repl.py +45 -39
  153. adam/repl_commands.py +26 -19
  154. adam/repl_session.py +8 -1
  155. adam/repl_state.py +85 -36
  156. adam/sql/lark_completer.py +284 -0
  157. adam/sql/lark_parser.py +604 -0
  158. adam/sql/sql_completer.py +4 -6
  159. adam/sql/sql_state_machine.py +29 -16
  160. adam/sso/authn_ad.py +6 -8
  161. adam/sso/authn_okta.py +4 -6
  162. adam/sso/cred_cache.py +3 -5
  163. adam/sso/idp.py +9 -12
  164. adam/utils.py +484 -37
  165. adam/utils_athena.py +19 -19
  166. adam/utils_audits.py +12 -12
  167. adam/utils_issues.py +32 -0
  168. adam/utils_k8s/app_clusters.py +14 -19
  169. adam/utils_k8s/app_pods.py +7 -2
  170. adam/utils_k8s/cassandra_clusters.py +30 -19
  171. adam/utils_k8s/cassandra_nodes.py +2 -2
  172. adam/utils_k8s/custom_resources.py +16 -17
  173. adam/utils_k8s/ingresses.py +2 -2
  174. adam/utils_k8s/jobs.py +7 -11
  175. adam/utils_k8s/k8s.py +96 -0
  176. adam/utils_k8s/kube_context.py +2 -2
  177. adam/utils_k8s/pods.py +37 -81
  178. adam/utils_k8s/secrets.py +4 -4
  179. adam/utils_k8s/service_accounts.py +5 -4
  180. adam/utils_k8s/services.py +2 -2
  181. adam/utils_k8s/statefulsets.py +6 -14
  182. adam/utils_local.py +4 -0
  183. adam/utils_repl/appendable_completer.py +6 -0
  184. adam/utils_repl/repl_completer.py +128 -2
  185. adam/utils_repl/state_machine.py +3 -3
  186. adam/utils_sqlite.py +78 -42
  187. adam/version.py +1 -1
  188. {kaqing-2.0.145.dist-info → kaqing-2.0.189.dist-info}/METADATA +1 -1
  189. kaqing-2.0.189.dist-info/RECORD +253 -0
  190. kaqing-2.0.189.dist-info/top_level.txt +2 -0
  191. teddy/__init__.py +0 -0
  192. teddy/lark_parser.py +436 -0
  193. teddy/lark_parser2.py +618 -0
  194. adam/commands/app.py +0 -67
  195. adam/commands/cp.py +0 -95
  196. adam/commands/cql/cql_completions.py +0 -28
  197. adam/commands/export/clean_up_export_session.py +0 -53
  198. adam/commands/export/export_select_x.py +0 -54
  199. adam/commands/postgres/postgres_context.py +0 -248
  200. adam/commands/postgres/postgres_utils.py +0 -31
  201. adam/commands/postgres/psql_completions.py +0 -10
  202. adam/commands/reaper/reaper_session.py +0 -159
  203. adam/commands/show/show_app_actions.py +0 -56
  204. adam/commands/show/show_commands.py +0 -61
  205. adam/commands/show/show_repairs.py +0 -47
  206. kaqing-2.0.145.dist-info/RECORD +0 -227
  207. kaqing-2.0.145.dist-info/top_level.txt +0 -1
  208. {kaqing-2.0.145.dist-info → kaqing-2.0.189.dist-info}/WHEEL +0 -0
  209. {kaqing-2.0.145.dist-info → kaqing-2.0.189.dist-info}/entry_points.txt +0 -0
adam/repl_state.py CHANGED
@@ -3,7 +3,6 @@ from enum import Enum
3
3
  import re
4
4
  from typing import Callable
5
5
 
6
- from adam.commands.postgres.postgres_context import PostgresContext
7
6
  from adam.utils_k8s.app_clusters import AppClusters
8
7
  from adam.utils_k8s.app_pods import AppPods
9
8
  from adam.utils_k8s.cassandra_clusters import CassandraClusters
@@ -20,31 +19,8 @@ class BashSession:
20
19
  def pwd(self, state: 'ReplState'):
21
20
  command = f'cat /tmp/.qing-{self.session_id}'
22
21
 
23
- if state.device == ReplState.A and state.app_app:
24
- if state.app_pod:
25
- rs = [AppPods.exec(state.app_pod, state.namespace, command, show_out=False)]
26
- else:
27
- pods = AppPods.pod_names(state.namespace, state.app_env, state.app_pod)
28
- rs = AppClusters.exec(pods, state.namespace, command, show_out=False)
29
- elif state.pod:
30
- rs = [CassandraNodes.exec(state.pod, state.namespace, command, show_out=False)]
31
- elif state.sts:
32
- rs = CassandraClusters.exec(state.sts, state.namespace, command, action='bash', show_out=False)
33
-
34
- dir = None
35
- for r in rs:
36
- if r.exit_code(): # if fails to read the session file, ignore
37
- continue
38
-
39
- dir0 = r.stdout.strip(' \r\n')
40
- if dir:
41
- if dir != dir0:
42
- log2('Inconsitent working dir found across multiple pods.')
43
- return None
44
- else:
45
- dir = dir0
46
-
47
- return dir
22
+ with device(state) as pods:
23
+ return pods.exec(command, action='bash', show_out=False)
48
24
 
49
25
  class RequiredState(Enum):
50
26
  CLUSTER = 'cluster'
@@ -85,6 +61,7 @@ class ReplState:
85
61
  self.bash_session = bash_session
86
62
  self.remote_dir = remote_dir
87
63
  self.original_state: ReplState = None
64
+ self.pod_targetted = False
88
65
 
89
66
  self.export_session: str = None
90
67
 
@@ -105,11 +82,11 @@ class ReplState:
105
82
  msg = ''
106
83
  if self.device == ReplState.P:
107
84
  msg = f'{ReplState.P}:'
108
- pg: PostgresContext = PostgresContext.apply(self.namespace, self.pg_path) if self.pg_path else None
109
- if pg and pg.db:
110
- msg += pg.db
111
- elif pg and pg.host:
112
- msg += pg.host
85
+ host, database = self.pg_host_n_database()
86
+ if database:
87
+ msg += database
88
+ elif host:
89
+ msg += host
113
90
  elif self.device == ReplState.A:
114
91
  msg = f'{ReplState.A}:'
115
92
  if self.app_env:
@@ -337,8 +314,8 @@ class ReplState:
337
314
  if self.device != ReplState.P:
338
315
  return (False, None)
339
316
 
340
- pg: PostgresContext = PostgresContext.apply(self.namespace, self.pg_path)
341
- if not pg.db:
317
+ _, database = self.pg_host_n_database()
318
+ if not database:
342
319
  def error():
343
320
  if self.in_repl:
344
321
  log2('cd to a database first.')
@@ -369,7 +346,10 @@ class ReplState:
369
346
  if not self.export_session:
370
347
  def error():
371
348
  if self.in_repl:
372
- log2("Select an export database first with 'use' command.")
349
+ if self.device == ReplState.C:
350
+ log2("Select an export database first with 'use' command.")
351
+ else:
352
+ log2('cd to an export database first.')
373
353
  else:
374
354
  log2('* export database is missing.')
375
355
  log2()
@@ -413,9 +393,10 @@ class ReplState:
413
393
  self.pop()
414
394
  self.bash_session = None
415
395
 
416
- def push(self):
396
+ def push(self, pod_targetted=False):
417
397
  if not self.original_state:
418
398
  self.original_state = copy(self)
399
+ self.pod_targetted = pod_targetted
419
400
 
420
401
  def pop(self):
421
402
  if o := self.original_state:
@@ -429,4 +410,72 @@ class ReplState:
429
410
  # self.export_session = o.export_session
430
411
  self.namespace = o.namespace
431
412
 
432
- self.original_state = None
413
+ self.original_state = None
414
+ self.pod_targetted = False
415
+
416
+ def pg_host_n_database(self):
417
+ host = None
418
+ database = None
419
+
420
+ if self.pg_path:
421
+ host_n_db = self.pg_path.split('/')
422
+ host = host_n_db[0]
423
+ if len(host_n_db) > 1:
424
+ database = host_n_db[1]
425
+
426
+ return host, database
427
+
428
+ def with_no_pod(self):
429
+ state1 = copy(self)
430
+ state1.pod = None
431
+
432
+ return state1
433
+
434
+ class DevicePodService:
435
+ def __init__(self, handler: 'DeviceExecHandler'):
436
+ self.handler = handler
437
+
438
+ def exec(self, command: str, action='bash', show_out = True):
439
+ state = self.handler.state
440
+
441
+ rs = None
442
+ if state.device == ReplState.A and state.app_app:
443
+ if state.app_pod:
444
+ rs = [AppPods.exec(state.app_pod, state.namespace, command, show_out=show_out)]
445
+ else:
446
+ pods = AppPods.pod_names(state.namespace, state.app_env, state.app_app)
447
+ rs = AppClusters.exec(pods, state.namespace, command, show_out=show_out)
448
+ elif state.pod:
449
+ rs = [CassandraNodes.exec(state.pod, state.namespace, command, show_out=show_out)]
450
+ elif state.sts:
451
+ rs = CassandraClusters.exec(state.sts, state.namespace, command, action=action, show_out=show_out)
452
+ # assume that pg-agent or ops pod is single pod
453
+
454
+ dir = None
455
+ if rs:
456
+ for r in rs:
457
+ if r.exit_code(): # if fails to read the session file, ignore
458
+ continue
459
+
460
+ dir0 = r.stdout.strip(' \r\n')
461
+ if dir:
462
+ if dir != dir0:
463
+ log2('Inconsitent working dir found across multiple pods.')
464
+ return None
465
+ else:
466
+ dir = dir0
467
+
468
+ return dir
469
+
470
+ class DeviceExecHandler:
471
+ def __init__(self, state: ReplState):
472
+ self.state = state
473
+
474
+ def __enter__(self):
475
+ return DevicePodService(self)
476
+
477
+ def __exit__(self, exc_type, exc_val, exc_tb):
478
+ return False
479
+
480
+ def device(state: ReplState):
481
+ return DeviceExecHandler(state)
@@ -0,0 +1,284 @@
1
+ import functools
2
+ import os
3
+ from typing import Iterable
4
+ from prompt_toolkit.completion import CompleteEvent, Completer, Completion, NestedCompleter, WordCompleter
5
+ from prompt_toolkit.document import Document
6
+
7
+ from adam.config import Config
8
+ from adam.sql.lark_parser import LarkParser
9
+ from adam.utils import debug, log_timing, offload
10
+ from adam.utils_repl.appendable_completer import AppendableCompleter
11
+ from adam.utils_repl.repl_completer import merge_completions, preload
12
+
13
+ __all__ = [
14
+ "LarkCompleter",
15
+ ]
16
+
17
+ def default_columns(x: list[str]):
18
+ return 'id,x.,y.,z.'.split(',')
19
+
20
+ class LarkCompleter(Completer, AppendableCompleter):
21
+ SYSTEM = 'system'
22
+
23
+ def __init__(self,
24
+ dml: str = None,
25
+ expandables: dict = {},
26
+ variant: str = 'c',
27
+
28
+ name: str = None,
29
+ options_lambda: callable = None,
30
+ auto: str = 'lazy',
31
+ debug = False
32
+ ) -> None:
33
+ self.nested: NestedCompleter = None
34
+ self.options_lambda = options_lambda
35
+ if options_lambda and auto == 'lazy':
36
+ preload(options_lambda, log_key=name)
37
+
38
+ self.variant = variant
39
+ self.parser = None
40
+ self.dml = dml
41
+ self.expandables = expandables
42
+
43
+ self.display_dict = {}
44
+ self.meta_dict = {}
45
+ self.WORD = None
46
+ self.sentence = False
47
+ self.match_middle = False
48
+ self.pattern = None
49
+
50
+ self.debug = debug
51
+
52
+ if variant:
53
+ self.parser = LarkCompleter.lark_parser(variant)
54
+ self.preload_lazy_auto_completes()
55
+
56
+ def __repr__(self):
57
+ return f"LarkCompleter.{self.variant}"
58
+
59
+ def preload_lazy_auto_completes(self):
60
+ for key, value in self.expandables.items():
61
+ if callable(value):
62
+ if self.auto_complete(key) == 'lazy':
63
+ preload(value, log_key=key)
64
+
65
+ def from_lambda(name: str, options_lambda: callable, auto: str = 'lazy'):
66
+ return LarkCompleter(name=name, options_lambda=options_lambda, auto=auto, variant=None)
67
+
68
+ @functools.lru_cache()
69
+ def lark_parser(variant: str):
70
+ dir_path = os.path.dirname(os.path.realpath(__file__))
71
+ with open(dir_path + f"/qingl.lark") as f:
72
+ grammar: str = None
73
+ with log_timing(f'lark.{variant}.file-read'):
74
+ grammar = f.read()
75
+
76
+ common_contexts = {
77
+ 'cd_command.file_name': 'direct-dirs',
78
+ }
79
+
80
+ if variant in ['a', 'c0', 'p0', 'x0']:
81
+ grammar = grammar.replace('start: statement_sequence', f'start: qing_{variant}_statement')
82
+ contexts_by_path = {
83
+ } | common_contexts
84
+
85
+ debug(f'* GRAMMAR replaced to start: qing_{variant}_statement')
86
+
87
+ return LarkParser(grammar, contexts_by_path)
88
+ elif variant == 'system':
89
+ grammar = grammar.replace('start: statement_sequence', f'start: qing_{variant}_statement')
90
+ contexts_by_path = {
91
+ } | common_contexts
92
+
93
+ return LarkParser(grammar, contexts_by_path)
94
+
95
+ grammar = grammar.replace('qing_statement: qing_p_statement', f'qing_statement: qing_{variant}_statement')
96
+ debug(f'* GRAMMAR replaced to qing_statement: qing_{variant}_statement')
97
+
98
+ bash_contexts = {
99
+ 'bash_statement.host_name': 'hosts',
100
+ 'bash_statement.bash_command': 'bash-commands',
101
+ }
102
+
103
+ contexts_by_path = {
104
+ 'describe_keyspace.keyspace_name': 'keyspaces',
105
+ 'keyspace_ref.keyspace_path.namespace_ref.identifier_ref': 'tables',
106
+ 'preview_table_statement.path.identifier_ref': 'tables',
107
+
108
+ 'insert_statement.insert_select': 'column-names',
109
+ 'update_statement.set_clause.path.identifier_ref': 'column-names',
110
+ 'update_statement.where_clause.cond.expr.path.identifier_ref': 'column-names',
111
+ 'delete_statement.where_clause.cond.expr.path.identifier_ref': 'column-names',
112
+
113
+ 'select_clause.projection.result_expr.expr.path.identifier_ref': 'columns',
114
+ 'select_from.where_clause.cond.expr.path.identifier_ref': 'columns',
115
+ 'select_from.where_clause.cond.expr.logical_term.and_expr.cond.expr.path.identifier_ref': 'columns',
116
+ 'select_from.group_by_clause.group_term.expr.path.identifier_ref': 'columns',
117
+ 'select_statement.order_by_clause.ordering_term.expr.path.identifier_ref': 'columns',
118
+ 'select_from.from_clause.from_terms.join_clause.ansi_join_clause.ansi_join_predicate.expr.path.identifier_ref': 'columns',
119
+ 'select_from.from_clause.from_terms.join_clause.ansi_join_clause.ansi_join_predicate.expr.comparison_term.relational_expr.expr.path.identifier_ref': 'columns',
120
+
121
+ 'select_from.from_clause.from_terms.from_generic.alias.identifier_ref': 'column-aliases',
122
+
123
+ 'select_statement.limit_clause.expr.literal.nbr.digit': 'limits',
124
+ } | common_contexts
125
+
126
+ if variant == 'p':
127
+ contexts_by_path = bash_contexts | contexts_by_path
128
+ elif variant == 'c':
129
+ contexts_by_path = {
130
+ 'export_table.path.identifier_ref': 'tables',
131
+ 'show_column_counts_command.path.identifier_ref': 'tables',
132
+ 'export_statement.export_tables.keyspace_name': 'keyspaces',
133
+
134
+ 'alter_cql_table_statement.properties.property.property_name': 'table-props',
135
+ 'alter_tables_statement.properties.property.property_name': 'table-props',
136
+ 'alter_cql_table_statement.properties.property.property_value.literal': 'table-props-value',
137
+
138
+ 'select_clause.projection.result_expr.expr.path.identifier_ref': 'columns',
139
+ 'export_statement.export_tables.export_table.column_name_list.column_name': 'columns',
140
+
141
+ 'consistency_statement.consistency': 'consistencies',
142
+ 'export_statement.export_to.export_database_type': 'export-database-types',
143
+ 'drop_export_database.export_database_name': 'export-databases',
144
+ 'use_export_db_statement.export_database_name': 'export-databases',
145
+ 'clean_up_export_session_statement.clean_up_export_sessions.export_session_name': 'export-sessions',
146
+ 'show_export_command.export_session_name': 'export-sessions',
147
+ 'import_statement.import_session.export_session_name': 'export-sessions-incomplete',
148
+ 'download_session_statement.export_session_name': 'export-sessions-incomplete',
149
+ } | bash_contexts | contexts_by_path
150
+ elif variant == 'l':
151
+ contexts_by_path = {
152
+ 'add_partition_action.partition_ref.partition_name': 'partition-columns',
153
+ 'show_topn_statement.topn_count': 'topn-counts',
154
+ 'show_topn_statement.topn_type': 'topn-types',
155
+ 'show_topn_statement.topn_window': 'topn-windows'
156
+ } | contexts_by_path
157
+ elif variant == 'x':
158
+ contexts_by_path = {
159
+ 'show_column_counts_command.path.identifier_ref': 'tables',
160
+ 'drop_export_database.export_database_name': 'export-databases',
161
+ 'use_export_db_statement.export_database_name': 'export-databases',
162
+ } | contexts_by_path
163
+
164
+ grammar = grammar.replace('select_clause: "SELECT"i hint_comment? projection', 'select_clause: ("SELECT"i | "XELECT"i) hint_comment? projection')
165
+
166
+ with offload():
167
+ with open('/tmp/grammar.lark', 'wt') as f:
168
+ f.write(grammar)
169
+
170
+ return LarkParser(grammar, contexts_by_path)
171
+
172
+ def get_completions(
173
+ self, document: Document, complete_event: CompleteEvent
174
+ ) -> Iterable[Completion]:
175
+ if not self.nested and self.options_lambda:
176
+ # for lazy completions
177
+ self.nested = NestedCompleter.from_nested_dict(self.options_lambda())
178
+
179
+ nested_words = set()
180
+
181
+ if self.nested:
182
+ # from NestedCompleter
183
+
184
+ # Split document.
185
+ text = document.text_before_cursor.lstrip()
186
+ stripped_len = len(document.text_before_cursor) - len(text)
187
+
188
+ # If there is a space, check for the first term, and use a
189
+ # subcompleter.
190
+ if " " in text:
191
+ first_term = text.split()[0]
192
+ completer = self.nested.options.get(first_term)
193
+
194
+ # If we have a sub completer, use this for the completions.
195
+ if completer is not None:
196
+ remaining_text = text[len(first_term) :].lstrip()
197
+ move_cursor = len(text) - len(remaining_text) + stripped_len
198
+
199
+ new_document = Document(
200
+ remaining_text,
201
+ cursor_position=document.cursor_position - move_cursor,
202
+ )
203
+
204
+ for c in completer.get_completions(new_document, complete_event):
205
+ nested_words.add(c.text)
206
+ yield c
207
+
208
+ # No space in the input: behave exactly like `WordCompleter`.
209
+ else:
210
+ completer = WordCompleter(
211
+ list(self.nested.options.keys()), ignore_case=self.nested.ignore_case
212
+ )
213
+ for c in completer.get_completions(document, complete_event):
214
+ nested_words.add(c.text)
215
+ yield c
216
+
217
+ if self.parser:
218
+ full = document.text_before_cursor
219
+ if self.dml:
220
+ full = self.dml + ' ' + full
221
+
222
+ words0 = []
223
+ words1 = []
224
+ context = {}
225
+ for word in self.parser.next_terminals(full, context=context):
226
+ if ex := self.expandable(word):
227
+ if ex in self.expandables:
228
+ e = self.expandables[ex]
229
+ if callable(e):
230
+ if self.auto_complete(ex) != 'off':
231
+ ctx = None
232
+ if 'last-id' in context:
233
+ ctx = context['last-id']
234
+ e = e(ctx)
235
+ words0.extend(e)
236
+ else:
237
+ words0.extend(e)
238
+ else:
239
+ words1.append(word)
240
+ words = words0 + words1
241
+
242
+ word_before_cursor = document.get_word_before_cursor(
243
+ WORD=self.WORD, pattern=self.pattern
244
+ )
245
+
246
+ word_before_cursor = word_before_cursor.lower()
247
+
248
+ def word_matches(word: str) -> bool:
249
+ return word.lower().startswith(word_before_cursor)
250
+
251
+ for word in words:
252
+ if word_matches(word) and word not in nested_words:
253
+ display = self.display_dict.get(word, word)
254
+ display_meta = self.meta_dict.get(word, "")
255
+ yield Completion(
256
+ word,
257
+ -len(word_before_cursor),
258
+ display=display,
259
+ display_meta=display_meta,
260
+ )
261
+
262
+ def completions_for_nesting(self, dml: str = None):
263
+ if dml:
264
+ return {dml: LarkCompleter(dml, expandables=self.expandables, variant=self.variant)}
265
+
266
+ return {
267
+ word.text.lower(): LarkCompleter(word.text, expandables=self.expandables, variant=self.variant)
268
+ for word in self.get_completions(Document(''), None)
269
+ }
270
+
271
+ def expandable(self, word: str):
272
+ return word.strip('`') if word.startswith('`') else None
273
+
274
+ def append_completions(self, key: str, value: dict[str, any]):
275
+ if isinstance(value, LarkCompleter) and self.variant == value.variant:
276
+ return
277
+
278
+ if self.nested:
279
+ self.nested = NestedCompleter.from_nested_dict(merge_completions(self.nested.options, value))
280
+ else:
281
+ self.nested = NestedCompleter.from_nested_dict(value)
282
+
283
+ def auto_complete(self, key: str, default = 'lazy'):
284
+ return Config().get(f'auto-complete.{self.variant}.{key}', default=default)