kaqing 2.0.110__py3-none-any.whl → 2.0.184__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 (204) 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 +5 -5
  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 +98 -0
  22. adam/commands/audit/audit.py +27 -31
  23. adam/commands/audit/audit_repair_tables.py +14 -18
  24. adam/commands/audit/audit_run.py +16 -23
  25. adam/commands/audit/show_last10.py +4 -17
  26. adam/commands/audit/show_slow10.py +4 -17
  27. adam/commands/audit/show_top10.py +4 -16
  28. adam/commands/audit/utils_show_top10.py +15 -3
  29. adam/commands/bash/__init__.py +5 -0
  30. adam/commands/bash/bash.py +36 -0
  31. adam/commands/bash/bash_completer.py +93 -0
  32. adam/commands/bash/utils_bash.py +16 -0
  33. adam/commands/cat.py +36 -0
  34. adam/commands/cd.py +11 -95
  35. adam/commands/check.py +15 -24
  36. adam/commands/cli_commands.py +2 -3
  37. adam/commands/clipboard_copy.py +86 -0
  38. adam/commands/code.py +57 -0
  39. adam/commands/command.py +198 -40
  40. adam/commands/commands_utils.py +12 -27
  41. adam/commands/cql/cql_completions.py +27 -10
  42. adam/commands/cql/cqlsh.py +12 -30
  43. adam/commands/cql/utils_cql.py +297 -0
  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 +65 -73
  51. adam/commands/deploy/deploy_utils.py +14 -24
  52. adam/commands/deploy/undeploy.py +4 -27
  53. adam/commands/deploy/undeploy_frontend.py +4 -7
  54. adam/commands/deploy/undeploy_pg_agent.py +6 -8
  55. adam/commands/deploy/undeploy_pod.py +11 -12
  56. adam/commands/devices/__init__.py +0 -0
  57. adam/commands/devices/device.py +123 -0
  58. adam/commands/devices/device_app.py +163 -0
  59. adam/commands/devices/device_auit_log.py +49 -0
  60. adam/commands/devices/device_cass.py +179 -0
  61. adam/commands/devices/device_export.py +84 -0
  62. adam/commands/devices/device_postgres.py +150 -0
  63. adam/commands/devices/devices.py +25 -0
  64. adam/commands/download_file.py +47 -0
  65. adam/commands/exit.py +1 -4
  66. adam/commands/export/__init__.py +0 -0
  67. adam/commands/export/clean_up_all_export_sessions.py +37 -0
  68. adam/commands/export/clean_up_export_sessions.py +39 -0
  69. adam/commands/export/download_export_session.py +39 -0
  70. adam/commands/export/drop_export_database.py +39 -0
  71. adam/commands/export/drop_export_databases.py +37 -0
  72. adam/commands/export/export.py +53 -0
  73. adam/commands/export/export_databases.py +245 -0
  74. adam/commands/export/export_select.py +59 -0
  75. adam/commands/export/export_select_x.py +54 -0
  76. adam/commands/export/export_sessions.py +209 -0
  77. adam/commands/export/export_use.py +49 -0
  78. adam/commands/export/exporter.py +332 -0
  79. adam/commands/export/import_files.py +44 -0
  80. adam/commands/export/import_session.py +44 -0
  81. adam/commands/export/importer.py +81 -0
  82. adam/commands/export/importer_athena.py +177 -0
  83. adam/commands/export/importer_sqlite.py +67 -0
  84. adam/commands/export/show_column_counts.py +45 -0
  85. adam/commands/export/show_export_databases.py +38 -0
  86. adam/commands/export/show_export_session.py +39 -0
  87. adam/commands/export/show_export_sessions.py +37 -0
  88. adam/commands/export/utils_export.py +343 -0
  89. adam/commands/find_files.py +51 -0
  90. adam/commands/find_processes.py +76 -0
  91. adam/commands/head.py +36 -0
  92. adam/commands/help.py +5 -3
  93. adam/commands/intermediate_command.py +49 -0
  94. adam/commands/issues.py +11 -43
  95. adam/commands/kubectl.py +38 -0
  96. adam/commands/login.py +22 -24
  97. adam/commands/logs.py +3 -6
  98. adam/commands/ls.py +11 -116
  99. adam/commands/medusa/medusa.py +4 -22
  100. adam/commands/medusa/medusa_backup.py +20 -27
  101. adam/commands/medusa/medusa_restore.py +38 -37
  102. adam/commands/medusa/medusa_show_backupjobs.py +16 -18
  103. adam/commands/medusa/medusa_show_restorejobs.py +13 -18
  104. adam/commands/nodetool.py +11 -17
  105. adam/commands/param_get.py +11 -14
  106. adam/commands/param_set.py +8 -12
  107. adam/commands/postgres/postgres.py +45 -46
  108. adam/commands/postgres/postgres_databases.py +269 -0
  109. adam/commands/postgres/postgres_ls.py +4 -8
  110. adam/commands/postgres/postgres_preview.py +5 -9
  111. adam/commands/postgres/psql_completions.py +4 -3
  112. adam/commands/postgres/utils_postgres.py +70 -0
  113. adam/commands/preview_table.py +8 -44
  114. adam/commands/pwd.py +14 -46
  115. adam/commands/reaper/reaper.py +4 -27
  116. adam/commands/reaper/reaper_forward.py +49 -56
  117. adam/commands/reaper/reaper_forward_session.py +6 -0
  118. adam/commands/reaper/reaper_forward_stop.py +10 -16
  119. adam/commands/reaper/reaper_restart.py +7 -14
  120. adam/commands/reaper/reaper_run_abort.py +8 -33
  121. adam/commands/reaper/reaper_runs.py +43 -58
  122. adam/commands/reaper/reaper_runs_abort.py +29 -49
  123. adam/commands/reaper/reaper_schedule_activate.py +9 -32
  124. adam/commands/reaper/reaper_schedule_start.py +9 -32
  125. adam/commands/reaper/reaper_schedule_stop.py +9 -32
  126. adam/commands/reaper/reaper_schedules.py +4 -14
  127. adam/commands/reaper/reaper_status.py +8 -16
  128. adam/commands/reaper/utils_reaper.py +194 -0
  129. adam/commands/repair/repair.py +4 -22
  130. adam/commands/repair/repair_log.py +5 -11
  131. adam/commands/repair/repair_run.py +27 -34
  132. adam/commands/repair/repair_scan.py +32 -38
  133. adam/commands/repair/repair_stop.py +5 -11
  134. adam/commands/report.py +27 -29
  135. adam/commands/restart.py +25 -26
  136. adam/commands/rollout.py +19 -24
  137. adam/commands/shell.py +12 -4
  138. adam/commands/show/show.py +10 -25
  139. adam/commands/show/show_adam.py +3 -3
  140. adam/commands/show/show_cassandra_repairs.py +35 -0
  141. adam/commands/show/show_cassandra_status.py +33 -51
  142. adam/commands/show/show_cassandra_version.py +5 -18
  143. adam/commands/show/show_commands.py +20 -25
  144. adam/commands/show/show_host.py +1 -1
  145. adam/commands/show/show_login.py +20 -27
  146. adam/commands/show/show_params.py +2 -5
  147. adam/commands/show/show_processes.py +15 -19
  148. adam/commands/show/show_storage.py +10 -20
  149. adam/commands/watch.py +26 -29
  150. adam/config.py +5 -14
  151. adam/embedded_params.py +1 -1
  152. adam/log.py +4 -4
  153. adam/pod_exec_result.py +6 -3
  154. adam/repl.py +69 -115
  155. adam/repl_commands.py +52 -19
  156. adam/repl_state.py +161 -40
  157. adam/sql/sql_completer.py +52 -27
  158. adam/sql/sql_state_machine.py +131 -19
  159. adam/sso/authn_ad.py +6 -8
  160. adam/sso/authn_okta.py +4 -6
  161. adam/sso/cred_cache.py +3 -5
  162. adam/sso/idp.py +9 -12
  163. adam/utils.py +511 -9
  164. adam/utils_athena.py +145 -0
  165. adam/utils_audits.py +12 -103
  166. adam/utils_issues.py +32 -0
  167. adam/utils_k8s/app_clusters.py +28 -0
  168. adam/utils_k8s/app_pods.py +36 -0
  169. adam/utils_k8s/cassandra_clusters.py +30 -19
  170. adam/utils_k8s/cassandra_nodes.py +3 -3
  171. adam/utils_k8s/custom_resources.py +16 -17
  172. adam/utils_k8s/ingresses.py +2 -2
  173. adam/utils_k8s/jobs.py +7 -11
  174. adam/utils_k8s/k8s.py +87 -0
  175. adam/utils_k8s/kube_context.py +2 -2
  176. adam/utils_k8s/pods.py +89 -78
  177. adam/utils_k8s/secrets.py +4 -4
  178. adam/utils_k8s/service_accounts.py +5 -4
  179. adam/utils_k8s/services.py +2 -2
  180. adam/utils_k8s/statefulsets.py +1 -12
  181. adam/utils_local.py +4 -0
  182. adam/utils_net.py +4 -4
  183. adam/utils_repl/__init__.py +0 -0
  184. adam/utils_repl/automata_completer.py +48 -0
  185. adam/utils_repl/repl_completer.py +46 -0
  186. adam/utils_repl/state_machine.py +173 -0
  187. adam/utils_sqlite.py +137 -0
  188. adam/version.py +1 -1
  189. {kaqing-2.0.110.dist-info → kaqing-2.0.184.dist-info}/METADATA +1 -1
  190. kaqing-2.0.184.dist-info/RECORD +244 -0
  191. adam/commands/app.py +0 -67
  192. adam/commands/bash.py +0 -150
  193. adam/commands/cp.py +0 -95
  194. adam/commands/cql/cql_utils.py +0 -112
  195. adam/commands/devices.py +0 -118
  196. adam/commands/postgres/postgres_context.py +0 -239
  197. adam/commands/postgres/postgres_utils.py +0 -31
  198. adam/commands/reaper/reaper_session.py +0 -159
  199. adam/commands/show/show_app_actions.py +0 -56
  200. adam/commands/show/show_repairs.py +0 -47
  201. kaqing-2.0.110.dist-info/RECORD +0 -187
  202. {kaqing-2.0.110.dist-info → kaqing-2.0.184.dist-info}/WHEEL +0 -0
  203. {kaqing-2.0.110.dist-info → kaqing-2.0.184.dist-info}/entry_points.txt +0 -0
  204. {kaqing-2.0.110.dist-info → kaqing-2.0.184.dist-info}/top_level.txt +0 -0
