tabsdata-cli 2.0.0__cp312-abi3-win_amd64.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.
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/__init__.py +5 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/_registry.py +78 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/BANNER +5 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/LICENSE +3 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/README-PyPi.md +8 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/RELEASE-NOTES +1 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/THIRD-PARTY +3 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/VERSION +1 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/cli_utils.py +993 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/shell.py +390 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/__init__.py +3 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/auth_group.py +197 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/cli.py +508 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/collection_group.py +458 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/config_utils.py +93 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/connection_group.py +159 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/examples_group.py +206 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/execution_utils.py +429 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/fn_group.py +1109 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/fnrun_group.py +285 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/group_group.py +184 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/metrics_group.py +323 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/permission_group.py +259 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/plan_group.py +324 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/project_group.py +170 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/role_group.py +630 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/settings_group.py +344 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/table_group.py +600 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/trigger_group.py +338 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/trx_group.py +329 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/user_group.py +200 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/worker_group.py +185 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/template.py +781 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/py.typed +0 -0
- tabsdata_cli-2.0.0.dist-info/METADATA +39 -0
- tabsdata_cli-2.0.0.dist-info/RECORD +39 -0
- tabsdata_cli-2.0.0.dist-info/WHEEL +5 -0
- tabsdata_cli-2.0.0.dist-info/licenses/src/main/python/tabsdatak/cli/assets/manifest/LICENSE +3 -0
- tabsdata_cli-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2026 Tabsdata Inc.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
import rich_click as click
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
from tabsdatak.cli._registry import cli_command
|
|
13
|
+
from tabsdatak.cli.cli_utils import (
|
|
14
|
+
MutuallyExclusiveOption,
|
|
15
|
+
beautify_time,
|
|
16
|
+
echo_more_results,
|
|
17
|
+
format_option,
|
|
18
|
+
get_currently_pinned_object,
|
|
19
|
+
hint_common_solutions,
|
|
20
|
+
logical_prompt,
|
|
21
|
+
make_console,
|
|
22
|
+
max_result_option,
|
|
23
|
+
proj_option,
|
|
24
|
+
render,
|
|
25
|
+
take,
|
|
26
|
+
verify_login_or_prompt,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from tabsdatak.sdk.tabsdata_server import TabsdataServer
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
_TABLE_STATUS_LABELS = {"A": "Active", "F": "Frozen", "D": "Deleted"}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _size_mb(data_bytes) -> str:
|
|
37
|
+
if data_bytes is None:
|
|
38
|
+
return "-"
|
|
39
|
+
return f"{int(data_bytes) / (1024 * 1024):.2f}"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@cli_command
|
|
43
|
+
@click.group()
|
|
44
|
+
def table():
|
|
45
|
+
"""Table management commands"""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@table.command()
|
|
49
|
+
@proj_option
|
|
50
|
+
@click.option(
|
|
51
|
+
"--name",
|
|
52
|
+
"-n",
|
|
53
|
+
help="Name of the table to be deleted.",
|
|
54
|
+
)
|
|
55
|
+
@click.option(
|
|
56
|
+
"--coll",
|
|
57
|
+
"-c",
|
|
58
|
+
help="Name of the collection to which the table belongs.",
|
|
59
|
+
)
|
|
60
|
+
@click.option(
|
|
61
|
+
"--confirm",
|
|
62
|
+
help="Write 'delete' to confirm deletion. Will be prompted for it if not provided.",
|
|
63
|
+
)
|
|
64
|
+
@click.pass_context
|
|
65
|
+
def delete(ctx: click.Context, name: str, coll: str, confirm: str):
|
|
66
|
+
"""Delete a table"""
|
|
67
|
+
verify_login_or_prompt(ctx)
|
|
68
|
+
name = name or logical_prompt(ctx, "Name of the table to be deleted")
|
|
69
|
+
coll = (
|
|
70
|
+
coll
|
|
71
|
+
or get_currently_pinned_object(ctx, "collection")
|
|
72
|
+
or logical_prompt(ctx, "Name of the collection to which the table belongs")
|
|
73
|
+
)
|
|
74
|
+
click.echo(f"Deleting table '{name}' in collection '{coll}'")
|
|
75
|
+
click.echo("-" * 10)
|
|
76
|
+
confirm = confirm or logical_prompt(ctx, "Please type 'delete' to confirm deletion")
|
|
77
|
+
if confirm != "delete":
|
|
78
|
+
raise click.ClickException(
|
|
79
|
+
"Deletion not confirmed. The confirmation word is 'delete'."
|
|
80
|
+
)
|
|
81
|
+
try:
|
|
82
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
83
|
+
server.delete_table(coll, name)
|
|
84
|
+
click.echo("Table deleted successfully")
|
|
85
|
+
except Exception as e:
|
|
86
|
+
hint_common_solutions(ctx, e)
|
|
87
|
+
raise click.ClickException(f"Failed to delete table: {e}")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@table.command()
|
|
91
|
+
@proj_option
|
|
92
|
+
@click.option(
|
|
93
|
+
"--name",
|
|
94
|
+
help="Name of the table.",
|
|
95
|
+
)
|
|
96
|
+
@click.option(
|
|
97
|
+
"--coll",
|
|
98
|
+
"-c",
|
|
99
|
+
help="Name of the collection to which the table belongs.",
|
|
100
|
+
)
|
|
101
|
+
@click.option(
|
|
102
|
+
"--file",
|
|
103
|
+
help=(
|
|
104
|
+
"File in which the table will be stored. Can be an absolute or a relative "
|
|
105
|
+
"path. Any directory that appears must already exist (it will not be created "
|
|
106
|
+
"as part of the download)."
|
|
107
|
+
),
|
|
108
|
+
)
|
|
109
|
+
@click.option(
|
|
110
|
+
"--at",
|
|
111
|
+
help=(
|
|
112
|
+
"If provided, the table values at the given time will be shown. Must be "
|
|
113
|
+
" either a valid timestamp in the form of a unix timestamp (milliseconds since "
|
|
114
|
+
"epoch without a dot, e.g. '1750074554472') or a valid date-time format. "
|
|
115
|
+
"The valid "
|
|
116
|
+
"formats are 'YYYY-MM-DD', 'YYYY-MM-DDTHH', 'YYYY-MM-DDTHH:MM', "
|
|
117
|
+
"'YYYY-MM-DDTHH:MM:SS', and 'YYYY-MM-DDTHH:MM:SS.sss'. A 'Z' character can be "
|
|
118
|
+
"added at the end to indicate UTC time (e.g., '2023-10-01T12Z' or "
|
|
119
|
+
"'2023-10-01T12:00:00.000Z') or it can be omitted to indicate local "
|
|
120
|
+
"time (e.g., '2023-10-01T12' or '2023-10-01T12:00:00.000')."
|
|
121
|
+
),
|
|
122
|
+
type=str,
|
|
123
|
+
cls=MutuallyExclusiveOption,
|
|
124
|
+
mutually_exclusive=["at-trx", "version"],
|
|
125
|
+
)
|
|
126
|
+
@click.option(
|
|
127
|
+
"--at-trx",
|
|
128
|
+
help=(
|
|
129
|
+
"ID of a transaction. If provided, the table values at the end of the given "
|
|
130
|
+
"transaction will be shown."
|
|
131
|
+
),
|
|
132
|
+
type=str,
|
|
133
|
+
cls=MutuallyExclusiveOption,
|
|
134
|
+
mutually_exclusive=["at", "version"],
|
|
135
|
+
)
|
|
136
|
+
@click.option(
|
|
137
|
+
"--version",
|
|
138
|
+
help=(
|
|
139
|
+
"ID of a dataversion of the table. If provided, the table values at that "
|
|
140
|
+
"dataversion will be shown."
|
|
141
|
+
),
|
|
142
|
+
type=str,
|
|
143
|
+
cls=MutuallyExclusiveOption,
|
|
144
|
+
mutually_exclusive=["at", "at-trx"],
|
|
145
|
+
)
|
|
146
|
+
@click.pass_context
|
|
147
|
+
def download(
|
|
148
|
+
ctx: click.Context,
|
|
149
|
+
coll: str,
|
|
150
|
+
name: str,
|
|
151
|
+
file: str,
|
|
152
|
+
at: str,
|
|
153
|
+
at_trx: str,
|
|
154
|
+
version: str,
|
|
155
|
+
):
|
|
156
|
+
"""Download the table as a parquet file"""
|
|
157
|
+
verify_login_or_prompt(ctx)
|
|
158
|
+
file = file or logical_prompt(ctx, "File in which the table will be stored")
|
|
159
|
+
name = name or logical_prompt(ctx, "Name of the table")
|
|
160
|
+
coll = (
|
|
161
|
+
coll
|
|
162
|
+
or get_currently_pinned_object(ctx, "collection")
|
|
163
|
+
or logical_prompt(ctx, "Name of the collection to which the table belongs")
|
|
164
|
+
)
|
|
165
|
+
click.echo(f"Downloading table to file '{file}'")
|
|
166
|
+
click.echo("-" * 10)
|
|
167
|
+
try:
|
|
168
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
169
|
+
server.download_table(
|
|
170
|
+
coll,
|
|
171
|
+
name,
|
|
172
|
+
file,
|
|
173
|
+
at=at,
|
|
174
|
+
at_trx=at_trx,
|
|
175
|
+
version=version,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
click.echo("Table downloaded successfully")
|
|
179
|
+
|
|
180
|
+
except Exception as e:
|
|
181
|
+
hint_common_solutions(ctx, e)
|
|
182
|
+
raise click.ClickException(f"Failed to download table: {e}")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@table.command()
|
|
186
|
+
@proj_option
|
|
187
|
+
@click.option(
|
|
188
|
+
"--coll",
|
|
189
|
+
"-c",
|
|
190
|
+
help="Name of the collection to which the tables belong.",
|
|
191
|
+
)
|
|
192
|
+
@click.option(
|
|
193
|
+
"--name",
|
|
194
|
+
"-n",
|
|
195
|
+
help=(
|
|
196
|
+
"A name wildcard to match for the list. "
|
|
197
|
+
"For example, 'my_table*' will match all tables "
|
|
198
|
+
"starting with 'my_table'."
|
|
199
|
+
),
|
|
200
|
+
)
|
|
201
|
+
@click.option(
|
|
202
|
+
"--at",
|
|
203
|
+
help=(
|
|
204
|
+
"If provided, the table values at the given time will be shown. Must be either"
|
|
205
|
+
" a valid timestamp in the form of a unix timestamp (milliseconds since epoch"
|
|
206
|
+
" without a dot, e.g. '1750074554472') or a valid date-time format. The valid"
|
|
207
|
+
" formats are 'YYYY-MM-DD', 'YYYY-MM-DDTHH', 'YYYY-MM-DDTHH:MM',"
|
|
208
|
+
" 'YYYY-MM-DDTHH:MM:SS', and 'YYYY-MM-DDTHH:MM:SS.sss'. A 'Z' character can be"
|
|
209
|
+
" added at the end to indicate UTC time (e.g., '2023-10-01T12Z' or"
|
|
210
|
+
" '2023-10-01T12:00:00.000Z') or it can be omitted to indicate local time"
|
|
211
|
+
" (e.g., '2023-10-01T12' or '2023-10-01T12:00:00.000')."
|
|
212
|
+
),
|
|
213
|
+
type=str,
|
|
214
|
+
cls=MutuallyExclusiveOption,
|
|
215
|
+
mutually_exclusive=["at-trx"],
|
|
216
|
+
)
|
|
217
|
+
@click.option(
|
|
218
|
+
"--at-trx",
|
|
219
|
+
help=(
|
|
220
|
+
"ID of a transaction. If provided, the table values at the end of the given "
|
|
221
|
+
"transaction will be shown."
|
|
222
|
+
),
|
|
223
|
+
type=str,
|
|
224
|
+
cls=MutuallyExclusiveOption,
|
|
225
|
+
mutually_exclusive=["at"],
|
|
226
|
+
)
|
|
227
|
+
@format_option
|
|
228
|
+
@max_result_option
|
|
229
|
+
@click.pass_context
|
|
230
|
+
def list(ctx: click.Context, coll: str, name: str, at: str, at_trx: str):
|
|
231
|
+
"""List all tables in a collection"""
|
|
232
|
+
verify_login_or_prompt(ctx)
|
|
233
|
+
coll = (
|
|
234
|
+
coll
|
|
235
|
+
or get_currently_pinned_object(ctx, "collection")
|
|
236
|
+
or logical_prompt(ctx, "Name of the collection to which the tables belong")
|
|
237
|
+
)
|
|
238
|
+
try:
|
|
239
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
240
|
+
request_filter = []
|
|
241
|
+
if name:
|
|
242
|
+
request_filter.append(f"name:lk:{name}")
|
|
243
|
+
list_of_tables, truncated = take(
|
|
244
|
+
ctx,
|
|
245
|
+
server.list_tables_generator(
|
|
246
|
+
coll, filter=request_filter, at=at, at_trx=at_trx
|
|
247
|
+
),
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def _table():
|
|
251
|
+
from tabsdatak.sdk.tabsdata_server import _convert_timestamp_to_string
|
|
252
|
+
|
|
253
|
+
cli_table = Table(title="Tables")
|
|
254
|
+
cli_table.add_column("Name", style="cyan", no_wrap=True)
|
|
255
|
+
cli_table.add_column("Function")
|
|
256
|
+
cli_table.add_column("Status")
|
|
257
|
+
cli_table.add_column("Private")
|
|
258
|
+
cli_table.add_column("Created by")
|
|
259
|
+
cli_table.add_column("Created on")
|
|
260
|
+
for table in list_of_tables:
|
|
261
|
+
data = table.kwargs
|
|
262
|
+
defined_on = data.get("defined_on")
|
|
263
|
+
cli_table.add_row(
|
|
264
|
+
table.name,
|
|
265
|
+
table.function.name,
|
|
266
|
+
_TABLE_STATUS_LABELS.get(
|
|
267
|
+
data.get("status"), data.get("status") or "-"
|
|
268
|
+
),
|
|
269
|
+
"Yes" if data.get("private") else "No",
|
|
270
|
+
data.get("defined_by") or "-",
|
|
271
|
+
_convert_timestamp_to_string(defined_on) if defined_on else "-",
|
|
272
|
+
)
|
|
273
|
+
click.echo()
|
|
274
|
+
make_console().print(cli_table)
|
|
275
|
+
click.echo(
|
|
276
|
+
f"Number of tables in collection '{coll}': {len(list_of_tables)}"
|
|
277
|
+
)
|
|
278
|
+
click.echo()
|
|
279
|
+
|
|
280
|
+
render(
|
|
281
|
+
ctx,
|
|
282
|
+
data=lambda: [t._data for t in list_of_tables],
|
|
283
|
+
table=_table,
|
|
284
|
+
)
|
|
285
|
+
echo_more_results(ctx, truncated)
|
|
286
|
+
|
|
287
|
+
except Exception as e:
|
|
288
|
+
hint_common_solutions(ctx, e)
|
|
289
|
+
raise click.ClickException(f"Failed to list tables: {e}")
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@table.command()
|
|
293
|
+
@proj_option
|
|
294
|
+
@click.option(
|
|
295
|
+
"--name",
|
|
296
|
+
help="Name of the table.",
|
|
297
|
+
)
|
|
298
|
+
@click.option(
|
|
299
|
+
"--coll",
|
|
300
|
+
"-c",
|
|
301
|
+
help="Name of the collection to which the table belongs.",
|
|
302
|
+
)
|
|
303
|
+
@click.option(
|
|
304
|
+
"--len",
|
|
305
|
+
type=int,
|
|
306
|
+
help=(
|
|
307
|
+
"Maximum amount of rows to be shown. If not provided, the server default "
|
|
308
|
+
"of 100 rows applies. Cannot be greater than 1000."
|
|
309
|
+
),
|
|
310
|
+
)
|
|
311
|
+
@click.option(
|
|
312
|
+
"--offset",
|
|
313
|
+
type=int,
|
|
314
|
+
help=(
|
|
315
|
+
"How many rows to skip before starting to read. If not provided, "
|
|
316
|
+
"reading will start at the first row."
|
|
317
|
+
),
|
|
318
|
+
)
|
|
319
|
+
@click.option(
|
|
320
|
+
"--sql",
|
|
321
|
+
help=(
|
|
322
|
+
"A SQL query to run on the table server-side, before the rows are returned. "
|
|
323
|
+
"The table is the only queryable relation and is referenced by its own name "
|
|
324
|
+
"(for example, 'SELECT a, b FROM my_table WHERE a > 10'); referencing any "
|
|
325
|
+
"other table or a file-reading function is rejected. '--offset' is applied "
|
|
326
|
+
"before the query and '--len' caps its result."
|
|
327
|
+
),
|
|
328
|
+
)
|
|
329
|
+
@click.option(
|
|
330
|
+
"--at",
|
|
331
|
+
help=(
|
|
332
|
+
"If provided, the table values at the given time will be shown. Must be "
|
|
333
|
+
" either a valid timestamp in the form of a unix timestamp (milliseconds since "
|
|
334
|
+
"epoch without a dot, e.g. '1750074554472') or a valid date-time format. "
|
|
335
|
+
"The valid "
|
|
336
|
+
"formats are 'YYYY-MM-DD', 'YYYY-MM-DDTHH', 'YYYY-MM-DDTHH:MM', "
|
|
337
|
+
"'YYYY-MM-DDTHH:MM:SS', and 'YYYY-MM-DDTHH:MM:SS.sss'. A 'Z' character can be "
|
|
338
|
+
"added at the end to indicate UTC time (e.g., '2023-10-01T12Z' or "
|
|
339
|
+
"'2023-10-01T12:00:00.000Z') or it can be omitted to indicate local "
|
|
340
|
+
"time (e.g., '2023-10-01T12' or '2023-10-01T12:00:00.000')."
|
|
341
|
+
),
|
|
342
|
+
type=str,
|
|
343
|
+
cls=MutuallyExclusiveOption,
|
|
344
|
+
mutually_exclusive=["at-trx", "version"],
|
|
345
|
+
)
|
|
346
|
+
@click.option(
|
|
347
|
+
"--at-trx",
|
|
348
|
+
help=(
|
|
349
|
+
"ID of a transaction. If provided, the table values at the end of the given "
|
|
350
|
+
"transaction will be shown."
|
|
351
|
+
),
|
|
352
|
+
type=str,
|
|
353
|
+
cls=MutuallyExclusiveOption,
|
|
354
|
+
mutually_exclusive=["at", "version"],
|
|
355
|
+
)
|
|
356
|
+
@click.option(
|
|
357
|
+
"--version",
|
|
358
|
+
help=(
|
|
359
|
+
"ID of a dataversion of the table. If provided, the table values at that "
|
|
360
|
+
"dataversion will be shown."
|
|
361
|
+
),
|
|
362
|
+
type=str,
|
|
363
|
+
cls=MutuallyExclusiveOption,
|
|
364
|
+
mutually_exclusive=["at", "at-trx"],
|
|
365
|
+
)
|
|
366
|
+
@click.option(
|
|
367
|
+
"--file",
|
|
368
|
+
help=(
|
|
369
|
+
"File in which the table will be stored as a NDJSON. Can be an absolute or a "
|
|
370
|
+
"relative path. Any directory that appears must already exist (it will not be "
|
|
371
|
+
"created as part of the download)."
|
|
372
|
+
),
|
|
373
|
+
)
|
|
374
|
+
@format_option
|
|
375
|
+
@click.pass_context
|
|
376
|
+
def data(
|
|
377
|
+
ctx: click.Context,
|
|
378
|
+
coll: str,
|
|
379
|
+
name: str,
|
|
380
|
+
len: int,
|
|
381
|
+
offset: int,
|
|
382
|
+
sql: str,
|
|
383
|
+
file: str,
|
|
384
|
+
at: str,
|
|
385
|
+
at_trx: str,
|
|
386
|
+
version: str,
|
|
387
|
+
):
|
|
388
|
+
"""Show rows of data from the table"""
|
|
389
|
+
verify_login_or_prompt(ctx)
|
|
390
|
+
name = name or logical_prompt(ctx, "Name of the table")
|
|
391
|
+
coll = (
|
|
392
|
+
coll
|
|
393
|
+
or get_currently_pinned_object(ctx, "collection")
|
|
394
|
+
or logical_prompt(ctx, "Name of the collection to which the table belongs")
|
|
395
|
+
)
|
|
396
|
+
try:
|
|
397
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
398
|
+
table_data = server.sample_table(
|
|
399
|
+
coll,
|
|
400
|
+
name,
|
|
401
|
+
at=at,
|
|
402
|
+
at_trx=at_trx,
|
|
403
|
+
version=version,
|
|
404
|
+
len=len,
|
|
405
|
+
offset=offset,
|
|
406
|
+
sql=sql,
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
def _table():
|
|
410
|
+
click.echo()
|
|
411
|
+
click.echo(f"Data of table '{name}'")
|
|
412
|
+
click.echo(table_data)
|
|
413
|
+
click.echo()
|
|
414
|
+
|
|
415
|
+
# table_data is a polars DataFrame -> its rows are the full info.
|
|
416
|
+
render(ctx, data=lambda: table_data.to_dicts(), table=_table)
|
|
417
|
+
|
|
418
|
+
if file:
|
|
419
|
+
# Diagnostics to stderr so json/yaml stdout stays parseable.
|
|
420
|
+
click.echo(f"Saving data to file '{file}'", err=True)
|
|
421
|
+
table_data.write_ndjson(file)
|
|
422
|
+
click.echo("Data saved successfully", err=True)
|
|
423
|
+
|
|
424
|
+
except Exception as e:
|
|
425
|
+
hint_common_solutions(ctx, e)
|
|
426
|
+
raise click.ClickException(f"Failed to obtain table data: {e}")
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
@table.command()
|
|
430
|
+
@proj_option
|
|
431
|
+
@click.option(
|
|
432
|
+
"--name",
|
|
433
|
+
help="Name of the table.",
|
|
434
|
+
)
|
|
435
|
+
@click.option(
|
|
436
|
+
"--coll",
|
|
437
|
+
"-c",
|
|
438
|
+
help="Name of the collection to which the table belongs.",
|
|
439
|
+
)
|
|
440
|
+
@click.option(
|
|
441
|
+
"--at",
|
|
442
|
+
help=(
|
|
443
|
+
"If provided, the table values at the given time will be shown. Must be "
|
|
444
|
+
" either a valid timestamp in the form of a unix timestamp (milliseconds since "
|
|
445
|
+
"epoch without a dot, e.g. '1750074554472') or a valid date-time format. "
|
|
446
|
+
"The valid "
|
|
447
|
+
"formats are 'YYYY-MM-DD', 'YYYY-MM-DDTHH', 'YYYY-MM-DDTHH:MM', "
|
|
448
|
+
"'YYYY-MM-DDTHH:MM:SS', and 'YYYY-MM-DDTHH:MM:SS.sss'. A 'Z' character can be "
|
|
449
|
+
"added at the end to indicate UTC time (e.g., '2023-10-01T12Z' or "
|
|
450
|
+
"'2023-10-01T12:00:00.000Z') or it can be omitted to indicate local "
|
|
451
|
+
"time (e.g., '2023-10-01T12' or '2023-10-01T12:00:00.000')."
|
|
452
|
+
),
|
|
453
|
+
type=str,
|
|
454
|
+
cls=MutuallyExclusiveOption,
|
|
455
|
+
mutually_exclusive=["at-trx", "version"],
|
|
456
|
+
)
|
|
457
|
+
@click.option(
|
|
458
|
+
"--at-trx",
|
|
459
|
+
help=(
|
|
460
|
+
"ID of a transaction. If provided, the table values at the end of the given "
|
|
461
|
+
"transaction will be shown."
|
|
462
|
+
),
|
|
463
|
+
type=str,
|
|
464
|
+
cls=MutuallyExclusiveOption,
|
|
465
|
+
mutually_exclusive=["at", "version"],
|
|
466
|
+
)
|
|
467
|
+
@click.option(
|
|
468
|
+
"--version",
|
|
469
|
+
help=(
|
|
470
|
+
"ID of a dataversion of the table. If provided, the table values at that "
|
|
471
|
+
"dataversion will be shown."
|
|
472
|
+
),
|
|
473
|
+
type=str,
|
|
474
|
+
cls=MutuallyExclusiveOption,
|
|
475
|
+
mutually_exclusive=["at", "at-trx"],
|
|
476
|
+
)
|
|
477
|
+
@format_option
|
|
478
|
+
@click.pass_context
|
|
479
|
+
def schema(
|
|
480
|
+
ctx: click.Context,
|
|
481
|
+
coll: str,
|
|
482
|
+
name: str,
|
|
483
|
+
at: str,
|
|
484
|
+
at_trx: str,
|
|
485
|
+
version: str,
|
|
486
|
+
):
|
|
487
|
+
"""Show table schema"""
|
|
488
|
+
verify_login_or_prompt(ctx)
|
|
489
|
+
name = name or logical_prompt(ctx, "Name of the table")
|
|
490
|
+
coll = (
|
|
491
|
+
coll
|
|
492
|
+
or get_currently_pinned_object(ctx, "collection")
|
|
493
|
+
or logical_prompt(ctx, "Name of the collection to which the table belongs")
|
|
494
|
+
)
|
|
495
|
+
try:
|
|
496
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
497
|
+
table_schema = server.get_table_schema(
|
|
498
|
+
coll,
|
|
499
|
+
name,
|
|
500
|
+
at=at,
|
|
501
|
+
at_trx=at_trx,
|
|
502
|
+
version=version,
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
def _table():
|
|
506
|
+
table = Table(title="Schema")
|
|
507
|
+
table.add_column("Column", style="cyan", no_wrap=True)
|
|
508
|
+
table.add_column("Type")
|
|
509
|
+
for column in table_schema:
|
|
510
|
+
table.add_row(column["name"], column["type"])
|
|
511
|
+
click.echo()
|
|
512
|
+
make_console().print(table)
|
|
513
|
+
click.echo()
|
|
514
|
+
|
|
515
|
+
# table_schema is already the raw list of column dicts -> full info.
|
|
516
|
+
render(ctx, data=lambda: table_schema, table=_table)
|
|
517
|
+
|
|
518
|
+
except Exception as e:
|
|
519
|
+
hint_common_solutions(ctx, e)
|
|
520
|
+
raise click.ClickException(f"Failed to obtain table schema: {e}")
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
@table.command()
|
|
524
|
+
@proj_option
|
|
525
|
+
@click.option(
|
|
526
|
+
"--coll",
|
|
527
|
+
"-c",
|
|
528
|
+
help="Name of the collection to which the data belongs.",
|
|
529
|
+
)
|
|
530
|
+
@click.option(
|
|
531
|
+
"--name",
|
|
532
|
+
help="The name of the table to which the data belongs.",
|
|
533
|
+
)
|
|
534
|
+
@format_option
|
|
535
|
+
@max_result_option
|
|
536
|
+
@click.pass_context
|
|
537
|
+
def versions(ctx: click.Context, coll: str, name: str):
|
|
538
|
+
"""
|
|
539
|
+
List all versions of the data of a table.
|
|
540
|
+
"""
|
|
541
|
+
verify_login_or_prompt(ctx)
|
|
542
|
+
coll = (
|
|
543
|
+
coll
|
|
544
|
+
or get_currently_pinned_object(ctx, "collection")
|
|
545
|
+
or logical_prompt(ctx, "Name of the collection to which the data belongs")
|
|
546
|
+
)
|
|
547
|
+
name = name or logical_prompt(
|
|
548
|
+
ctx, "The name of the table to which the data belongs"
|
|
549
|
+
)
|
|
550
|
+
try:
|
|
551
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
552
|
+
# /data-versions returns all statuses; the CLI lists committed only.
|
|
553
|
+
data_version_list, truncated = take(
|
|
554
|
+
ctx,
|
|
555
|
+
server.list_dataversions_generator(coll, name, filter="status:eq:C"),
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
def _table():
|
|
559
|
+
table = Table(title="Data versions")
|
|
560
|
+
table.add_column("Version", style="cyan", no_wrap=True)
|
|
561
|
+
table.add_column("Function")
|
|
562
|
+
table.add_column("Function version", no_wrap=True)
|
|
563
|
+
table.add_column("Execution plan ID", no_wrap=True)
|
|
564
|
+
table.add_column("Transaction ID", no_wrap=True)
|
|
565
|
+
table.add_column("Created on", no_wrap=True)
|
|
566
|
+
table.add_column("Schema change")
|
|
567
|
+
table.add_column("Data changed")
|
|
568
|
+
table.add_column("Rows")
|
|
569
|
+
table.add_column("Size (MB)")
|
|
570
|
+
table.add_column("Status")
|
|
571
|
+
for data_version in data_version_list:
|
|
572
|
+
data = data_version.kwargs
|
|
573
|
+
row_count = data.get("row_count")
|
|
574
|
+
table.add_row(
|
|
575
|
+
data_version.id,
|
|
576
|
+
data.get("function") or "-",
|
|
577
|
+
data.get("function_version_id") or "-",
|
|
578
|
+
data.get("execution_id") or "-",
|
|
579
|
+
data.get("transaction_id") or "-",
|
|
580
|
+
beautify_time(data_version.created_at_str),
|
|
581
|
+
"Yes" if data.get("schema_data_changed") else "No",
|
|
582
|
+
"Yes" if data.get("data_changed") else "No",
|
|
583
|
+
str(row_count) if row_count is not None else "-",
|
|
584
|
+
_size_mb(data.get("data_bytes")),
|
|
585
|
+
data_version.status,
|
|
586
|
+
)
|
|
587
|
+
click.echo()
|
|
588
|
+
make_console().print(table)
|
|
589
|
+
click.echo(f"Number of data versions: {len(data_version_list)}")
|
|
590
|
+
|
|
591
|
+
render(
|
|
592
|
+
ctx,
|
|
593
|
+
data=lambda: [dv._data for dv in data_version_list],
|
|
594
|
+
table=_table,
|
|
595
|
+
)
|
|
596
|
+
echo_more_results(ctx, truncated)
|
|
597
|
+
|
|
598
|
+
except Exception as e:
|
|
599
|
+
hint_common_solutions(ctx, e)
|
|
600
|
+
raise click.ClickException(f"Failed to list data versions: {e}")
|