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.
- adam/batch.py +1 -15
- adam/commands/alter_tables.py +3 -14
- adam/commands/app.py +3 -3
- adam/commands/app_ping.py +2 -2
- adam/commands/audit/audit.py +26 -11
- adam/commands/audit/audit_repair_tables.py +20 -37
- adam/commands/audit/audit_run.py +58 -0
- adam/commands/audit/show_last10.py +51 -0
- adam/commands/audit/show_slow10.py +50 -0
- adam/commands/audit/show_top10.py +49 -0
- adam/commands/audit/utils_show_top10.py +59 -0
- adam/commands/bash/bash.py +124 -0
- adam/commands/bash/bash_completer.py +93 -0
- adam/commands/cat.py +55 -0
- adam/commands/cd.py +23 -11
- adam/commands/check.py +6 -0
- adam/commands/code.py +60 -0
- adam/commands/command.py +9 -4
- adam/commands/commands_utils.py +1 -2
- adam/commands/cql/cql_completions.py +7 -3
- adam/commands/cql/cql_utils.py +100 -8
- adam/commands/cql/cqlsh.py +10 -5
- adam/commands/deploy/deploy.py +7 -1
- adam/commands/deploy/deploy_pg_agent.py +2 -2
- adam/commands/deploy/undeploy.py +7 -1
- adam/commands/deploy/undeploy_pg_agent.py +2 -2
- adam/commands/devices.py +29 -0
- adam/commands/export/__init__.py +0 -0
- adam/commands/export/export.py +60 -0
- adam/commands/export/export_on_x.py +76 -0
- adam/commands/export/export_rmdbs.py +65 -0
- adam/commands/export/export_select.py +68 -0
- adam/commands/export/export_use.py +56 -0
- adam/commands/export/utils_export.py +253 -0
- adam/commands/help.py +9 -5
- adam/commands/issues.py +6 -0
- adam/commands/kubectl.py +41 -0
- adam/commands/login.py +6 -3
- adam/commands/logs.py +1 -0
- adam/commands/ls.py +39 -27
- adam/commands/medusa/medusa_show_backupjobs.py +1 -0
- adam/commands/nodetool.py +5 -2
- adam/commands/postgres/postgres.py +4 -4
- adam/commands/postgres/{postgres_session.py → postgres_context.py} +26 -27
- adam/commands/postgres/postgres_utils.py +5 -5
- adam/commands/postgres/psql_completions.py +1 -1
- adam/commands/preview_table.py +18 -32
- adam/commands/pwd.py +4 -3
- adam/commands/reaper/reaper.py +3 -0
- adam/commands/repair/repair.py +3 -3
- adam/commands/report.py +6 -0
- adam/commands/show/show.py +3 -1
- adam/commands/show/show_app_actions.py +3 -0
- adam/commands/show/show_app_queues.py +3 -2
- adam/commands/show/show_login.py +3 -0
- adam/config.py +1 -1
- adam/embedded_params.py +1 -1
- adam/pod_exec_result.py +7 -1
- adam/repl.py +121 -97
- adam/repl_commands.py +29 -17
- adam/repl_state.py +224 -44
- adam/sql/sql_completer.py +86 -62
- adam/sql/sql_state_machine.py +563 -0
- adam/sql/term_completer.py +3 -0
- adam/utils_athena.py +108 -74
- adam/utils_audits.py +104 -0
- adam/utils_export.py +42 -0
- adam/utils_k8s/app_clusters.py +33 -0
- adam/utils_k8s/app_pods.py +31 -0
- adam/utils_k8s/cassandra_clusters.py +4 -5
- adam/utils_k8s/cassandra_nodes.py +4 -4
- adam/utils_k8s/pods.py +42 -6
- adam/utils_k8s/statefulsets.py +2 -2
- adam/version.py +1 -1
- {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/METADATA +1 -1
- {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/RECORD +80 -67
- adam/commands/bash.py +0 -92
- adam/commands/cql/cql_table_completer.py +0 -8
- adam/commands/describe/describe.py +0 -46
- adam/commands/describe/describe_keyspace.py +0 -60
- adam/commands/describe/describe_keyspaces.py +0 -50
- adam/commands/describe/describe_table.py +0 -60
- adam/commands/describe/describe_tables.py +0 -50
- adam/commands/postgres/psql_table_completer.py +0 -11
- adam/sql/state_machine.py +0 -460
- /adam/commands/{describe → bash}/__init__.py +0 -0
- {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/WHEEL +0 -0
- {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/entry_points.txt +0 -0
- {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.
|
|
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.
|
|
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.
|
|
59
|
-
ReplCommands.
|
|
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(),
|
|
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 [
|
|
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
|
|
89
|
-
return [
|
|
90
|
-
DeployPod(), UndeployPod(), DeployPgAgent(), UndeployPgAgent(), AuditRepairTables(), Audit()]
|
|
92
|
+
def postgres_ops() -> list[Command]:
|
|
93
|
+
return [Postgres(), DeployPgAgent(), UndeployPgAgent()]
|
|
91
94
|
|
|
92
|
-
def
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
170
|
-
if not
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
181
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
201
|
-
|
|
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
|
-
|
|
334
|
+
elif required == RequiredState.PG_DATABASE:
|
|
335
|
+
if self.device != ReplState.P:
|
|
336
|
+
return (False, None)
|
|
211
337
|
|
|
212
|
-
|
|
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('
|
|
380
|
+
log2(f'Switch to {required}: first.')
|
|
217
381
|
else:
|
|
218
|
-
log2('*
|
|
382
|
+
log2('* on a wrong device.')
|
|
219
383
|
log2()
|
|
220
384
|
display_help()
|
|
385
|
+
return (False, error)
|
|
221
386
|
|
|
222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
|
2
|
-
|
|
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
|
-
|
|
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(
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
24
|
+
tokens = []
|
|
41
25
|
else:
|
|
42
26
|
statement: Statement = stmts[0]
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
'
|
|
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
|
}
|