kaqing 2.0.95__py3-none-any.whl → 2.0.115__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 (89) hide show
  1. adam/batch.py +1 -15
  2. adam/commands/alter_tables.py +3 -14
  3. adam/commands/app.py +3 -3
  4. adam/commands/app_ping.py +2 -2
  5. adam/commands/audit/audit.py +26 -11
  6. adam/commands/audit/audit_repair_tables.py +20 -37
  7. adam/commands/audit/audit_run.py +58 -0
  8. adam/commands/audit/show_last10.py +51 -0
  9. adam/commands/audit/show_slow10.py +50 -0
  10. adam/commands/audit/show_top10.py +49 -0
  11. adam/commands/audit/utils_show_top10.py +59 -0
  12. adam/commands/bash/bash.py +124 -0
  13. adam/commands/bash/bash_completer.py +93 -0
  14. adam/commands/cat.py +55 -0
  15. adam/commands/cd.py +23 -11
  16. adam/commands/check.py +6 -0
  17. adam/commands/code.py +60 -0
  18. adam/commands/command.py +9 -4
  19. adam/commands/commands_utils.py +1 -2
  20. adam/commands/cql/cql_completions.py +7 -3
  21. adam/commands/cql/cql_utils.py +100 -8
  22. adam/commands/cql/cqlsh.py +10 -5
  23. adam/commands/deploy/deploy.py +7 -1
  24. adam/commands/deploy/deploy_pg_agent.py +2 -2
  25. adam/commands/deploy/undeploy.py +7 -1
  26. adam/commands/deploy/undeploy_pg_agent.py +2 -2
  27. adam/commands/devices.py +29 -0
  28. adam/commands/export/__init__.py +0 -0
  29. adam/commands/export/export.py +60 -0
  30. adam/commands/export/export_on_x.py +76 -0
  31. adam/commands/export/export_rmdbs.py +65 -0
  32. adam/commands/export/export_select.py +68 -0
  33. adam/commands/export/export_use.py +56 -0
  34. adam/commands/export/utils_export.py +253 -0
  35. adam/commands/help.py +9 -5
  36. adam/commands/issues.py +6 -0
  37. adam/commands/kubectl.py +41 -0
  38. adam/commands/login.py +6 -3
  39. adam/commands/logs.py +1 -0
  40. adam/commands/ls.py +39 -27
  41. adam/commands/medusa/medusa_show_backupjobs.py +1 -0
  42. adam/commands/nodetool.py +5 -2
  43. adam/commands/postgres/postgres.py +4 -4
  44. adam/commands/postgres/{postgres_session.py → postgres_context.py} +26 -27
  45. adam/commands/postgres/postgres_utils.py +5 -5
  46. adam/commands/postgres/psql_completions.py +1 -1
  47. adam/commands/preview_table.py +18 -32
  48. adam/commands/pwd.py +4 -3
  49. adam/commands/reaper/reaper.py +3 -0
  50. adam/commands/repair/repair.py +3 -3
  51. adam/commands/report.py +6 -0
  52. adam/commands/show/show.py +3 -1
  53. adam/commands/show/show_app_actions.py +3 -0
  54. adam/commands/show/show_app_queues.py +3 -2
  55. adam/commands/show/show_login.py +3 -0
  56. adam/config.py +1 -1
  57. adam/embedded_params.py +1 -1
  58. adam/pod_exec_result.py +7 -1
  59. adam/repl.py +121 -97
  60. adam/repl_commands.py +29 -17
  61. adam/repl_state.py +224 -44
  62. adam/sql/sql_completer.py +86 -62
  63. adam/sql/sql_state_machine.py +563 -0
  64. adam/sql/term_completer.py +3 -0
  65. adam/utils_athena.py +108 -74
  66. adam/utils_audits.py +104 -0
  67. adam/utils_export.py +42 -0
  68. adam/utils_k8s/app_clusters.py +33 -0
  69. adam/utils_k8s/app_pods.py +31 -0
  70. adam/utils_k8s/cassandra_clusters.py +4 -5
  71. adam/utils_k8s/cassandra_nodes.py +4 -4
  72. adam/utils_k8s/pods.py +42 -6
  73. adam/utils_k8s/statefulsets.py +2 -2
  74. adam/version.py +1 -1
  75. {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/METADATA +1 -1
  76. {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/RECORD +80 -67
  77. adam/commands/bash.py +0 -92
  78. adam/commands/cql/cql_table_completer.py +0 -8
  79. adam/commands/describe/describe.py +0 -46
  80. adam/commands/describe/describe_keyspace.py +0 -60
  81. adam/commands/describe/describe_keyspaces.py +0 -50
  82. adam/commands/describe/describe_table.py +0 -60
  83. adam/commands/describe/describe_tables.py +0 -50
  84. adam/commands/postgres/psql_table_completer.py +0 -11
  85. adam/sql/state_machine.py +0 -460
  86. /adam/commands/{describe → bash}/__init__.py +0 -0
  87. {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/WHEEL +0 -0
  88. {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/entry_points.txt +0 -0
  89. {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/top_level.txt +0 -0
adam/repl_commands.py CHANGED
@@ -2,7 +2,8 @@ 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
4
  from adam.commands.audit.audit import Audit
5
- from adam.commands.audit.audit_repair_tables import AuditRepairTables
5
+ from adam.commands.cat import Cat
6
+ from adam.commands.code import Code
6
7
  from adam.commands.deploy.code_start import CodeStart
7
8
  from adam.commands.deploy.code_stop import CodeStop
8
9
  from adam.commands.deploy.deploy import Deploy
@@ -13,16 +14,21 @@ from adam.commands.deploy.undeploy import Undeploy
13
14
  from adam.commands.deploy.undeploy_frontend import UndeployFrontend
14
15
  from adam.commands.deploy.undeploy_pg_agent import UndeployPgAgent
15
16
  from adam.commands.deploy.undeploy_pod import UndeployPod
16
- from adam.commands.describe.describe import Describe
17
+ from adam.commands.export.export import ExportTables
18
+ from adam.commands.export.export_rmdbs import RemoveExportDatabases
19
+ from adam.commands.export.export_select import ExportSelect
20
+ from adam.commands.export.export_use import ExportUse
21
+ from adam.commands.export.export_on_x import ExportSelectX, ExportUseX
22
+ from adam.commands.kubectl import Kubectl
17
23
  from adam.commands.shell import Shell
18
24
  from adam.commands.show.show_app_queues import ShowAppQueues
19
25
  from adam.commands.cp import ClipboardCopy
20
- from adam.commands.bash import Bash
26
+ from adam.commands.bash.bash import Bash
21
27
  from adam.commands.cd import Cd
22
28
  from adam.commands.check import Check
23
29
  from adam.commands.command import Command
24
30
  from adam.commands.cql.cqlsh import Cqlsh
25
- from adam.commands.devices import DeviceApp, DeviceAuditLog, DeviceCass, DevicePostgres
31
+ from adam.commands.devices import DeviceApp, DeviceAuditLog, DeviceCass, DeviceExport, DevicePostgres
26
32
  from adam.commands.exit import Exit
27
33
  from adam.commands.medusa.medusa import Medusa
28
34
  from adam.commands.param_get import GetParam
@@ -55,10 +61,10 @@ from adam.commands.watch import Watch
55
61
 
56
62
  class ReplCommands:
57
63
  def repl_cmd_list() -> list[Command]:
58
- cmds: list[Command] = ReplCommands.navigation() + ReplCommands.cassandra_check() + ReplCommands.cassandra_ops() + \
59
- ReplCommands.tools() + ReplCommands.app() + ReplCommands.exit()
64
+ cmds: list[Command] = ReplCommands.navigation() + ReplCommands.cassandra_ops() + ReplCommands.postgres_ops() + \
65
+ ReplCommands.app_ops() + ReplCommands.audit_ops() + ReplCommands.export_ops() + ReplCommands.tools() + ReplCommands.exit()
60
66
 
61
- intermediate_cmds: list[Command] = [App(), Reaper(), Repair(), Deploy(), Describe(), Show(), Undeploy()]
67
+ intermediate_cmds: list[Command] = [App(), Audit(), Reaper(), Repair(), Deploy(), Show(), Undeploy()]
62
68
  ic = [c.command() for c in intermediate_cmds]
63
69
  # 1. dedup commands
64
70
  deduped = []
@@ -75,22 +81,28 @@ class ReplCommands:
75
81
  return deduped
76
82
 
77
83
  def navigation() -> list[Command]:
78
- return [Ls(), PreviewTable(), DeviceApp(), DevicePostgres(), DeviceCass(), DeviceAuditLog(), Cd(), Pwd(), ClipboardCopy(),
84
+ return [Ls(), PreviewTable(), DeviceApp(), DevicePostgres(), DeviceCass(), DeviceAuditLog(), DeviceExport(), Cd(), Cat(), Pwd(), ClipboardCopy(),
79
85
  GetParam(), SetParam(), ShowParams(), ShowKubectlCommands(), ShowLogin(), ShowAdam(), ShowHost()]
80
86
 
81
- def cassandra_check() -> list[Command]:
82
- return Describe.cmd_list() + [ShowCassandraStatus(),
83
- ShowCassandraVersion(), ShowRepairs(), ShowStorage(), ShowProcesses(), Check(), Issues(), NodeTool(), Report()]
84
-
85
87
  def cassandra_ops() -> list[Command]:
86
- return [AlterTables()] + Medusa.cmd_list() + [Restart(), RollOut(), Watch()] + Reaper.cmd_list() + Repair.cmd_list()
88
+ return [Cqlsh(), ShowCassandraStatus(), ShowCassandraVersion(), ShowRepairs(), ShowStorage(), ShowProcesses(), Check(), Issues(), NodeTool(), Report()] + \
89
+ [AlterTables(), Bash(), ExportTables(), ExportSelect(), ExportUse(), RemoveExportDatabases()] + \
90
+ Medusa.cmd_list() + [Restart(), RollOut(), Watch()] + Reaper.cmd_list() + Repair.cmd_list()
87
91
 
88
- def tools() -> list[Command]:
89
- return [Cqlsh(), Postgres(), Bash(), Shell(), CodeStart(), CodeStop(), DeployFrontend(), UndeployFrontend(),
90
- DeployPod(), UndeployPod(), DeployPgAgent(), UndeployPgAgent(), AuditRepairTables(), Audit()]
92
+ def postgres_ops() -> list[Command]:
93
+ return [Postgres(), DeployPgAgent(), UndeployPgAgent()]
91
94
 
92
- def app() -> list[Command]:
95
+ def app_ops() -> list[Command]:
93
96
  return [ShowAppActions(), ShowAppId(), ShowAppQueues(), AppPing(), App()]
94
97
 
98
+ def audit_ops() -> list[Command]:
99
+ return [Audit()] + Audit.cmd_list()
100
+
101
+ def export_ops() -> list[Command]:
102
+ return [ExportUseX(), ExportSelectX()]
103
+
104
+ def tools() -> list[Command]:
105
+ return [Shell(), CodeStart(), CodeStop(), DeployFrontend(), UndeployFrontend(), DeployPod(), UndeployPod(), Kubectl(), Code()]
106
+
95
107
  def exit() -> list[Command]:
96
108
  return [Exit()]
adam/repl_state.py CHANGED
@@ -1,8 +1,11 @@
1
- import copy
1
+ from copy import copy
2
2
  from enum import Enum
3
3
  import re
4
+ from typing import Callable
4
5
 
5
- from adam.commands.postgres.postgres_session import PostgresSession
6
+ from adam.commands.postgres.postgres_context import PostgresContext
7
+ from adam.utils_k8s.app_clusters import AppClusters
8
+ from adam.utils_k8s.app_pods import AppPods
6
9
  from adam.utils_k8s.cassandra_clusters import CassandraClusters
7
10
  from adam.utils_k8s.cassandra_nodes import CassandraNodes
8
11
  from adam.utils_k8s.kube_context import KubeContext
@@ -17,7 +20,13 @@ class BashSession:
17
20
  def pwd(self, state: 'ReplState'):
18
21
  command = f'cat /tmp/.qing-{self.session_id}'
19
22
 
20
- if state.pod:
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:
21
30
  rs = [CassandraNodes.exec(state.pod, state.namespace, command, show_out=False)]
22
31
  elif state.sts:
23
32
  rs = CassandraClusters.exec(state.sts, state.namespace, command, action='bash', show_out=False)
@@ -44,17 +53,22 @@ class RequiredState(Enum):
44
53
  NAMESPACE = 'namespace'
45
54
  PG_DATABASE = 'pg_database'
46
55
  APP_APP = 'app_app'
56
+ EXPORT_DB = 'export_db'
47
57
 
48
58
  class ReplState:
49
59
  A = 'a'
50
60
  C = 'c'
51
61
  L = 'l'
52
62
  P = 'p'
63
+ X = 'x'
64
+
65
+ ANY = [A, C, L, P, X]
66
+ NON_L = [A, C, P, X]
53
67
 
54
68
  def __init__(self, device: str = None,
55
69
  sts: str = None, pod: str = None, namespace: str = None, ns_sts: str = None,
56
70
  pg_path: str = None,
57
- app_env: str = None, app_app: str = None,
71
+ app_env: str = None, app_app: str = None, app_pod: str = None,
58
72
  in_repl = False, bash_session: BashSession = None, remote_dir = None):
59
73
  self.namespace = KubeContext.in_cluster_namespace()
60
74
 
@@ -64,12 +78,15 @@ class ReplState:
64
78
  self.pg_path = pg_path
65
79
  self.app_env = app_env
66
80
  self.app_app = app_app
81
+ self.app_pod = app_pod
67
82
  if namespace:
68
83
  self.namespace = namespace
69
84
  self.in_repl = in_repl
70
85
  self.bash_session = bash_session
71
86
  self.remote_dir = remote_dir
72
- # self.wait_log_flag = False
87
+ self.original_state: ReplState = None
88
+
89
+ self.export_session: str = None
73
90
 
74
91
  if ns_sts:
75
92
  nn = ns_sts.split('@')
@@ -84,13 +101,49 @@ class ReplState:
84
101
  def __hash__(self):
85
102
  return hash((self.sts, self.pod))
86
103
 
104
+ def __str__(self):
105
+ msg = ''
106
+ if self.device == ReplState.P:
107
+ 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
113
+ elif self.device == ReplState.A:
114
+ msg = f'{ReplState.A}:'
115
+ if self.app_env:
116
+ msg += self.app_env
117
+ if self.app_app:
118
+ msg += f'/{self.app_app}'
119
+ if self.app_pod:
120
+ # azops88-c3-c3-k8sdeploy-appleader-001-79957cf5b6-9k4bw
121
+ group = re.match(r".*?-.*?-.*?-.*?-(.*?-.*?)-.*", self.app_pod)
122
+ msg += '/' + group[1]
123
+ elif self.device == ReplState.L:
124
+ msg = f'{ReplState.L}:'
125
+ elif self.device == ReplState.X:
126
+ msg = f'{ReplState.X}:'
127
+ else:
128
+ msg = f'{ReplState.C}:'
129
+ if self.pod:
130
+ # cs-d0767a536f-cs-d0767a536f-default-sts-0
131
+ group = re.match(r".*?-.*?-(.*)", self.pod)
132
+ msg += group[1]
133
+ elif self.sts:
134
+ # cs-d0767a536f-cs-d0767a536f-default-sts
135
+ group = re.match(r".*?-.*?-(.*)", self.sts)
136
+ msg += group[1]
137
+
138
+ return msg
139
+
87
140
  def apply_args(self, args: list[str], cmd: list[str] = None, resolve_pg = True, args_to_check = 6) -> tuple['ReplState', list[str]]:
88
141
  state = self
89
142
 
90
143
  new_args = []
91
144
  for index, arg in enumerate(args):
92
145
  if index < args_to_check:
93
- state = copy.copy(state)
146
+ state = copy(state)
94
147
 
95
148
  s, n = KubeContext.is_sts_name(arg)
96
149
  if s:
@@ -128,9 +181,9 @@ class ReplState:
128
181
  new_args = []
129
182
  for index, arg in enumerate(args):
130
183
  if index < 6:
131
- state = copy.copy(state)
184
+ state = copy(state)
132
185
 
133
- groups = re.match(r'^([a|c|l|p]):(.*)$', arg)
186
+ groups = re.match(r'^([a|c|l|p|x]):(.*)$', arg)
134
187
  if groups:
135
188
  if groups[1] == 'p':
136
189
  state.device = 'p'
@@ -149,6 +202,8 @@ class ReplState:
149
202
  state.namespace = ns
150
203
  elif groups[1] == 'l':
151
204
  state.device = 'l'
205
+ elif groups[1] == 'x':
206
+ state.device = 'x'
152
207
  else:
153
208
  state.device = 'a'
154
209
  if path := groups[2]:
@@ -166,84 +221,209 @@ class ReplState:
166
221
 
167
222
  return (state, new_args)
168
223
 
169
- def validate(self, required: RequiredState = None, pg_required: RequiredState = None, app_required: RequiredState = None):
170
- if not pg_required and not app_required:
171
- if required == RequiredState.CLUSTER:
172
- if not self.namespace or not self.sts:
224
+ def validate(self, required: list[RequiredState] = [], show_err = True):
225
+ if not required:
226
+ return True
227
+
228
+ def default_err():
229
+ if self.in_repl:
230
+ log2(f'Not a valid command on {self.device}: drive.')
231
+ else:
232
+ log2('* on a wrong device.')
233
+ log2()
234
+ display_help()
235
+
236
+ if type(required) is not list:
237
+ valid, err = self._validate(required)
238
+ if valid:
239
+ return True
240
+
241
+ if show_err:
242
+ if err:
243
+ err()
244
+ else:
245
+ default_err()
246
+
247
+ return False
248
+
249
+ devices = [r for r in required if r in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X]]
250
+ non_devices = [r for r in required if r not in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X]]
251
+
252
+ first_error: Callable = None
253
+ for r in non_devices:
254
+ valid, err = self._validate(r)
255
+ if valid:
256
+ return True
257
+
258
+ if not first_error:
259
+ first_error = err
260
+
261
+ if devices:
262
+ valid, err = self._validate_device(devices)
263
+ if valid:
264
+ return True
265
+
266
+ if not first_error:
267
+ first_error = err
268
+
269
+ if show_err and first_error:
270
+ if first_error:
271
+ first_error()
272
+ else:
273
+ default_err()
274
+
275
+ return False
276
+
277
+ def _validate(self, required: RequiredState):
278
+ if required == RequiredState.CLUSTER:
279
+ if self.device != ReplState.C:
280
+ return (False, None)
281
+
282
+ if not self.namespace or not self.sts:
283
+ def error():
173
284
  if self.in_repl:
174
285
  log2('cd to a Cassandra cluster first.')
175
286
  else:
176
287
  log2('* Cassandra cluster is missing.')
177
288
  log2()
178
289
  display_help()
290
+ return (False, error)
291
+
292
+ elif required == RequiredState.POD:
293
+ if self.device != ReplState.C:
294
+ return (False, None)
179
295
 
180
- return False
181
- elif required == RequiredState.POD:
182
- if not self.namespace or not self.pod:
296
+ if not self.namespace or not self.pod:
297
+ def error():
183
298
  if self.in_repl:
184
299
  log2('cd to a pod first.')
185
300
  else:
186
301
  log2('* Pod is missing.')
187
302
  log2()
188
303
  display_help()
304
+ return (False, error)
189
305
 
190
- return False
191
- elif required == RequiredState.CLUSTER_OR_POD:
192
- if not self.namespace or not self.sts and not self.pod:
306
+ elif required == RequiredState.CLUSTER_OR_POD:
307
+ if self.device != ReplState.C:
308
+ return (False, None)
309
+
310
+ if not self.namespace or not self.sts and not self.pod:
311
+ def error():
193
312
  if self.in_repl:
194
313
  log2('cd to a Cassandra cluster first.')
195
314
  else:
196
315
  log2('* Cassandra cluster or pod is missing.')
197
316
  log2()
198
317
  display_help()
318
+ return (False, error)
319
+
320
+ elif required == RequiredState.NAMESPACE:
321
+ if self.device != ReplState.C:
322
+ return (False, None)
199
323
 
200
- return False
201
- elif required == RequiredState.NAMESPACE:
202
- if not self.namespace:
324
+ if not self.namespace:
325
+ def error():
203
326
  if self.in_repl:
204
327
  log2('Namespace is required.')
205
328
  else:
206
329
  log2('* namespace is missing.')
207
330
  log2()
208
331
  display_help()
332
+ return (False, error)
209
333
 
210
- return False
334
+ elif required == RequiredState.PG_DATABASE:
335
+ if self.device != ReplState.P:
336
+ return (False, None)
211
337
 
212
- if pg_required == RequiredState.PG_DATABASE:
213
- pg = PostgresSession(self.namespace, self.pg_path)
338
+ pg: PostgresContext = PostgresContext.apply(self.namespace, self.pg_path)
214
339
  if not pg.db:
340
+ def error():
341
+ if self.in_repl:
342
+ log2('cd to a database first.')
343
+ else:
344
+ log2('* database is missing.')
345
+ log2()
346
+ display_help()
347
+ return (False, error)
348
+
349
+ elif required == RequiredState.APP_APP:
350
+ if self.device != ReplState.A:
351
+ return (False, None)
352
+
353
+ if not self.app_app:
354
+ def error():
355
+ if self.in_repl:
356
+ log2('cd to an app first.')
357
+ else:
358
+ log2('* app is missing.')
359
+ log2()
360
+ display_help()
361
+ return (False, error)
362
+
363
+ elif required == RequiredState.EXPORT_DB:
364
+ if self.device != ReplState.X:
365
+ return (False, None)
366
+
367
+ if not self.export_session:
368
+ def error():
369
+ if self.in_repl:
370
+ log2('use an export database first.')
371
+ else:
372
+ log2('* export database is missing.')
373
+ log2()
374
+ display_help()
375
+ return (False, error)
376
+
377
+ elif required in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X] and self.device != required:
378
+ def error():
215
379
  if self.in_repl:
216
- log2('cd to a database first.')
380
+ log2(f'Switch to {required}: first.')
217
381
  else:
218
- log2('* database is missing.')
382
+ log2('* on a wrong device.')
219
383
  log2()
220
384
  display_help()
385
+ return (False, error)
221
386
 
222
- return False
223
-
224
- if app_required == RequiredState.APP_APP and not self.app_app:
225
- if self.in_repl:
226
- log2('cd to an app first.')
227
- else:
228
- log2('* app is missing.')
229
- log2()
230
- display_help()
387
+ return (True, None)
231
388
 
232
- return False
389
+ def _validate_device(self, devices: list[RequiredState]):
390
+ if self.device not in devices:
391
+ def error():
392
+ if self.in_repl:
393
+ log2(f'Not a valid command on {self.device}: drive.')
394
+ else:
395
+ log2('* on a wrong device.')
396
+ log2()
397
+ display_help()
398
+ return (False, error)
233
399
 
234
- return True
400
+ return (True, None)
235
401
 
236
402
  def user_pass(self, secret_path = 'cql.secret'):
237
403
  return Secrets.get_user_pass(self.pod if self.pod else self.sts, self.namespace, secret_path=secret_path)
238
404
 
239
405
  def enter_bash(self, bash_session: BashSession):
406
+ self.push()
407
+
240
408
  self.bash_session = bash_session
241
- if self.device != ReplState.C:
242
- self.device = ReplState.C
243
- log2(f'Moved to {ReplState.C}: automatically. Will move back to {ReplState.P}: when you exit the bash session.')
244
409
 
245
410
  def exit_bash(self):
246
- if self.bash_session and self.bash_session.device:
247
- self.device = self.bash_session.device
248
-
249
- self.bash_session = None
411
+ self.pop()
412
+ self.bash_session = None
413
+
414
+ def push(self):
415
+ if not self.original_state:
416
+ self.original_state = copy(self)
417
+
418
+ def pop(self):
419
+ if o := self.original_state:
420
+ self.device = o.device
421
+ self.sts = o.sts
422
+ self.pod = o.pod
423
+ self.pg_path = o.pg_path
424
+ self.app_env = o.app_env
425
+ self.app_app = o.app_app
426
+ self.app_pod = o.app_pod
427
+ self.namespace = o.namespace
428
+
429
+ self.original_state = None
adam/sql/sql_completer.py CHANGED
@@ -1,78 +1,102 @@
1
- from typing import Callable, Iterable
2
- from prompt_toolkit.completion import CompleteEvent, Completer, Completion
3
- from prompt_toolkit.document import Document
1
+ from typing import Callable
2
+
4
3
  import sqlparse
5
- from sqlparse.sql import Statement
4
+ from sqlparse.sql import Statement, Token
6
5
 
7
- from adam.sql.state_machine import StateMachine, StateTo
8
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
9
10
 
10
11
  __all__ = [
11
12
  "SqlCompleter",
12
13
  ]
13
14
 
14
- DML_COMPLETER = TermCompleter(['select', 'insert', 'delete', 'update'])
15
-
16
- def default_columns(tables: list[str]):
15
+ def default_columns(x: list[str]):
17
16
  return 'id,x.,y.,z.'.split(',')
18
17
 
19
- class SqlCompleter(Completer):
20
- def __init__(self, tables: Callable[[], list[str]], dml: str = None, columns: Callable[[list[str]], list[str]] = default_columns, debug = False):
21
- super().__init__()
22
- self.dml = dml
23
- self.tables = tables
24
- self.columns = columns
25
- self.debug = debug
26
- self.machine = StateMachine(debug=self.debug)
27
-
28
- def get_completions(
29
- self, document: Document, complete_event: CompleteEvent
30
- ) -> Iterable[Completion]:
31
- text = document.text_before_cursor.lstrip()
32
- state = ''
33
- if self.dml:
34
- state = f'{self.dml}_'
35
- text = f'{self.dml} {text}'
36
-
37
- completer: Completer = None
18
+ class SqlCompleter(AutomataCompleter[Token]):
19
+ def tokens(self, text: str) -> list[Token]:
20
+ tokens = []
21
+
38
22
  stmts = sqlparse.parse(text)
39
23
  if not stmts:
40
- completer = DML_COMPLETER
24
+ tokens = []
41
25
  else:
42
26
  statement: Statement = stmts[0]
43
- state: StateTo = self.machine.traverse_tokens(statement.tokens, StateTo(state))
44
- if self.debug:
45
- print('\n =>', state.to_s if isinstance(state, StateTo) else '')
46
-
47
- if not state or not state.to_s:
48
- completer = DML_COMPLETER
49
-
50
- if state and state.to_s in self.machine.suggestions:
51
- terms = []
52
-
53
- for word in self.machine.suggestions[state.to_s].strip(' ').split(','):
54
- if word == 'tables':
55
- terms.extend(self.tables())
56
- elif word == 'columns':
57
- terms.extend(self.columns([]))
58
- elif word == 'single':
59
- terms.append("'")
60
- elif word == 'comma':
61
- terms.append(",")
62
- else:
63
- terms.append(word)
64
-
65
- if terms:
66
- completer = TermCompleter(terms)
67
-
68
- if completer:
69
- for c in completer.get_completions(document, complete_event):
70
- yield c
71
-
72
- def completions(table_names: Callable[[], list[str]], columns: Callable[[list[str]], list[str]] = default_columns):
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
+ if 'last_name' in state.context and (n := state.context['last_name']):
72
+ if 'last_namespace' in state.context and (ns := state.context['last_namespace']):
73
+ n = f'{ns}.{n}'
74
+ terms.extend(self.columns([n]))
75
+ else:
76
+ terms.extend(self.columns([]))
77
+ elif word == 'partition-columns':
78
+ terms.extend(self.partition_columns([]))
79
+ elif word == 'table-props':
80
+ terms.extend(self.table_props().keys())
81
+ elif word == 'table-prop-values':
82
+ if 'last_name' in state.context and state.context['last_name']:
83
+ terms.extend(self.table_props()[state.context['last_name']])
84
+ elif word == 'single':
85
+ terms.append("'")
86
+ elif word == 'comma':
87
+ terms.append(",")
88
+ else:
89
+ terms.append(word)
90
+
91
+ return terms
92
+
93
+ def completions_for_nesting(self, dml: str = None):
94
+ if dml:
95
+ return {dml: SqlCompleter(self.tables, dml, columns=self.columns, partition_columns=self.partition_columns,
96
+ table_props=self.table_props, variant=self.variant)}
97
+
73
98
  return {
74
- 'delete': SqlCompleter(table_names, 'delete', columns=columns),
75
- 'insert': SqlCompleter(table_names, 'insert', columns=columns),
76
- 'select': SqlCompleter(table_names, 'select', columns=columns),
77
- 'update': SqlCompleter(table_names, 'update', columns=columns),
99
+ word : SqlCompleter(self.tables, word, columns=self.columns, partition_columns=self.partition_columns,
100
+ table_props=self.table_props, variant=self.variant)
101
+ for word in self.machine.suggestions[''].strip(' ').split(',')
78
102
  }