adam/repl_state.py CHANGED
@@ -1,10 +1,10 @@
1
- import copy
1
+ from copy import copy
2
2
  from enum import Enum
3
3
  import re
4
- import traceback
5
4
  from typing import Callable
6
5
 
7
- from adam.commands.postgres.postgres_context import PostgresContext
6
+ from adam.utils_k8s.app_clusters import AppClusters
7
+ from adam.utils_k8s.app_pods import AppPods
8
8
  from adam.utils_k8s.cassandra_clusters import CassandraClusters
9
9
  from adam.utils_k8s.cassandra_nodes import CassandraNodes
10
10
  from adam.utils_k8s.kube_context import KubeContext
@@ -19,25 +19,8 @@ class BashSession:
19
19
  def pwd(self, state: 'ReplState'):
20
20
  command = f'cat /tmp/.qing-{self.session_id}'
21
21
 
22
- if state.pod:
23
- rs = [CassandraNodes.exec(state.pod, state.namespace, command, show_out=False)]
24
- elif state.sts:
25
- rs = CassandraClusters.exec(state.sts, state.namespace, command, action='bash', show_out=False)
26
-
27
- dir = None
28
- for r in rs:
29
- if r.exit_code(): # if fails to read the session file, ignore
30
- continue
31
-
32
- dir0 = r.stdout.strip(' \r\n')
33
- if dir:
34
- if dir != dir0:
35
- log2('Inconsitent working dir found across multiple pods.')
36
- return None
37
- else:
38
- dir = dir0
39
-
40
- return dir
22
+ with device(state) as pods:
23
+ return pods.exec(command, action='bash', show_out=False)
41
24
 
