kaqing 1.98.15__py3-none-any.whl → 2.0.145__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/app_session.py +1 -1
- adam/apps.py +2 -2
- adam/batch.py +30 -31
- adam/checks/check_utils.py +4 -4
- adam/checks/compactionstats.py +1 -1
- adam/checks/cpu.py +2 -2
- adam/checks/disk.py +1 -1
- adam/checks/gossip.py +1 -1
- adam/checks/memory.py +3 -3
- adam/checks/status.py +1 -1
- adam/commands/alter_tables.py +81 -0
- adam/commands/app.py +3 -3
- adam/commands/app_ping.py +2 -2
- adam/commands/audit/audit.py +86 -0
- adam/commands/audit/audit_repair_tables.py +77 -0
- 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 +48 -0
- adam/commands/audit/utils_show_top10.py +59 -0
- adam/commands/bash/bash.py +133 -0
- adam/commands/bash/bash_completer.py +93 -0
- adam/commands/cat.py +56 -0
- adam/commands/cd.py +12 -82
- adam/commands/check.py +6 -0
- adam/commands/cli_commands.py +3 -3
- adam/commands/code.py +60 -0
- adam/commands/command.py +48 -12
- adam/commands/commands_utils.py +4 -5
- adam/commands/cql/cql_completions.py +28 -0
- adam/commands/cql/cql_utils.py +209 -0
- adam/commands/{cqlsh.py → cql/cqlsh.py} +15 -10
- adam/commands/deploy/__init__.py +0 -0
- adam/commands/{frontend → deploy}/code_start.py +1 -1
- adam/commands/{frontend → deploy}/code_stop.py +1 -1
- adam/commands/{frontend → deploy}/code_utils.py +2 -2
- adam/commands/deploy/deploy.py +48 -0
- adam/commands/deploy/deploy_frontend.py +52 -0
- adam/commands/deploy/deploy_pg_agent.py +38 -0
- adam/commands/deploy/deploy_pod.py +110 -0
- adam/commands/deploy/deploy_utils.py +29 -0
- adam/commands/deploy/undeploy.py +48 -0
- adam/commands/deploy/undeploy_frontend.py +41 -0
- adam/commands/deploy/undeploy_pg_agent.py +42 -0
- adam/commands/deploy/undeploy_pod.py +51 -0
- adam/commands/devices/__init__.py +0 -0
- adam/commands/devices/device.py +27 -0
- adam/commands/devices/device_app.py +146 -0
- adam/commands/devices/device_auit_log.py +43 -0
- adam/commands/devices/device_cass.py +145 -0
- adam/commands/devices/device_export.py +86 -0
- adam/commands/devices/device_postgres.py +109 -0
- adam/commands/devices/devices.py +25 -0
- adam/commands/export/__init__.py +0 -0
- adam/commands/export/clean_up_export_session.py +53 -0
- adam/commands/{frontend/teardown_frontend.py → export/clean_up_export_sessions.py} +9 -11
- adam/commands/export/drop_export_database.py +58 -0
- adam/commands/export/drop_export_databases.py +46 -0
- adam/commands/export/export.py +83 -0
- adam/commands/export/export_databases.py +170 -0
- adam/commands/export/export_select.py +85 -0
- adam/commands/export/export_select_x.py +54 -0
- adam/commands/export/export_use.py +55 -0
- adam/commands/export/exporter.py +364 -0
- adam/commands/export/import_session.py +68 -0
- adam/commands/export/importer.py +67 -0
- adam/commands/export/importer_athena.py +80 -0
- adam/commands/export/importer_sqlite.py +47 -0
- adam/commands/export/show_column_counts.py +63 -0
- adam/commands/export/show_export_databases.py +39 -0
- adam/commands/export/show_export_session.py +51 -0
- adam/commands/export/show_export_sessions.py +47 -0
- adam/commands/export/utils_export.py +291 -0
- adam/commands/help.py +12 -7
- adam/commands/issues.py +6 -0
- adam/commands/kubectl.py +41 -0
- adam/commands/login.py +9 -5
- adam/commands/logs.py +2 -1
- adam/commands/ls.py +4 -107
- adam/commands/medusa/medusa.py +2 -26
- adam/commands/medusa/medusa_backup.py +2 -2
- adam/commands/medusa/medusa_restore.py +3 -4
- adam/commands/medusa/medusa_show_backupjobs.py +4 -3
- adam/commands/medusa/medusa_show_restorejobs.py +3 -3
- adam/commands/nodetool.py +9 -4
- adam/commands/param_set.py +1 -1
- adam/commands/postgres/postgres.py +42 -43
- adam/commands/postgres/postgres_context.py +248 -0
- adam/commands/postgres/postgres_preview.py +0 -1
- adam/commands/postgres/postgres_utils.py +31 -0
- adam/commands/postgres/psql_completions.py +10 -0
- adam/commands/preview_table.py +18 -40
- adam/commands/pwd.py +2 -28
- adam/commands/reaper/reaper.py +4 -24
- adam/commands/reaper/reaper_restart.py +1 -1
- adam/commands/reaper/reaper_session.py +2 -2
- adam/commands/repair/repair.py +3 -27
- adam/commands/repair/repair_log.py +1 -1
- adam/commands/repair/repair_run.py +2 -2
- adam/commands/repair/repair_scan.py +2 -7
- adam/commands/repair/repair_stop.py +1 -1
- adam/commands/report.py +6 -0
- adam/commands/restart.py +2 -2
- adam/commands/rollout.py +1 -1
- adam/commands/shell.py +33 -0
- adam/commands/show/show.py +11 -26
- adam/commands/show/show_app_actions.py +3 -0
- adam/commands/show/show_app_id.py +1 -1
- adam/commands/show/show_app_queues.py +3 -2
- adam/commands/show/show_cassandra_status.py +3 -3
- adam/commands/show/show_cassandra_version.py +3 -3
- adam/commands/show/show_commands.py +4 -1
- adam/commands/show/show_host.py +33 -0
- adam/commands/show/show_login.py +3 -0
- adam/commands/show/show_processes.py +1 -1
- adam/commands/show/show_repairs.py +2 -2
- adam/commands/show/show_storage.py +1 -1
- adam/commands/watch.py +1 -1
- adam/config.py +16 -3
- adam/embedded_params.py +1 -1
- adam/pod_exec_result.py +10 -2
- adam/repl.py +132 -117
- adam/repl_commands.py +62 -18
- adam/repl_state.py +276 -55
- adam/sql/__init__.py +0 -0
- adam/sql/sql_completer.py +120 -0
- adam/sql/sql_state_machine.py +617 -0
- adam/sql/term_completer.py +76 -0
- adam/sso/authenticator.py +1 -1
- adam/sso/authn_ad.py +36 -56
- adam/sso/authn_okta.py +6 -32
- adam/sso/cred_cache.py +1 -1
- adam/sso/idp.py +74 -9
- adam/sso/idp_login.py +2 -2
- adam/sso/idp_session.py +10 -7
- adam/utils.py +85 -4
- adam/utils_athena.py +145 -0
- adam/utils_audits.py +102 -0
- adam/utils_k8s/__init__.py +0 -0
- adam/utils_k8s/app_clusters.py +33 -0
- adam/utils_k8s/app_pods.py +31 -0
- adam/{k8s_utils → utils_k8s}/cassandra_clusters.py +6 -21
- adam/{k8s_utils → utils_k8s}/cassandra_nodes.py +12 -5
- adam/utils_k8s/config_maps.py +34 -0
- adam/utils_k8s/deployment.py +56 -0
- adam/{k8s_utils → utils_k8s}/jobs.py +1 -1
- adam/{k8s_utils → utils_k8s}/kube_context.py +1 -1
- adam/utils_k8s/pods.py +342 -0
- adam/{k8s_utils → utils_k8s}/secrets.py +4 -0
- adam/utils_k8s/service_accounts.py +169 -0
- adam/{k8s_utils → utils_k8s}/statefulsets.py +5 -4
- adam/{k8s_utils → utils_k8s}/volumes.py +9 -0
- adam/utils_net.py +24 -0
- adam/utils_repl/__init__.py +0 -0
- adam/utils_repl/automata_completer.py +48 -0
- adam/utils_repl/repl_completer.py +46 -0
- adam/utils_repl/state_machine.py +173 -0
- adam/utils_sqlite.py +101 -0
- adam/version.py +1 -1
- {kaqing-1.98.15.dist-info → kaqing-2.0.145.dist-info}/METADATA +1 -1
- kaqing-2.0.145.dist-info/RECORD +227 -0
- adam/commands/bash.py +0 -87
- adam/commands/cql_utils.py +0 -53
- adam/commands/devices.py +0 -89
- adam/commands/frontend/setup.py +0 -60
- adam/commands/frontend/setup_frontend.py +0 -58
- adam/commands/frontend/teardown.py +0 -61
- adam/commands/postgres/postgres_session.py +0 -225
- adam/commands/user_entry.py +0 -77
- adam/k8s_utils/pods.py +0 -211
- kaqing-1.98.15.dist-info/RECORD +0 -160
- /adam/commands/{frontend → audit}/__init__.py +0 -0
- /adam/{k8s_utils → commands/bash}/__init__.py +0 -0
- /adam/{medusa_show_restorejobs.py → commands/cql/__init__.py} +0 -0
- /adam/{k8s_utils → utils_k8s}/custom_resources.py +0 -0
- /adam/{k8s_utils → utils_k8s}/ingresses.py +0 -0
- /adam/{k8s_utils → utils_k8s}/services.py +0 -0
- {kaqing-1.98.15.dist-info → kaqing-2.0.145.dist-info}/WHEEL +0 -0
- {kaqing-1.98.15.dist-info → kaqing-2.0.145.dist-info}/entry_points.txt +0 -0
- {kaqing-1.98.15.dist-info → kaqing-2.0.145.dist-info}/top_level.txt +0 -0
adam/repl_state.py
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import copy
|
|
1
|
+
from copy import copy
|
|
2
2
|
from enum import Enum
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
|
|
6
|
-
from adam.
|
|
7
|
-
from adam.
|
|
8
|
-
from adam.
|
|
3
|
+
import re
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
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
|
|
9
|
+
from adam.utils_k8s.cassandra_clusters import CassandraClusters
|
|
10
|
+
from adam.utils_k8s.cassandra_nodes import CassandraNodes
|
|
11
|
+
from adam.utils_k8s.kube_context import KubeContext
|
|
12
|
+
from adam.utils_k8s.secrets import Secrets
|
|
9
13
|
from adam.utils import display_help, log2, random_alphanumeric
|
|
10
14
|
|
|
11
15
|
class BashSession:
|
|
@@ -16,7 +20,13 @@ class BashSession:
|
|
|
16
20
|
def pwd(self, state: 'ReplState'):
|
|
17
21
|
command = f'cat /tmp/.qing-{self.session_id}'
|
|
18
22
|
|
|
19
|
-
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:
|
|
20
30
|
rs = [CassandraNodes.exec(state.pod, state.namespace, command, show_out=False)]
|
|
21
31
|
elif state.sts:
|
|
22
32
|
rs = CassandraClusters.exec(state.sts, state.namespace, command, action='bash', show_out=False)
|
|
@@ -43,16 +53,22 @@ class RequiredState(Enum):
|
|
|
43
53
|
NAMESPACE = 'namespace'
|
|
44
54
|
PG_DATABASE = 'pg_database'
|
|
45
55
|
APP_APP = 'app_app'
|
|
56
|
+
EXPORT_DB = 'export_db'
|
|
46
57
|
|
|
47
58
|
class ReplState:
|
|
48
59
|
A = 'a'
|
|
49
60
|
C = 'c'
|
|
61
|
+
L = 'l'
|
|
50
62
|
P = 'p'
|
|
63
|
+
X = 'x'
|
|
64
|
+
|
|
65
|
+
ANY = [A, C, L, P, X]
|
|
66
|
+
NON_L = [A, C, P, X]
|
|
51
67
|
|
|
52
68
|
def __init__(self, device: str = None,
|
|
53
69
|
sts: str = None, pod: str = None, namespace: str = None, ns_sts: str = None,
|
|
54
70
|
pg_path: str = None,
|
|
55
|
-
app_env: str = None, app_app: str = None,
|
|
71
|
+
app_env: str = None, app_app: str = None, app_pod: str = None,
|
|
56
72
|
in_repl = False, bash_session: BashSession = None, remote_dir = None):
|
|
57
73
|
self.namespace = KubeContext.in_cluster_namespace()
|
|
58
74
|
|
|
@@ -62,12 +78,15 @@ class ReplState:
|
|
|
62
78
|
self.pg_path = pg_path
|
|
63
79
|
self.app_env = app_env
|
|
64
80
|
self.app_app = app_app
|
|
81
|
+
self.app_pod = app_pod
|
|
65
82
|
if namespace:
|
|
66
83
|
self.namespace = namespace
|
|
67
84
|
self.in_repl = in_repl
|
|
68
85
|
self.bash_session = bash_session
|
|
69
86
|
self.remote_dir = remote_dir
|
|
70
|
-
self.
|
|
87
|
+
self.original_state: ReplState = None
|
|
88
|
+
|
|
89
|
+
self.export_session: str = None
|
|
71
90
|
|
|
72
91
|
if ns_sts:
|
|
73
92
|
nn = ns_sts.split('@')
|
|
@@ -82,13 +101,51 @@ class ReplState:
|
|
|
82
101
|
def __hash__(self):
|
|
83
102
|
return hash((self.sts, self.pod))
|
|
84
103
|
|
|
85
|
-
def
|
|
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
|
+
if self.export_session:
|
|
128
|
+
msg += self.export_session
|
|
129
|
+
else:
|
|
130
|
+
msg = f'{ReplState.C}:'
|
|
131
|
+
if self.pod:
|
|
132
|
+
# cs-d0767a536f-cs-d0767a536f-default-sts-0
|
|
133
|
+
group = re.match(r".*?-.*?-(.*)", self.pod)
|
|
134
|
+
msg += group[1]
|
|
135
|
+
elif self.sts:
|
|
136
|
+
# cs-d0767a536f-cs-d0767a536f-default-sts
|
|
137
|
+
group = re.match(r".*?-.*?-(.*)", self.sts)
|
|
138
|
+
msg += group[1]
|
|
139
|
+
|
|
140
|
+
return msg
|
|
141
|
+
|
|
142
|
+
def apply_args(self, args: list[str], cmd: list[str] = None, resolve_pg = True, args_to_check = 6) -> tuple['ReplState', list[str]]:
|
|
86
143
|
state = self
|
|
87
144
|
|
|
88
145
|
new_args = []
|
|
89
146
|
for index, arg in enumerate(args):
|
|
90
|
-
if index <
|
|
91
|
-
state = copy
|
|
147
|
+
if index < args_to_check:
|
|
148
|
+
state = copy(state)
|
|
92
149
|
|
|
93
150
|
s, n = KubeContext.is_sts_name(arg)
|
|
94
151
|
if s:
|
|
@@ -120,92 +177,256 @@ class ReplState:
|
|
|
120
177
|
|
|
121
178
|
return (state, new_args)
|
|
122
179
|
|
|
123
|
-
def
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
180
|
+
def apply_device_arg(self, args: list[str], cmd: list[str] = None) -> tuple['ReplState', list[str]]:
|
|
181
|
+
state = self
|
|
182
|
+
|
|
183
|
+
new_args = []
|
|
184
|
+
for index, arg in enumerate(args):
|
|
185
|
+
if index < 6:
|
|
186
|
+
state = copy(state)
|
|
187
|
+
|
|
188
|
+
groups = re.match(r'^([a|c|l|p|x]):(.*)$', arg)
|
|
189
|
+
if groups:
|
|
190
|
+
if groups[1] == 'p':
|
|
191
|
+
state.device = 'p'
|
|
192
|
+
state.pg_path = groups[2]
|
|
193
|
+
elif groups[1] == 'c':
|
|
194
|
+
state.device = 'c'
|
|
195
|
+
if path := groups[2]:
|
|
196
|
+
p_and_ns = path.split('@')
|
|
197
|
+
sts_and_pod = p_and_ns[0].split('/')
|
|
198
|
+
state.sts = sts_and_pod[0]
|
|
199
|
+
if len(sts_and_pod) > 1:
|
|
200
|
+
state.pod = sts_and_pod[1]
|
|
201
|
+
if len(p_and_ns) > 1:
|
|
202
|
+
state.namespace = p_and_ns[1]
|
|
203
|
+
elif ns := KubeContext.in_cluster_namespace():
|
|
204
|
+
state.namespace = ns
|
|
205
|
+
elif groups[1] == 'l':
|
|
206
|
+
state.device = 'l'
|
|
207
|
+
elif groups[1] == 'x':
|
|
208
|
+
state.device = 'x'
|
|
209
|
+
else:
|
|
210
|
+
state.device = 'a'
|
|
211
|
+
if path := groups[2]:
|
|
212
|
+
env_and_app = path.split('/')
|
|
213
|
+
state.app_env = env_and_app[0]
|
|
214
|
+
if len(env_and_app) > 1:
|
|
215
|
+
state.app_app = env_and_app[1]
|
|
216
|
+
else:
|
|
217
|
+
new_args.append(arg)
|
|
218
|
+
else:
|
|
219
|
+
new_args.append(arg)
|
|
220
|
+
|
|
221
|
+
if cmd:
|
|
222
|
+
new_args = new_args[len(cmd):]
|
|
223
|
+
|
|
224
|
+
return (state, new_args)
|
|
225
|
+
|
|
226
|
+
def validate(self, required: list[RequiredState] = [], show_err = True):
|
|
227
|
+
if not required:
|
|
228
|
+
return True
|
|
229
|
+
|
|
230
|
+
def default_err():
|
|
231
|
+
if self.in_repl:
|
|
232
|
+
log2(f'Not a valid command on {self.device}: drive.')
|
|
233
|
+
else:
|
|
234
|
+
log2('* on a wrong device.')
|
|
235
|
+
log2()
|
|
236
|
+
display_help()
|
|
237
|
+
|
|
238
|
+
if type(required) is not list:
|
|
239
|
+
valid, err = self._validate(required)
|
|
240
|
+
if valid:
|
|
241
|
+
return True
|
|
242
|
+
|
|
243
|
+
if show_err:
|
|
244
|
+
if err:
|
|
245
|
+
err()
|
|
246
|
+
else:
|
|
247
|
+
default_err()
|
|
248
|
+
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
devices = [r for r in required if r in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X]]
|
|
252
|
+
non_devices = [r for r in required if r not in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X]]
|
|
253
|
+
|
|
254
|
+
first_error: Callable = None
|
|
255
|
+
for r in non_devices:
|
|
256
|
+
valid, err = self._validate(r)
|
|
257
|
+
if valid:
|
|
258
|
+
return True
|
|
259
|
+
|
|
260
|
+
if not first_error:
|
|
261
|
+
first_error = err
|
|
262
|
+
|
|
263
|
+
if devices:
|
|
264
|
+
valid, err = self._validate_device(devices)
|
|
265
|
+
if valid:
|
|
266
|
+
return True
|
|
267
|
+
|
|
268
|
+
if not first_error:
|
|
269
|
+
first_error = err
|
|
270
|
+
|
|
271
|
+
if show_err and first_error:
|
|
272
|
+
if first_error:
|
|
273
|
+
first_error()
|
|
274
|
+
else:
|
|
275
|
+
default_err()
|
|
276
|
+
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
def _validate(self, required: RequiredState):
|
|
280
|
+
if required == RequiredState.CLUSTER:
|
|
281
|
+
if self.device != ReplState.C:
|
|
282
|
+
return (False, None)
|
|
283
|
+
|
|
284
|
+
if not self.namespace or not self.sts:
|
|
285
|
+
def error():
|
|
127
286
|
if self.in_repl:
|
|
128
287
|
log2('cd to a Cassandra cluster first.')
|
|
129
288
|
else:
|
|
130
289
|
log2('* Cassandra cluster is missing.')
|
|
131
290
|
log2()
|
|
132
291
|
display_help()
|
|
292
|
+
return (False, error)
|
|
293
|
+
|
|
294
|
+
elif required == RequiredState.POD:
|
|
295
|
+
if self.device != ReplState.C:
|
|
296
|
+
return (False, None)
|
|
133
297
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if not self.namespace or not self.pod:
|
|
298
|
+
if not self.namespace or not self.pod:
|
|
299
|
+
def error():
|
|
137
300
|
if self.in_repl:
|
|
138
301
|
log2('cd to a pod first.')
|
|
139
302
|
else:
|
|
140
303
|
log2('* Pod is missing.')
|
|
141
304
|
log2()
|
|
142
305
|
display_help()
|
|
306
|
+
return (False, error)
|
|
143
307
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
308
|
+
elif required == RequiredState.CLUSTER_OR_POD:
|
|
309
|
+
if self.device != ReplState.C:
|
|
310
|
+
return (False, None)
|
|
311
|
+
|
|
312
|
+
if not self.namespace or not self.sts and not self.pod:
|
|
313
|
+
def error():
|
|
147
314
|
if self.in_repl:
|
|
148
315
|
log2('cd to a Cassandra cluster first.')
|
|
149
316
|
else:
|
|
150
317
|
log2('* Cassandra cluster or pod is missing.')
|
|
151
318
|
log2()
|
|
152
319
|
display_help()
|
|
320
|
+
return (False, error)
|
|
321
|
+
|
|
322
|
+
elif required == RequiredState.NAMESPACE:
|
|
323
|
+
if self.device != ReplState.C:
|
|
324
|
+
return (False, None)
|
|
153
325
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if not self.namespace:
|
|
326
|
+
if not self.namespace:
|
|
327
|
+
def error():
|
|
157
328
|
if self.in_repl:
|
|
158
329
|
log2('Namespace is required.')
|
|
159
330
|
else:
|
|
160
331
|
log2('* namespace is missing.')
|
|
161
332
|
log2()
|
|
162
333
|
display_help()
|
|
334
|
+
return (False, error)
|
|
163
335
|
|
|
164
|
-
|
|
336
|
+
elif required == RequiredState.PG_DATABASE:
|
|
337
|
+
if self.device != ReplState.P:
|
|
338
|
+
return (False, None)
|
|
165
339
|
|
|
166
|
-
|
|
167
|
-
pg = PostgresSession(self.namespace, self.pg_path)
|
|
340
|
+
pg: PostgresContext = PostgresContext.apply(self.namespace, self.pg_path)
|
|
168
341
|
if not pg.db:
|
|
342
|
+
def error():
|
|
343
|
+
if self.in_repl:
|
|
344
|
+
log2('cd to a database first.')
|
|
345
|
+
else:
|
|
346
|
+
log2('* database is missing.')
|
|
347
|
+
log2()
|
|
348
|
+
display_help()
|
|
349
|
+
return (False, error)
|
|
350
|
+
|
|
351
|
+
elif required == RequiredState.APP_APP:
|
|
352
|
+
if self.device != ReplState.A:
|
|
353
|
+
return (False, None)
|
|
354
|
+
|
|
355
|
+
if not self.app_app:
|
|
356
|
+
def error():
|
|
357
|
+
if self.in_repl:
|
|
358
|
+
log2('cd to an app first.')
|
|
359
|
+
else:
|
|
360
|
+
log2('* app is missing.')
|
|
361
|
+
log2()
|
|
362
|
+
display_help()
|
|
363
|
+
return (False, error)
|
|
364
|
+
|
|
365
|
+
elif required == RequiredState.EXPORT_DB:
|
|
366
|
+
if self.device not in [ReplState.C, ReplState.X]:
|
|
367
|
+
return (False, None)
|
|
368
|
+
|
|
369
|
+
if not self.export_session:
|
|
370
|
+
def error():
|
|
371
|
+
if self.in_repl:
|
|
372
|
+
log2("Select an export database first with 'use' command.")
|
|
373
|
+
else:
|
|
374
|
+
log2('* export database is missing.')
|
|
375
|
+
log2()
|
|
376
|
+
display_help()
|
|
377
|
+
return (False, error)
|
|
378
|
+
|
|
379
|
+
elif required in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X] and self.device != required:
|
|
380
|
+
def error():
|
|
169
381
|
if self.in_repl:
|
|
170
|
-
log2('
|
|
382
|
+
log2(f'Switch to {required}: first.')
|
|
171
383
|
else:
|
|
172
|
-
log2('*
|
|
384
|
+
log2('* on a wrong device.')
|
|
173
385
|
log2()
|
|
174
386
|
display_help()
|
|
387
|
+
return (False, error)
|
|
175
388
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if app_required == RequiredState.APP_APP and not self.app_app:
|
|
179
|
-
if self.in_repl:
|
|
180
|
-
log2('cd to an app first.')
|
|
181
|
-
else:
|
|
182
|
-
log2('* app is missing.')
|
|
183
|
-
log2()
|
|
184
|
-
display_help()
|
|
389
|
+
return (True, None)
|
|
185
390
|
|
|
186
|
-
|
|
391
|
+
def _validate_device(self, devices: list[RequiredState]):
|
|
392
|
+
if self.device not in devices:
|
|
393
|
+
def error():
|
|
394
|
+
if self.in_repl:
|
|
395
|
+
log2(f'Not a valid command on {self.device}: drive.')
|
|
396
|
+
else:
|
|
397
|
+
log2('* on a wrong device.')
|
|
398
|
+
log2()
|
|
399
|
+
display_help()
|
|
400
|
+
return (False, error)
|
|
187
401
|
|
|
188
|
-
return True
|
|
402
|
+
return (True, None)
|
|
189
403
|
|
|
190
404
|
def user_pass(self, secret_path = 'cql.secret'):
|
|
191
405
|
return Secrets.get_user_pass(self.pod if self.pod else self.sts, self.namespace, secret_path=secret_path)
|
|
192
406
|
|
|
193
407
|
def enter_bash(self, bash_session: BashSession):
|
|
408
|
+
self.push()
|
|
409
|
+
|
|
194
410
|
self.bash_session = bash_session
|
|
195
|
-
if self.device != ReplState.C:
|
|
196
|
-
self.device = ReplState.C
|
|
197
|
-
log2(f'Moved to {ReplState.C}: automatically. Will move back to {ReplState.P}: when you exit the bash session.')
|
|
198
411
|
|
|
199
412
|
def exit_bash(self):
|
|
200
|
-
|
|
201
|
-
self.device = self.bash_session.device
|
|
202
|
-
|
|
413
|
+
self.pop()
|
|
203
414
|
self.bash_session = None
|
|
204
415
|
|
|
205
|
-
def
|
|
206
|
-
if not self.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
416
|
+
def push(self):
|
|
417
|
+
if not self.original_state:
|
|
418
|
+
self.original_state = copy(self)
|
|
419
|
+
|
|
420
|
+
def pop(self):
|
|
421
|
+
if o := self.original_state:
|
|
422
|
+
self.device = o.device
|
|
423
|
+
self.sts = o.sts
|
|
424
|
+
self.pod = o.pod
|
|
425
|
+
self.pg_path = o.pg_path
|
|
426
|
+
self.app_env = o.app_env
|
|
427
|
+
self.app_app = o.app_app
|
|
428
|
+
self.app_pod = o.app_pod
|
|
429
|
+
# self.export_session = o.export_session
|
|
430
|
+
self.namespace = o.namespace
|
|
431
|
+
|
|
432
|
+
self.original_state = None
|
adam/sql/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
4
|
+
import sqlparse
|
|
5
|
+
from sqlparse.sql import Statement, Token
|
|
6
|
+
|
|
7
|
+
from adam.sql.term_completer import TermCompleter
|
|
8
|
+
from adam.utils_repl.automata_completer import AutomataCompleter
|
|
9
|
+
from adam.sql.sql_state_machine import AthenaStateMachine, CqlStateMachine, SqlStateMachine
|
|
10
|
+
from adam.utils_repl.state_machine import State
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"SqlCompleter",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
def default_columns(x: list[str]):
|
|
17
|
+
return 'id,x.,y.,z.'.split(',')
|
|
18
|
+
|
|
19
|
+
class SqlVariant(Enum):
|
|
20
|
+
SQL = 'sql'
|
|
21
|
+
CQL = 'cql'
|
|
22
|
+
ATHENA = 'athena'
|
|
23
|
+
|
|
24
|
+
class SqlCompleter(AutomataCompleter[Token]):
|
|
25
|
+
def tokens(self, text: str) -> list[Token]:
|
|
26
|
+
tokens = []
|
|
27
|
+
|
|
28
|
+
stmts = sqlparse.parse(text)
|
|
29
|
+
if not stmts:
|
|
30
|
+
tokens = []
|
|
31
|
+
else:
|
|
32
|
+
statement: Statement = stmts[0]
|
|
33
|
+
tokens = statement.tokens
|
|
34
|
+
|
|
35
|
+
return tokens
|
|
36
|
+
|
|
37
|
+
def __init__(self,
|
|
38
|
+
tables: Callable[[], list[str]],
|
|
39
|
+
dml: str = None,
|
|
40
|
+
expandables: dict = {},
|
|
41
|
+
variant: SqlVariant = SqlVariant.SQL,
|
|
42
|
+
debug = False):
|
|
43
|
+
machine = SqlStateMachine(debug=debug)
|
|
44
|
+
if variant == SqlVariant.CQL:
|
|
45
|
+
machine = CqlStateMachine(debug=debug)
|
|
46
|
+
elif variant == SqlVariant.ATHENA:
|
|
47
|
+
machine = AthenaStateMachine(debug=debug)
|
|
48
|
+
super().__init__(machine, dml, debug)
|
|
49
|
+
|
|
50
|
+
self.tables = tables
|
|
51
|
+
if 'columns' not in expandables:
|
|
52
|
+
expandables['columns'] = default_columns
|
|
53
|
+
self.expandables = expandables
|
|
54
|
+
self.variant = variant
|
|
55
|
+
self.debug = debug
|
|
56
|
+
|
|
57
|
+
def suggestions_completer(self, state: State, suggestions: str) -> list[str]:
|
|
58
|
+
if not suggestions:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
terms = []
|
|
62
|
+
for suggestion in suggestions.split(','):
|
|
63
|
+
terms.extend(self._terms(state, suggestion))
|
|
64
|
+
|
|
65
|
+
return TermCompleter(terms)
|
|
66
|
+
|
|
67
|
+
def _terms(self, state: State, word: str) -> list[str]:
|
|
68
|
+
terms = []
|
|
69
|
+
|
|
70
|
+
if word.startswith('`') and word.endswith('`'):
|
|
71
|
+
terms.append(word.strip('`'))
|
|
72
|
+
elif word == 'tables':
|
|
73
|
+
terms.extend(self.tables())
|
|
74
|
+
elif word == 'columns':
|
|
75
|
+
if 'last_name' in state.context and (n := state.context['last_name']):
|
|
76
|
+
if 'last_namespace' in state.context and (ns := state.context['last_namespace']):
|
|
77
|
+
n = f'{ns}.{n}'
|
|
78
|
+
terms.extend(self._call_expandable(word, [n]))
|
|
79
|
+
else:
|
|
80
|
+
terms.extend(self._call_expandable(word, []))
|
|
81
|
+
elif word == 'partition-columns':
|
|
82
|
+
terms.extend(self._call_expandable(word, []))
|
|
83
|
+
elif word == 'table-props':
|
|
84
|
+
terms.extend(self._call_expandable(word).keys())
|
|
85
|
+
elif word == 'table-prop-values':
|
|
86
|
+
if 'last_name' in state.context and state.context['last_name']:
|
|
87
|
+
table_props = self._call_expandable('table-props')
|
|
88
|
+
terms.extend(table_props[state.context['last_name']])
|
|
89
|
+
elif word == 'single':
|
|
90
|
+
terms.append("'")
|
|
91
|
+
elif word == 'comma':
|
|
92
|
+
terms.append(",")
|
|
93
|
+
elif word in self.machine.expandable_names():
|
|
94
|
+
terms.extend(self._call_expandable(word))
|
|
95
|
+
else:
|
|
96
|
+
terms.append(word)
|
|
97
|
+
|
|
98
|
+
return terms
|
|
99
|
+
|
|
100
|
+
def _call_expandable(self, name: str, *args):
|
|
101
|
+
if name in self.expandables:
|
|
102
|
+
c = self.expandables[name]
|
|
103
|
+
if args:
|
|
104
|
+
return c(args)
|
|
105
|
+
else:
|
|
106
|
+
return c()
|
|
107
|
+
|
|
108
|
+
return []
|
|
109
|
+
|
|
110
|
+
def completions_for_nesting(self, dml: str = None):
|
|
111
|
+
if dml:
|
|
112
|
+
return {dml: SqlCompleter(self.tables, dml, expandables=self.expandables, variant=self.variant)}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
word: SqlCompleter(self.tables, word, expandables=self.expandables, variant=self.variant)
|
|
116
|
+
for word in self.machine.suggestions[''].strip(' ').split(',')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
def __str__(self):
|
|
120
|
+
return f'{self.variant}, {self.first_term}'
|