kaqing 2.0.50__py3-none-any.whl → 2.0.110__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 (123) hide show
  1. adam/apps.py +2 -2
  2. adam/batch.py +11 -15
  3. adam/checks/check_utils.py +4 -4
  4. adam/checks/compactionstats.py +1 -1
  5. adam/checks/cpu.py +2 -2
  6. adam/checks/disk.py +1 -1
  7. adam/checks/gossip.py +1 -1
  8. adam/checks/memory.py +3 -3
  9. adam/checks/status.py +1 -1
  10. adam/commands/alter_tables.py +3 -14
  11. adam/commands/app.py +2 -2
  12. adam/commands/app_ping.py +2 -2
  13. adam/commands/audit/audit.py +85 -0
  14. adam/commands/audit/audit_repair_tables.py +76 -0
  15. adam/commands/audit/audit_run.py +57 -0
  16. adam/commands/audit/show_last10.py +50 -0
  17. adam/commands/audit/show_slow10.py +49 -0
  18. adam/commands/audit/show_top10.py +48 -0
  19. adam/commands/audit/utils_show_top10.py +59 -0
  20. adam/commands/bash.py +76 -13
  21. adam/commands/cd.py +22 -13
  22. adam/commands/check.py +6 -0
  23. adam/commands/cli_commands.py +3 -3
  24. adam/commands/command.py +15 -11
  25. adam/commands/commands_utils.py +4 -5
  26. adam/commands/cql/cql_completions.py +7 -5
  27. adam/commands/cql/cql_utils.py +13 -10
  28. adam/commands/cql/cqlsh.py +6 -3
  29. adam/commands/deploy/code_utils.py +2 -2
  30. adam/commands/deploy/deploy.py +7 -1
  31. adam/commands/deploy/deploy_pg_agent.py +2 -2
  32. adam/commands/deploy/deploy_pod.py +6 -6
  33. adam/commands/deploy/deploy_utils.py +2 -2
  34. adam/commands/deploy/undeploy.py +7 -1
  35. adam/commands/deploy/undeploy_pg_agent.py +2 -2
  36. adam/commands/deploy/undeploy_pod.py +4 -4
  37. adam/commands/devices.py +29 -0
  38. adam/commands/help.py +10 -7
  39. adam/commands/issues.py +6 -0
  40. adam/commands/login.py +6 -3
  41. adam/commands/logs.py +2 -1
  42. adam/commands/ls.py +30 -24
  43. adam/commands/medusa/medusa_backup.py +2 -2
  44. adam/commands/medusa/medusa_restore.py +2 -2
  45. adam/commands/medusa/medusa_show_backupjobs.py +3 -2
  46. adam/commands/medusa/medusa_show_restorejobs.py +2 -2
  47. adam/commands/nodetool.py +5 -3
  48. adam/commands/postgres/postgres.py +3 -3
  49. adam/commands/postgres/{postgres_session.py → postgres_context.py} +29 -30
  50. adam/commands/postgres/postgres_utils.py +5 -5
  51. adam/commands/postgres/psql_completions.py +2 -3
  52. adam/commands/preview_table.py +17 -32
  53. adam/commands/pwd.py +5 -2
  54. adam/commands/reaper/reaper.py +3 -0
  55. adam/commands/reaper/reaper_restart.py +1 -1
  56. adam/commands/reaper/reaper_session.py +1 -1
  57. adam/commands/repair/repair.py +3 -3
  58. adam/commands/repair/repair_log.py +1 -1
  59. adam/commands/repair/repair_run.py +2 -2
  60. adam/commands/repair/repair_scan.py +1 -1
  61. adam/commands/repair/repair_stop.py +1 -1
  62. adam/commands/report.py +6 -0
  63. adam/commands/restart.py +2 -2
  64. adam/commands/rollout.py +1 -1
  65. adam/commands/show/show.py +5 -2
  66. adam/commands/show/show_app_actions.py +3 -0
  67. adam/commands/show/show_app_id.py +1 -1
  68. adam/commands/show/show_app_queues.py +3 -2
  69. adam/commands/show/show_cassandra_status.py +3 -3
  70. adam/commands/show/show_cassandra_version.py +3 -3
  71. adam/commands/show/show_host.py +33 -0
  72. adam/commands/show/show_login.py +3 -0
  73. adam/commands/show/show_processes.py +1 -1
  74. adam/commands/show/show_repairs.py +2 -2
  75. adam/commands/show/show_storage.py +1 -1
  76. adam/commands/watch.py +1 -1
  77. adam/config.py +2 -1
  78. adam/embedded_params.py +1 -1
  79. adam/pod_exec_result.py +7 -2
  80. adam/repl.py +141 -89
  81. adam/repl_commands.py +21 -20
  82. adam/repl_state.py +167 -39
  83. adam/sql/sql_completer.py +89 -49
  84. adam/sql/sql_state_machine.py +518 -0
  85. adam/sql/term_completer.py +76 -0
  86. adam/sso/cred_cache.py +1 -1
  87. adam/sso/idp.py +1 -1
  88. adam/utils.py +0 -1
  89. adam/utils_audits.py +193 -0
  90. adam/{k8s_utils → utils_k8s}/cassandra_clusters.py +6 -8
  91. adam/{k8s_utils → utils_k8s}/cassandra_nodes.py +11 -4
  92. adam/{k8s_utils → utils_k8s}/deployment.py +2 -2
  93. adam/{k8s_utils → utils_k8s}/pods.py +33 -9
  94. adam/{k8s_utils → utils_k8s}/secrets.py +4 -0
  95. adam/{k8s_utils → utils_k8s}/statefulsets.py +4 -4
  96. adam/utils_net.py +24 -0
  97. adam/version.py +1 -1
  98. {kaqing-2.0.50.dist-info → kaqing-2.0.110.dist-info}/METADATA +1 -1
  99. kaqing-2.0.110.dist-info/RECORD +187 -0
  100. adam/commands/cql/cql_table_completer.py +0 -16
  101. adam/commands/describe/describe.py +0 -46
  102. adam/commands/describe/describe_keyspace.py +0 -60
  103. adam/commands/describe/describe_keyspaces.py +0 -50
  104. adam/commands/describe/describe_table.py +0 -60
  105. adam/commands/describe/describe_tables.py +0 -50
  106. adam/commands/postgres/psql_table_completer.py +0 -18
  107. adam/sql/any_completer.py +0 -84
  108. adam/sql/sql_utils.py +0 -5
  109. adam/sql/table_name_completer.py +0 -17
  110. kaqing-2.0.50.dist-info/RECORD +0 -185
  111. /adam/commands/{describe → audit}/__init__.py +0 -0
  112. /adam/{k8s_utils → utils_k8s}/__init__.py +0 -0
  113. /adam/{k8s_utils → utils_k8s}/config_maps.py +0 -0
  114. /adam/{k8s_utils → utils_k8s}/custom_resources.py +0 -0
  115. /adam/{k8s_utils → utils_k8s}/ingresses.py +0 -0
  116. /adam/{k8s_utils → utils_k8s}/jobs.py +0 -0
  117. /adam/{k8s_utils → utils_k8s}/kube_context.py +0 -0
  118. /adam/{k8s_utils → utils_k8s}/service_accounts.py +0 -0
  119. /adam/{k8s_utils → utils_k8s}/services.py +0 -0
  120. /adam/{k8s_utils → utils_k8s}/volumes.py +0 -0
  121. {kaqing-2.0.50.dist-info → kaqing-2.0.110.dist-info}/WHEEL +0 -0
  122. {kaqing-2.0.50.dist-info → kaqing-2.0.110.dist-info}/entry_points.txt +0 -0
  123. {kaqing-2.0.50.dist-info → kaqing-2.0.110.dist-info}/top_level.txt +0 -0