42
25
  class RequiredState(Enum):
43
26
  CLUSTER = 'cluster'
@@ -46,20 +29,22 @@ class RequiredState(Enum):
46
29
  NAMESPACE = 'namespace'
47
30
  PG_DATABASE = 'pg_database'
48
31
  APP_APP = 'app_app'
32
+ EXPORT_DB = 'export_db'
49
33
 
50
34
  class ReplState:
51
35
  A = 'a'
52
36
  C = 'c'
53
37
  L = 'l'
54
38
  P = 'p'
39
+ X = 'x'
55
40
 
56
- ANY = [A, C, L, P]
57
- NON_L = [A, C, P]
41
+ ANY = [A, C, L, P, X]
42
+ NON_L = [A, C, P, X]
58
43
 
59
44
  def __init__(self, device: str = None,
60
45
  sts: str = None, pod: str = None, namespace: str = None, ns_sts: str = None,
61
46
  pg_path: str = None,
62
- app_env: str = None, app_app: str = None,
47
+ app_env: str = None, app_app: str = None, app_pod: str = None,
63
48
  in_repl = False, bash_session: BashSession = None, remote_dir = None):
64
49
  self.namespace = KubeContext.in_cluster_namespace()
65
50
 
@@ -69,12 +54,15 @@ class ReplState:
69
54
  self.pg_path = pg_path
70
55
  self.app_env = app_env
71
56
  self.app_app = app_app
57
+ self.app_pod = app_pod
72
58
  if namespace:
73
59
  self.namespace = namespace
74
60
  self.in_repl = in_repl
75
61
  self.bash_session = bash_session
76
62
  self.remote_dir = remote_dir
77
- # self.wait_log_flag = False
63
+ self.original_state: ReplState = None
64
+
65
+ self.export_session: str = None
78
66
 
79
67
  if ns_sts:
80
68
  nn = ns_sts.split('@')
@@ -89,13 +77,51 @@ class ReplState:
89
77
  def __hash__(self):
90
78
  return hash((self.sts, self.pod))
91
79
 
80
+ def __str__(self):
81
+ msg = ''
82
+ if self.device == ReplState.P:
83
+ msg = f'{ReplState.P}:'
84
+ host, database = self.pg_host_n_database()
85
+ if database:
86
+ msg += database
87
+ elif host:
88
+ msg += host
89
+ elif self.device == ReplState.A:
90
+ msg = f'{ReplState.A}:'
91
+ if self.app_env:
92
+ msg += self.app_env
93
+ if self.app_app:
94
+ msg += f'/{self.app_app}'
95
+ if self.app_pod:
96
+ # azops88-c3-c3-k8sdeploy-appleader-001-79957cf5b6-9k4bw
97
+ group = re.match(r".*?-.*?-.*?-.*?-(.*?-.*?)-.*", self.app_pod)
98
+ msg += '/' + group[1]
99
+ elif self.device == ReplState.L:
100
+ msg = f'{ReplState.L}:'
101
+ elif self.device == ReplState.X:
102
+ msg = f'{ReplState.X}:'
103
+ if self.export_session:
104
+ msg += self.export_session
105
+ else:
106
+ msg = f'{ReplState.C}:'
107
+ if self.pod:
108
+ # cs-d0767a536f-cs-d0767a536f-default-sts-0
109
+ group = re.match(r".*?-.*?-(.*)", self.pod)
110
+ msg += group[1]
111
+ elif self.sts:
112
+ # cs-d0767a536f-cs-d0767a536f-default-sts
113
+ group = re.match(r".*?-.*?-(.*)", self.sts)
114
+ msg += group[1]
115
+
116
+ return msg
117
+
92
118
  def apply_args(self, args: list[str], cmd: list[str] = None, resolve_pg = True, args_to_check = 6) -> tuple['ReplState', list[str]]:
93
119
  state = self