adam/repl_commands.py CHANGED
@@ -1,6 +1,9 @@
1
1
  from adam.commands.alter_tables import AlterTables
2
2
  from adam.commands.app import App
3
3
  from adam.commands.app_ping import AppPing
4
+ from adam.commands.audit.audit import Audit
5
+ from adam.commands.audit.audit_repair_tables import AuditRepairTables
6
+ from adam.commands.audit.audit_run import AuditRun
4
7
  from adam.commands.deploy.code_start import CodeStart
5
8
  from adam.commands.deploy.code_stop import CodeStop
6
9
  from adam.commands.deploy.deploy import Deploy
@@ -11,11 +14,6 @@ from adam.commands.deploy.undeploy import Undeploy
11
14
  from adam.commands.deploy.undeploy_frontend import UndeployFrontend
12
15
  from adam.commands.deploy.undeploy_pg_agent import UndeployPgAgent
13
16
  from adam.commands.deploy.undeploy_pod import UndeployPod
14
- from adam.commands.describe.describe import Describe
15
- from adam.commands.describe.describe_keyspace import DescribeKeyspace
16
- from adam.commands.describe.describe_keyspaces import DescribeKeyspaces
17
- from adam.commands.describe.describe_table import DescribeTable
18
- from adam.commands.describe.describe_tables import DescribeTables
19
17
  from adam.commands.shell import Shell