94
120
 
95
121
  new_args = []
96
122
  for index, arg in enumerate(args):
97
123
  if index < args_to_check:
98
- state = copy.copy(state)
124
+ state = copy(state)
99
125
 
100
126
  s, n = KubeContext.is_sts_name(arg)
101
127
  if s:
@@ -133,9 +159,9 @@ class ReplState:
133
159
  new_args = []
134
160
  for index, arg in enumerate(args):
135
161
  if index < 6:
136
- state = copy.copy(state)
162
+ state = copy(state)
137
163
 
138
- groups = re.match(r'^([a|c|l|p]):(.*)$', arg)
164
+ groups = re.match(r'^([a|c|l|p|x]):(.*)$', arg)
139
165
  if groups:
140
166
  if groups[1] == 'p':
141
167
  state.device = 'p'
@@ -154,6 +180,8 @@ class ReplState:
154
180
  state.namespace = ns
155
181
  elif groups[1] == 'l':
156
182
  state.device = 'l'
183
+ elif groups[1] == 'x':
184
+ state.device = 'x'
157
185
  else:
158
186
  state.device = 'a'
159
187
  if path := groups[2]:
@@ -196,8 +224,8 @@ class ReplState:
196
224
 
197
225
  return False
198
226
 
199
- devices = [r for r in required if r in [ReplState.L, ReplState.A, ReplState.C, ReplState.P]]
200
- non_devices = [r for r in required if r not in [ReplState.L, ReplState.A, ReplState.C, ReplState.P]]
227
+ devices = [r for r in required if r in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X]]
228
+ non_devices = [r for r in required if r not in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X]]
201
229
 
202
230
  first_error: Callable = None
203
231
  for r in non_devices:
@@ -285,8 +313,8 @@ class ReplState:
285
313
  if self.device != ReplState.P:
286
314
  return (False, None)
287
315
 
288
- pg: PostgresContext = PostgresContext.apply(self.namespace, self.pg_path)
289
- if not pg.db:
316
+ _, database = self.pg_host_n_database()
317
+ if not database:
290
318
  def error():
291
319
  if self.in_repl:
292
320
  log2('cd to a database first.')
@@ -310,7 +338,24 @@ class ReplState:
310
338
  display_help()
311
339
  return (False, error)
312
340
 
313
- elif required in [ReplState.L, ReplState.A, ReplState.C, ReplState.P] and self.device != required:
341
+ elif required == RequiredState.EXPORT_DB:
342
+ if self.device not in [ReplState.C, ReplState.X]:
343
+ return (False, None)
344
+
345
+ if not self.export_session:
346
+ def error():
347
+ if self.in_repl:
348
+ if self.device == ReplState.C:
349
+ log2("Select an export database first with 'use' command.")
350
+ else:
351
+ log2('cd to an export database first.')
352
+ else:
353
+ log2('* export database is missing.')
354
+ log2()
355
+ display_help()
356
+ return (False, error)
357
+
358
+ elif required in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X] and self.device != required:
314
359
  def error():
315
360
  if self.in_repl:
316
361
  log2(f'Switch to {required}: first.')
@@ -339,13 +384,89 @@ class ReplState:
339
384
  return Secrets.get_user_pass(self.pod if self.pod else self.sts, self.namespace, secret_path=secret_path)
340
385
 
341
386
  def enter_bash(self, bash_session: BashSession):
387
+ self.push()
388
+
342
389
  self.bash_session = bash_session
343
- if self.device != ReplState.C:
344
- self.device = ReplState.C
345
- log2(f'Moved to {ReplState.C}: automatically. Will move back to {ReplState.P}: when you exit the bash session.')
346
390
 
347
391
  def exit_bash(self):