20
18
  from adam.commands.show.show_app_queues import ShowAppQueues
21
19
  from adam.commands.cp import ClipboardCopy
@@ -24,7 +22,7 @@ from adam.commands.cd import Cd
24
22
  from adam.commands.check import Check
25
23
  from adam.commands.command import Command
26
24
  from adam.commands.cql.cqlsh import Cqlsh
27
- from adam.commands.devices import DeviceApp, DeviceCass, DevicePostgres
25
+ from adam.commands.devices import DeviceApp, DeviceAuditLog, DeviceCass, DevicePostgres
28
26
  from adam.commands.exit import Exit
29
27
  from adam.commands.medusa.medusa import Medusa
30
28
  from adam.commands.param_get import GetParam
@@ -46,6 +44,7 @@ from adam.commands.show.show_app_id import ShowAppId
46
44
  from adam.commands.show.show_cassandra_status import ShowCassandraStatus
47
45
  from adam.commands.show.show_cassandra_version import ShowCassandraVersion
48
46
  from adam.commands.show.show_commands import ShowKubectlCommands
47
+ from adam.commands.show.show_host import ShowHost
49
48
  from adam.commands.show.show_login import ShowLogin
50
49
  from adam.commands.show.show_params import ShowParams
51
50
  from adam.commands.show.show_processes import ShowProcesses
@@ -56,10 +55,10 @@ from adam.commands.watch import Watch
56
55
 
57
56
  class ReplCommands:
58
57
  def repl_cmd_list() -> list[Command]:
59
- cmds: list[Command] = ReplCommands.navigation() + ReplCommands.cassandra_check() + ReplCommands.cassandra_ops() + \
60
- ReplCommands.tools() + ReplCommands.app() + ReplCommands.exit()
58
+ cmds: list[Command] = ReplCommands.navigation() + ReplCommands.cassandra_ops() + ReplCommands.postgres_ops() + \
59
+ ReplCommands.app_ops() + ReplCommands.audit_ops() + ReplCommands.tools() + ReplCommands.exit()
61
60
 
62
- intermediate_cmds: list[Command] = [App(), Reaper(), Repair(), Deploy(), Describe(), Show(), Undeploy()]
61
+ intermediate_cmds: list[Command] = [App(), Audit(), Reaper(), Repair(), Deploy(), Show(), Undeploy()]
63
62
  ic = [c.command() for c in intermediate_cmds]
64
63
  # 1. dedup commands
65
64
  deduped = []
@@ -76,22 +75,24 @@ class ReplCommands:
76
75
  return deduped
77
76
 
78
77
  def navigation() -> list[Command]:
79
- return [Ls(), PreviewTable(), DeviceApp(), DevicePostgres(), DeviceCass(), Cd(), Pwd(), ClipboardCopy(),
80
- GetParam(), SetParam(), ShowParams(), ShowKubectlCommands(), ShowLogin(), ShowAdam()]
81
-
82
- def cassandra_check() -> list[Command]:
83
- return Describe.cmd_list() + [ShowCassandraStatus(),
84
- ShowCassandraVersion(), ShowRepairs(), ShowStorage(), ShowProcesses(), Check(), Issues(), NodeTool(), Report()]
78
+ return [Ls(), PreviewTable(), DeviceApp(), DevicePostgres(), DeviceCass(), DeviceAuditLog(), Cd(), Pwd(), ClipboardCopy(),
79
+ GetParam(), SetParam(), ShowParams(), ShowKubectlCommands(), ShowLogin(), ShowAdam(), ShowHost()]
85
80
 
86
81
  def cassandra_ops() -> list[Command]:
87
- return [AlterTables()] + Medusa.cmd_list() + [Restart(), RollOut(), Watch()] + Reaper.cmd_list() + Repair.cmd_list()
82
+ return [Cqlsh(), ShowCassandraStatus(), ShowCassandraVersion(), ShowRepairs(), ShowStorage(), ShowProcesses(), Check(), Issues(), NodeTool(), Report()] + \
83
+ [AlterTables()] + Medusa.cmd_list() + [Restart(), RollOut(), Watch()] + Reaper.cmd_list() + Repair.cmd_list()
88
84
 
89
- def tools() -> list[Command]:
90
- return [Cqlsh(), Postgres(), Bash(), Shell(), CodeStart(), CodeStop(), DeployFrontend(), UndeployFrontend(),
91
- DeployPod(), UndeployPod(), DeployPgAgent(), UndeployPgAgent()]
85
+ def postgres_ops() -> list[Command]:
86
+ return [Postgres(), DeployPgAgent(), UndeployPgAgent()]
92
87
 
93
- def app() -> list[Command]:
88
+ def app_ops() -> list[Command]:
94
89
  return [ShowAppActions(), ShowAppId(), ShowAppQueues(), AppPing(), App()]
95
90
 
91
+ def audit_ops() -> list[Command]:
92
+ return [Audit()] + Audit.cmd_list()
93
+
94
+ def tools() -> list[Command]:
95
+ return [Bash(), Shell(), CodeStart(), CodeStop(), DeployFrontend(), UndeployFrontend(), DeployPod(), UndeployPod()]
96
+
96
97
  def exit() -> list[Command]:
97
98
  return [Exit()]
adam/repl_state.py CHANGED
@@ -1,11 +1,14 @@
1
1
  import copy
2
2
  from enum import Enum
3
-
4
- from adam.commands.postgres.postgres_session import PostgresSession
5
- from adam.k8s_utils.cassandra_clusters import CassandraClusters
6
- from adam.k8s_utils.cassandra_nodes import CassandraNodes
7
- from adam.k8s_utils.kube_context import KubeContext
8
- from adam.k8s_utils.secrets import Secrets
3
+ import re
4
+ import traceback
5
+ from typing import Callable
6
+
7
+ from adam.commands.postgres.postgres_context import PostgresContext
8
+ from adam.utils_k8s.cassandra_clusters import CassandraClusters
9
+ from adam.utils_k8s.cassandra_nodes import CassandraNodes
10
+ from adam.utils_k8s.kube_context import KubeContext
11
+ from adam.utils_k8s.secrets import Secrets
9
12
  from adam.utils import display_help, log2, random_alphanumeric
10
13
 
11
14
  class BashSession:
@@ -47,8 +50,12 @@ class RequiredState(Enum):
47
50
  class ReplState:
48
51
  A = 'a'
49
52
  C = 'c'
53
+ L = 'l'
50
54
  P = 'p'
51
55
 
56
+ ANY = [A, C, L, P]
57
+ NON_L = [A, C, P]
58
+
52
59
  def __init__(self, device: str = None,
53
60
  sts: str = None, pod: str = None, namespace: str = None, ns_sts: str = None,
54
61
  pg_path: str = None,
@@ -82,12 +89,12 @@ class ReplState:
82
89
  def __hash__(self):
83
90
  return hash((self.sts, self.pod))
84
91
 
85
- def apply_args(self, args: list[str], cmd: list[str] = None, resolve_pg = True) -> tuple['ReplState', list[str]]:
92
+ def apply_args(self, args: list[str], cmd: list[str] = None, resolve_pg = True, args_to_check = 6) -> tuple['ReplState', list[str]]:
86
93
  state = self
87
94
 
88
95
  new_args = []
89
96
  for index, arg in enumerate(args):
90
- if index < 6:
97
+ if index < args_to_check:
91
98
  state = copy.copy(state)
92
99
 
93
100
  s, n = KubeContext.is_sts_name(arg)
@@ -128,8 +135,32 @@ class ReplState:
128
135
  if index < 6:
129
136
  state = copy.copy(state)
130
137
 
131
- if arg in [f'{ReplState.A}:', f'{ReplState.C}:', f'{ReplState.P}:']:
132
- state.device = arg.strip(':')
138
+ groups = re.match(r'^([a|c|l|p]):(.*)$', arg)
139
+ if groups:
140
+ if groups[1] == 'p':
141
+ state.device = 'p'
142
+ state.pg_path = groups[2]
143
+ elif groups[1] == 'c':
144
+ state.device = 'c'
145
+ if path := groups[2]:
146
+ p_and_ns = path.split('@')
147
+ sts_and_pod = p_and_ns[0].split('/')
148
+ state.sts = sts_and_pod[0]
149
+ if len(sts_and_pod) > 1:
150
+ state.pod = sts_and_pod[1]
151
+ if len(p_and_ns) > 1:
152
+ state.namespace = p_and_ns[1]
153
+ elif ns := KubeContext.in_cluster_namespace():
154
+ state.namespace = ns
155
+ elif groups[1] == 'l':
156
+ state.device = 'l'
157
+ else:
158
+ state.device = 'a'
159
+ if path := groups[2]:
160
+ env_and_app = path.split('/')
161
+ state.app_env = env_and_app[0]
162
+ if len(env_and_app) > 1:
163
+ state.app_app = env_and_app[1]
133
164
  else:
134
165
  new_args.append(arg)
135
166
  else:
@@ -140,72 +171,169 @@ class ReplState:
140
171
 
141
172
  return (state, new_args)
142
173
 
143
- def validate(self, required: RequiredState = None, pg_required: RequiredState = None, app_required: RequiredState = None):
144
- if not pg_required and not app_required:
145
- if required == RequiredState.CLUSTER:
146
- if not self.namespace or not self.sts:
174
+ def validate(self, required: list[RequiredState] = [], show_err = True):
175
+ if not required:
176
+ return True
177
+
178
+ def default_err():
179
+ if self.in_repl:
180
+ log2(f'Not a valid command on {self.device}: drive.')
181
+ else:
182
+ log2('* on a wrong device.')
183
+ log2()
184
+ display_help()
185
+
186
+ if type(required) is not list:
187
+ valid, err = self._validate(required)
188
+ if valid:
189
+ return True
190
+
191
+ if show_err:
192
+ if err:
193
+ err()
194
+ else:
195
+ default_err()
196
+
197
+ return False
198
+
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]]
201
+
202
+ first_error: Callable = None
203
+ for r in non_devices:
204
+ valid, err = self._validate(r)
205
+ if valid:
206
+ return True
207
+
208
+ if not first_error:
209
+ first_error = err
210
+
211
+ if devices:
212
+ valid, err = self._validate_device(devices)
213
+ if valid:
214
+ return True
215
+
216
+ if not first_error:
217
+ first_error = err
218
+
219
+ if show_err and first_error:
220
+ if first_error:
221
+ first_error()
222
+ else:
223
+ default_err()
224
+
225
+ return False
226
+
227
+ def _validate(self, required: RequiredState):
228
+ if required == RequiredState.CLUSTER:
229
+ if self.device != ReplState.C:
230
+ return (False, None)
231
+
232
+ if not self.namespace or not self.sts:
233
+ def error():
147
234
  if self.in_repl:
148
235
  log2('cd to a Cassandra cluster first.')
149
236
  else:
150
237
  log2('* Cassandra cluster is missing.')
151
238
  log2()
152
239
  display_help()
240
+ return (False, error)
153
241
 
154
- return False
155
- elif required == RequiredState.POD:
156
- if not self.namespace or not self.pod:
242
+ elif required == RequiredState.POD:
243
+ if self.device != ReplState.C:
244
+ return (False, None)
245
+
246
+ if not self.namespace or not self.pod:
247
+ def error():
157
248
  if self.in_repl:
158
249
  log2('cd to a pod first.')
159
250
  else:
160
251
  log2('* Pod is missing.')
161
252
  log2()
162
253
  display_help()
254
+ return (False, error)
255
+
256
+ elif required == RequiredState.CLUSTER_OR_POD:
257
+ if self.device != ReplState.C:
258
+ return (False, None)
163
259
 
164
- return False
165
- elif required == RequiredState.CLUSTER_OR_POD:
166
- if not self.namespace or not self.sts and not self.pod:
260
+ if not self.namespace or not self.sts and not self.pod:
261
+ def error():
167
262
  if self.in_repl:
168
263
  log2('cd to a Cassandra cluster first.')
169
264
  else:
170
265
  log2('* Cassandra cluster or pod is missing.')
171
266
  log2()
172
267
  display_help()
268
+ return (False, error)
269
+
270
+ elif required == RequiredState.NAMESPACE:
271
+ if self.device != ReplState.C:
272
+ return (False, None)
173
273
 
174
- return False
175
- elif required == RequiredState.NAMESPACE:
176
- if not self.namespace:
274
+ if not self.namespace:
275
+ def error():
177
276
  if self.in_repl:
178
277
  log2('Namespace is required.')
179
278
  else:
180
279
  log2('* namespace is missing.')
181
280
  log2()
182
281
  display_help()
282
+ return (False, error)
183
283
 
184
- return False
284
+ elif required == RequiredState.PG_DATABASE:
285
+ if self.device != ReplState.P:
286
+ return (False, None)
185
287
 
186
- if pg_required == RequiredState.PG_DATABASE:
187
- pg = PostgresSession(self.namespace, self.pg_path)
288
+ pg: PostgresContext = PostgresContext.apply(self.namespace, self.pg_path)
188
289
  if not pg.db:
290
+ def error():
291
+ if self.in_repl:
292
+ log2('cd to a database first.')
293
+ else:
294
+ log2('* database is missing.')
295
+ log2()
296
+ display_help()
297
+ return (False, error)
298
+
299
+ elif required == RequiredState.APP_APP:
300
+ if self.device != ReplState.A:
301
+ return (False, None)
302
+
303
+ if not self.app_app:
304
+ def error():
305
+ if self.in_repl:
306
+ log2('cd to an app first.')
307
+ else:
308
+ log2('* app is missing.')
309
+ log2()
310
+ display_help()
311
+ return (False, error)
312
+
313
+ elif required in [ReplState.L, ReplState.A, ReplState.C, ReplState.P] and self.device != required:
314
+ def error():
189
315
  if self.in_repl:
190
- log2('cd to a database first.')
316
+ log2(f'Switch to {required}: first.')
191
317
  else:
192
- log2('* database is missing.')
318
+ log2('* on a wrong device.')
193
319
  log2()
194
320
  display_help()
321
+ return (False, error)
195
322
 
196
- return False
197
-
198
- if app_required == RequiredState.APP_APP and not self.app_app:
199
- if self.in_repl:
200
- log2('cd to an app first.')
201
- else:
202
- log2('* app is missing.')
203
- log2()
204
- display_help()
323
+ return (True, None)
205
324
 
206
- return False
325
+ def _validate_device(self, devices: list[RequiredState]):
326
+ if self.device not in devices:
327
+ def error():
328
+ if self.in_repl:
329
+ log2(f'Not a valid command on {self.device}: drive.')
330
+ else:
331
+ log2('* on a wrong device.')
332
+ log2()
333
+ display_help()
334
+ return (False, error)
207
335
 
208
- return True
336
+ return (True, None)
209
337
 
210
338
  def user_pass(self, secret_path = 'cql.secret'):
211
339
  return Secrets.get_user_pass(self.pod if self.pod else self.sts, self.namespace, secret_path=secret_path)