348
- if self.bash_session and self.bash_session.device:
349
- self.device = self.bash_session.device
392
+ self.pop()
393
+ self.bash_session = None
394
+
395
+ def push(self):
396
+ if not self.original_state:
397
+ self.original_state = copy(self)
398
+
399
+ def pop(self):
400
+ if o := self.original_state:
401
+ self.device = o.device
402
+ self.sts = o.sts
403
+ self.pod = o.pod
404
+ self.pg_path = o.pg_path
405
+ self.app_env = o.app_env
406
+ self.app_app = o.app_app
407
+ self.app_pod = o.app_pod
408
+ # self.export_session = o.export_session
409
+ self.namespace = o.namespace
410
+
411
+ self.original_state = None
412
+
413
+ def pg_host_n_database(self):
414
+ host = None
415
+ database = None
416
+
417
+ if self.pg_path:
418
+ host_n_db = self.pg_path.split('/')
419
+ host = host_n_db[0]
420
+ if len(host_n_db) > 1:
421
+ database = host_n_db[1]
422
+
423
+ return host, database
424
+
425
+ class DevicePodService:
426
+ def __init__(self, handler: 'DeviceExecHandler'):
427
+ self.handler = handler
428
+
429
+ def exec(self, command: str, action='bash', show_out = True):
430
+ state = self.handler.state
431
+
432
+ rs = None
433
+ if state.device == ReplState.A and state.app_app:
434
+ if state.app_pod:
435
+ rs = [AppPods.exec(state.app_pod, state.namespace, command, show_out=show_out)]
436
+ else:
437
+ pods = AppPods.pod_names(state.namespace, state.app_env, state.app_app)
438
+ rs = AppClusters.exec(pods, state.namespace, command, show_out=show_out)
439
+ elif state.pod:
440
+ rs = [CassandraNodes.exec(state.pod, state.namespace, command, show_out=show_out)]
441
+ elif state.sts:
442
+ rs = CassandraClusters.exec(state.sts, state.namespace, command, action=action, show_out=show_out)
443
+ # assume that pg-agent or ops pod is single pod
444
+
445
+ dir = None
446
+ if rs:
447
+ for r in rs:
448
+ if r.exit_code(): # if fails to read the session file, ignore
449
+ continue
450
+
451
+ dir0 = r.stdout.strip(' \r\n')
452
+ if dir:
453
+ if dir != dir0:
454
+ log2('Inconsitent working dir found across multiple pods.')
455
+ return None
456
+ else:
457
+ dir = dir0
458
+
459
+ return dir
460
+
461
+ class DeviceExecHandler:
462
+ def __init__(self, state: ReplState):
463
+ self.state = state
464
+
465
+ def __enter__(self):
466
+ return DevicePodService(self)
467
+
468
+ def __exit__(self, exc_type, exc_val, exc_tb):
469
+ return False
350
470
 
351
- self.bash_session = None
471
+ def device(state: ReplState):
472
+ return DeviceExecHandler(state)
adam/sql/sql_completer.py CHANGED
@@ -1,7 +1,8 @@
1
+ from enum import Enum
1
2
  from typing import Callable
2
3
 
3
4
  import sqlparse
4
- from sqlparse.sql import Statement, Token
5
+ from sqlparse.sql import Token
5
6
 
6
7
  from adam.sql.term_completer import TermCompleter
7
8
  from adam.utils_repl.automata_completer import AutomataCompleter
@@ -12,41 +13,42 @@ __all__ = [
12
13
  "SqlCompleter",
13
14
  ]
14
15
 
15
- def default_columns(_: list[str]):
16
+ def default_columns(x: list[str]):
16
17
  return 'id,x.,y.,z.'.split(',')
17
18
 
19
+ class SqlVariant(Enum):
20
+ SQL = 'sql'
21
+ CQL = 'cql'
22
+ ATHENA = 'athena'
23
+
18
24
  class SqlCompleter(AutomataCompleter[Token]):
19
25
  def tokens(self, text: str) -> list[Token]:
20
26
  tokens = []
21
27
 
22
28
  stmts = sqlparse.parse(text)
23
- if not stmts:
24
- tokens = []
25
- else:
26
- statement: Statement = stmts[0]
27
- tokens = statement.tokens
29
+ if stmts:
30
+ for stmt in stmts:
31
+ tokens.extend(stmt.tokens)
28
32
 
29
33
  return tokens
30
34
 
31
35
  def __init__(self,
32
36
  tables: Callable[[], list[str]],
33
37
  dml: str = None,
34
- columns: Callable[[list[str]], list[str]] = default_columns,
35
- partition_columns: Callable[[list[str]], list[str]] = lambda x: [],
36
- table_props: Callable[[], dict[str,list[str]]] = lambda: [],
37
- variant = 'sql',
38
+ expandables: dict = {},
39
+ variant: SqlVariant = SqlVariant.SQL,
38
40
  debug = False):
39
41
  machine = SqlStateMachine(debug=debug)
40
- if variant == 'cql':
42
+ if variant == SqlVariant.CQL:
41
43
  machine = CqlStateMachine(debug=debug)
42
- elif variant == 'athena':
44
+ elif variant == SqlVariant.ATHENA:
43
45
  machine = AthenaStateMachine(debug=debug)
44
46
  super().__init__(machine, dml, debug)
45
47
 
46
48
  self.tables = tables
47
- self.columns = columns
48
- self.partition_columns = partition_columns
49
- self.table_props = table_props
49
+ if 'columns' not in expandables:
50
+ expandables['columns'] = default_columns
51
+ self.expandables = expandables
50
52
  self.variant = variant
51
53
  self.debug = debug
52
54
 
@@ -63,31 +65,54 @@ class SqlCompleter(AutomataCompleter[Token]):
63
65
  def _terms(self, state: State, word: str) -> list[str]:
64
66
  terms = []
65
67
 
66
- if word == 'tables':
68
+ if word.startswith('`') and word.endswith('`'):
69
+ terms.append(word.strip('`'))
70
+ elif word == 'tables':
67
71
  terms.extend(self.tables())
68
- elif word == '`tables`':
69
- terms.append('tables')
70
72
  elif word == 'columns':
71
- terms.extend(self.columns([]))
73
+ if 'last_name' in state.context and (n := state.context['last_name']):
74
+ if 'last_namespace' in state.context and (ns := state.context['last_namespace']):
75
+ n = f'{ns}.{n}'
76
+ terms.extend(self._call_expandable(word, [n]))
77
+ else:
78
+ terms.extend(self._call_expandable(word, []))
72
79
  elif word == 'partition-columns':
73
- terms.extend(self.partition_columns([]))
80
+ terms.extend(self._call_expandable(word, []))
74
81
  elif word == 'table-props':
75
- terms.extend(self.table_props().keys())
82
+ terms.extend(self._call_expandable(word).keys())
76
83
  elif word == 'table-prop-values':
77
84
  if 'last_name' in state.context and state.context['last_name']:
78
- terms.extend(self.table_props()[state.context['last_name']])
85
+ table_props = self._call_expandable('table-props')
86
+ terms.extend(table_props[state.context['last_name']])
79
87
  elif word == 'single':
80
88
  terms.append("'")
81
89
  elif word == 'comma':
82
90
  terms.append(",")
91
+ elif word in self.machine.expandable_names():
92
+ terms.extend(self._call_expandable(word))
83
93
  else:
84
94
  terms.append(word)
85
95
 
86
96
  return terms
87
97
 
88
- def completions_for_nesting(self):
98
+ def _call_expandable(self, name: str, *args):
99
+ if name in self.expandables:
100
+ c = self.expandables[name]
101
+ if args:
102
+ return c(args)
103
+ else:
104
+ return c()
105
+
106
+ return []
107
+
108
+ def completions_for_nesting(self, dml: str = None):
109
+ if dml:
110
+ return {dml: SqlCompleter(self.tables, dml, expandables=self.expandables, variant=self.variant)}
111
+
89
112
  return {
90
- word : SqlCompleter(self.tables, word, columns=self.columns, partition_columns=self.partition_columns,
91
- table_props=self.table_props, variant=self.variant)
113
+ word: SqlCompleter(self.tables, word, expandables=self.expandables, variant=self.variant)
92
114
  for word in self.machine.suggestions[''].strip(' ').split(',')
93
- }
115
+ }
116
+
117
+ def __str__(self):
118
+ return f'{self.variant}, {self.first_term}'