adam/sql/sql_completer.py CHANGED
@@ -1,53 +1,93 @@
1
- from typing import Dict, Iterable, Optional
2
- from prompt_toolkit.completion import CompleteEvent, Completer, Completion, NestedCompleter, WordCompleter
3
- from prompt_toolkit.document import Document
4
-
5
- from adam.sql.any_completer import AnyCompleter as any
6
- from adam.sql.sql_utils import safe_terms
7
- from adam.sql.table_name_completer import TableNameCompleter
8
-
9
- class SqlCompleter(NestedCompleter):
10
- def __init__(
11
- self, options: Dict[str, Optional[Completer]], ignore_case: bool = True
12
- ) -> None:
13
- super().__init__(options, ignore_case)
14
-
15
- def get_completions(
16
- self, document: Document, complete_event: CompleteEvent
17
- ) -> Iterable[Completion]:
18
- text = document.text_before_cursor.lstrip()
19
- stripped_len = len(document.text_before_cursor) - len(text)
20
-
21
- terms, has_space = safe_terms(text)
22
- if has_space:
23
- first_term = terms[0]
24
- completer = self.options.get(first_term)
25
-
26
- if completer is not None:
27
- remaining_text = text[len(first_term) :].lstrip()
28
- move_cursor = len(text) - len(remaining_text) + stripped_len
29
-
30
- new_document = Document(
31
- remaining_text,
32
- cursor_position=document.cursor_position - move_cursor,
33
- )
34
-
35
- for c in completer.get_completions(new_document, complete_event):
36
- yield c
1
+ from typing import Callable
2
+
3
+ import sqlparse
4
+ from sqlparse.sql import Statement, Token
5
+
6
+ from adam.sql.term_completer import TermCompleter
7
+ from adam.utils_repl.automata_completer import AutomataCompleter
8
+ from adam.sql.sql_state_machine import AthenaStateMachine, CqlStateMachine, SqlStateMachine
9
+ from adam.utils_repl.state_machine import State
10
+
11
+ __all__ = [
12
+ "SqlCompleter",
13
+ ]
14
+
15
+ def default_columns(_: list[str]):
16
+ return 'id,x.,y.,z.'.split(',')
17
+
18
+ class SqlCompleter(AutomataCompleter[Token]):
19
+ def tokens(self, text: str) -> list[Token]:
20
+ tokens = []
21
+
22
+ stmts = sqlparse.parse(text)
23
+ if not stmts:
24
+ tokens = []
25
+ else:
26
+ statement: Statement = stmts[0]
27
+ tokens = statement.tokens
28
+
29
+ return tokens
30
+
31
+ def __init__(self,
32
+ tables: Callable[[], list[str]],
33
+ 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
+ debug = False):
39
+ machine = SqlStateMachine(debug=debug)
40
+ if variant == 'cql':
41
+ machine = CqlStateMachine(debug=debug)
42
+ elif variant == 'athena':
43
+ machine = AthenaStateMachine(debug=debug)
44
+ super().__init__(machine, dml, debug)
45
+
46
+ self.tables = tables
47
+ self.columns = columns
48
+ self.partition_columns = partition_columns
49
+ self.table_props = table_props
50
+ self.variant = variant
51
+ self.debug = debug
52
+
53
+ def suggestions_completer(self, state: State, suggestions: str) -> list[str]:
54
+ if not suggestions:
55
+ return None
56
+
57
+ terms = []
58
+ for suggestion in suggestions.split(','):
59
+ terms.extend(self._terms(state, suggestion))
60
+
61
+ return TermCompleter(terms)
62
+
63
+ def _terms(self, state: State, word: str) -> list[str]:
64
+ terms = []
65
+
66
+ if word == 'tables':
67
+ terms.extend(self.tables())
68
+ elif word == '`tables`':
69
+ terms.append('tables')
70
+ elif word == 'columns':
71
+ terms.extend(self.columns([]))
72
+ elif word == 'partition-columns':
73
+ terms.extend(self.partition_columns([]))
74
+ elif word == 'table-props':
75
+ terms.extend(self.table_props().keys())
76
+ elif word == 'table-prop-values':
77
+ if 'last_name' in state.context and state.context['last_name']:
78
+ terms.extend(self.table_props()[state.context['last_name']])
79
+ elif word == 'single':
80
+ terms.append("'")
81
+ elif word == 'comma':
82
+ terms.append(",")
37
83
  else:
38
- completer = WordCompleter(
39
- list(self.options.keys()), ignore_case=self.ignore_case
40
- )
41
- for c in completer.get_completions(document, complete_event):
42
- yield c
84
+ terms.append(word)
85
+
86
+ return terms
43
87
 
44
- def completions(table: TableNameCompleter):
88
+ def completions_for_nesting(self):
45
89
  return {
46
- 'delete': {'from': table.nested({'where': any('id').nested({'=': any("'id'")})})},
47
- 'insert': {'into': table.nested({'values(': None})},
48
- 'select': any('*').nested({'from': table.nested({
49
- 'limit': any('1'),
50
- 'where': any('id').nested({'=': any("'id'").nested({'limit': any('1')})})
51
- })}),
52
- 'update': table.nested({'set': {'column': {'=': None}}}),
90
+ word : SqlCompleter(self.tables, word, columns=self.columns, partition_columns=self.partition_columns,
91
+ table_props=self.table_props, variant=self.variant)
92
+ for word in self.machine.suggestions[''].strip(' ').split(',')
53
93
  }