vgi-python 0.8.0__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.
- vgi/__init__.py +152 -0
- vgi/_duckdb.py +62 -0
- vgi/_storage_profile.py +132 -0
- vgi/_test_fixtures/__init__.py +20 -0
- vgi/_test_fixtures/accumulate/__init__.py +19 -0
- vgi/_test_fixtures/accumulate/worker.py +762 -0
- vgi/_test_fixtures/aggregate/__init__.py +62 -0
- vgi/_test_fixtures/aggregate/_common.py +21 -0
- vgi/_test_fixtures/aggregate/basic.py +232 -0
- vgi/_test_fixtures/aggregate/dynamic.py +409 -0
- vgi/_test_fixtures/aggregate/generic.py +86 -0
- vgi/_test_fixtures/aggregate/listagg.py +71 -0
- vgi/_test_fixtures/aggregate/percentile.py +107 -0
- vgi/_test_fixtures/aggregate/streaming.py +192 -0
- vgi/_test_fixtures/aggregate/varargs.py +75 -0
- vgi/_test_fixtures/aggregate/window.py +380 -0
- vgi/_test_fixtures/attach_options.py +308 -0
- vgi/_test_fixtures/bad_protocol.py +62 -0
- vgi/_test_fixtures/cancellable.py +336 -0
- vgi/_test_fixtures/catalog.py +813 -0
- vgi/_test_fixtures/http_server.py +394 -0
- vgi/_test_fixtures/nest_tensor.py +614 -0
- vgi/_test_fixtures/orchard_catalog.py +47 -0
- vgi/_test_fixtures/projection_repro/__init__.py +6 -0
- vgi/_test_fixtures/projection_repro/worker.py +454 -0
- vgi/_test_fixtures/scalar/__init__.py +116 -0
- vgi/_test_fixtures/scalar/_common.py +69 -0
- vgi/_test_fixtures/scalar/arithmetic.py +321 -0
- vgi/_test_fixtures/scalar/binary.py +120 -0
- vgi/_test_fixtures/scalar/formatting.py +176 -0
- vgi/_test_fixtures/scalar/geo.py +300 -0
- vgi/_test_fixtures/scalar/null_handling.py +107 -0
- vgi/_test_fixtures/scalar/random_demo.py +171 -0
- vgi/_test_fixtures/scalar/settings_secrets.py +102 -0
- vgi/_test_fixtures/scalar/type_info.py +219 -0
- vgi/_test_fixtures/schema_reconcile/__init__.py +29 -0
- vgi/_test_fixtures/schema_reconcile/worker.py +653 -0
- vgi/_test_fixtures/simple_writable.py +793 -0
- vgi/_test_fixtures/table/__init__.py +221 -0
- vgi/_test_fixtures/table/_common.py +162 -0
- vgi/_test_fixtures/table/batch_index.py +283 -0
- vgi/_test_fixtures/table/batch_index_broken.py +200 -0
- vgi/_test_fixtures/table/catalog_scans.py +162 -0
- vgi/_test_fixtures/table/filters.py +1005 -0
- vgi/_test_fixtures/table/late_materialization.py +249 -0
- vgi/_test_fixtures/table/make_series.py +273 -0
- vgi/_test_fixtures/table/misc.py +499 -0
- vgi/_test_fixtures/table/order_modes.py +164 -0
- vgi/_test_fixtures/table/pairs.py +437 -0
- vgi/_test_fixtures/table/partition_columns.py +472 -0
- vgi/_test_fixtures/table/partition_columns_broken.py +304 -0
- vgi/_test_fixtures/table/profiling_example.py +195 -0
- vgi/_test_fixtures/table/required_filters.py +234 -0
- vgi/_test_fixtures/table/sequence.py +710 -0
- vgi/_test_fixtures/table/settings.py +426 -0
- vgi/_test_fixtures/table/transaction_storage.py +162 -0
- vgi/_test_fixtures/table/tt_pushdown.py +191 -0
- vgi/_test_fixtures/table/versioned.py +230 -0
- vgi/_test_fixtures/table_in_out.py +1392 -0
- vgi/_test_fixtures/versioned.py +155 -0
- vgi/_test_fixtures/versioned_tables.py +595 -0
- vgi/_test_fixtures/worker.py +1631 -0
- vgi/_test_fixtures/writable/__init__.py +8 -0
- vgi/_test_fixtures/writable/generic.py +236 -0
- vgi/_test_fixtures/writable/table.py +149 -0
- vgi/_test_fixtures/writable/worker.py +1148 -0
- vgi/aggregate_function.py +607 -0
- vgi/argument_spec.py +472 -0
- vgi/arguments.py +1747 -0
- vgi/auth.py +55 -0
- vgi/catalog/__init__.py +88 -0
- vgi/catalog/attach_option.py +206 -0
- vgi/catalog/catalog_interface.py +2767 -0
- vgi/catalog/descriptors.py +870 -0
- vgi/catalog/duckdb_statistics.py +377 -0
- vgi/catalog/secret_type.py +96 -0
- vgi/catalog/setting.py +253 -0
- vgi/catalog/storage.py +372 -0
- vgi/client/__init__.py +67 -0
- vgi/client/catalog_mixin.py +1251 -0
- vgi/client/cli.py +582 -0
- vgi/client/cli_catalog.py +182 -0
- vgi/client/cli_schema.py +270 -0
- vgi/client/cli_table.py +907 -0
- vgi/client/cli_transaction.py +97 -0
- vgi/client/cli_utils.py +441 -0
- vgi/client/cli_view.py +303 -0
- vgi/client/client.py +2183 -0
- vgi/exceptions.py +205 -0
- vgi/function.py +245 -0
- vgi/function_storage.py +1636 -0
- vgi/function_storage_azure_sql.py +922 -0
- vgi/function_storage_cf_do.py +740 -0
- vgi/http/__init__.py +25 -0
- vgi/http/demo_storage.py +212 -0
- vgi/http/worker_page.py +1252 -0
- vgi/invocation.py +154 -0
- vgi/logging_config.py +93 -0
- vgi/meta_worker.py +661 -0
- vgi/metadata.py +1403 -0
- vgi/otel.py +406 -0
- vgi/protocol.py +2418 -0
- vgi/protocol_version.txt +1 -0
- vgi/py.typed +0 -0
- vgi/scalar_function.py +1211 -0
- vgi/schema_utils.py +234 -0
- vgi/secret_protocol.py +124 -0
- vgi/secret_service.py +238 -0
- vgi/serve.py +769 -0
- vgi/table_buffering_function.py +443 -0
- vgi/table_filter_pushdown.py +1528 -0
- vgi/table_function.py +1130 -0
- vgi/table_in_out_function.py +383 -0
- vgi/transactor/__init__.py +24 -0
- vgi/transactor/_duckdb_compat.py +27 -0
- vgi/transactor/client.py +137 -0
- vgi/transactor/protocol.py +149 -0
- vgi/transactor/server.py +740 -0
- vgi/worker.py +4761 -0
- vgi_python-0.8.0.dist-info/METADATA +735 -0
- vgi_python-0.8.0.dist-info/RECORD +124 -0
- vgi_python-0.8.0.dist-info/WHEEL +4 -0
- vgi_python-0.8.0.dist-info/entry_points.txt +5 -0
- vgi_python-0.8.0.dist-info/licenses/LICENSE +134 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
|
|
3
|
+
"""Transaction CLI commands for VGI.
|
|
4
|
+
|
|
5
|
+
This module provides CLI commands for transaction operations:
|
|
6
|
+
- begin: Begin a new transaction
|
|
7
|
+
- commit: Commit a transaction
|
|
8
|
+
- rollback: Rollback a transaction
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
|
|
16
|
+
from vgi.client.cli_utils import (
|
|
17
|
+
bytes_to_hex,
|
|
18
|
+
hex_to_attach_opaque_data,
|
|
19
|
+
hex_to_transaction_opaque_data,
|
|
20
|
+
output_json,
|
|
21
|
+
)
|
|
22
|
+
from vgi.client.client import Client
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.group()
|
|
26
|
+
def transaction() -> None:
|
|
27
|
+
"""Manage transactions in a catalog."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@transaction.command("begin")
|
|
31
|
+
@click.option("--attach-opaque-data", required=True, help="Hex-encoded attach ID")
|
|
32
|
+
@click.option("--worker", "-w", required=True, help="VGI worker command")
|
|
33
|
+
def transaction_begin(attach_opaque_data: str, worker: str) -> None:
|
|
34
|
+
"""Begin a new transaction.
|
|
35
|
+
|
|
36
|
+
Returns a transaction_opaque_data that can be used with other catalog operations.
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
client = Client(worker)
|
|
40
|
+
tx_id = client.catalog_transaction_begin(attach_opaque_data=hex_to_attach_opaque_data(attach_opaque_data))
|
|
41
|
+
if tx_id is None:
|
|
42
|
+
output_json({"error": "Catalog does not support transactions"})
|
|
43
|
+
return
|
|
44
|
+
output_json(
|
|
45
|
+
{
|
|
46
|
+
"transaction_opaque_data": bytes_to_hex(tx_id),
|
|
47
|
+
"attach_opaque_data": attach_opaque_data,
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@transaction.command("commit")
|
|
53
|
+
@click.argument("transaction_opaque_data")
|
|
54
|
+
@click.option("--attach-opaque-data", required=True, help="Hex-encoded attach ID")
|
|
55
|
+
@click.option("--worker", "-w", required=True, help="VGI worker command")
|
|
56
|
+
def transaction_commit(transaction_opaque_data: str, attach_opaque_data: str, worker: str) -> None:
|
|
57
|
+
"""Commit a transaction.
|
|
58
|
+
|
|
59
|
+
TRANSACTION_ID is the hex-encoded transaction ID from transaction begin.
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
client = Client(worker)
|
|
63
|
+
client.catalog_transaction_commit(
|
|
64
|
+
attach_opaque_data=hex_to_attach_opaque_data(attach_opaque_data),
|
|
65
|
+
transaction_opaque_data=hex_to_transaction_opaque_data(transaction_opaque_data),
|
|
66
|
+
)
|
|
67
|
+
output_json(
|
|
68
|
+
{
|
|
69
|
+
"status": "committed",
|
|
70
|
+
"transaction_opaque_data": transaction_opaque_data,
|
|
71
|
+
"attach_opaque_data": attach_opaque_data,
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@transaction.command("rollback")
|
|
77
|
+
@click.argument("transaction_opaque_data")
|
|
78
|
+
@click.option("--attach-opaque-data", required=True, help="Hex-encoded attach ID")
|
|
79
|
+
@click.option("--worker", "-w", required=True, help="VGI worker command")
|
|
80
|
+
def transaction_rollback(transaction_opaque_data: str, attach_opaque_data: str, worker: str) -> None:
|
|
81
|
+
"""Rollback a transaction.
|
|
82
|
+
|
|
83
|
+
TRANSACTION_ID is the hex-encoded transaction ID from transaction begin.
|
|
84
|
+
|
|
85
|
+
"""
|
|
86
|
+
client = Client(worker)
|
|
87
|
+
client.catalog_transaction_rollback(
|
|
88
|
+
attach_opaque_data=hex_to_attach_opaque_data(attach_opaque_data),
|
|
89
|
+
transaction_opaque_data=hex_to_transaction_opaque_data(transaction_opaque_data),
|
|
90
|
+
)
|
|
91
|
+
output_json(
|
|
92
|
+
{
|
|
93
|
+
"status": "rolled_back",
|
|
94
|
+
"transaction_opaque_data": transaction_opaque_data,
|
|
95
|
+
"attach_opaque_data": attach_opaque_data,
|
|
96
|
+
}
|
|
97
|
+
)
|
vgi/client/cli_utils.py
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
|
|
3
|
+
"""Shared utilities for VGI CLI commands.
|
|
4
|
+
|
|
5
|
+
This module provides common utilities used across CLI command groups:
|
|
6
|
+
- Hex string conversion for AttachOpaqueData and TransactionOpaqueData
|
|
7
|
+
- JSON to Arrow schema conversion for table columns
|
|
8
|
+
- Output formatting helpers
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
|
|
17
|
+
import click
|
|
18
|
+
import pyarrow as pa
|
|
19
|
+
|
|
20
|
+
from vgi.catalog import AttachOpaqueData, TransactionOpaqueData
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from vgi.catalog import CatalogAttachResult, FunctionInfo, SchemaInfo, TableInfo, ViewInfo
|
|
24
|
+
from vgi.catalog.catalog_interface import ScanFunctionResult
|
|
25
|
+
from vgi.client import Client
|
|
26
|
+
|
|
27
|
+
# Map of type names to PyArrow types for JSON schema definitions
|
|
28
|
+
ARROW_TYPE_MAP: dict[str, pa.DataType] = {
|
|
29
|
+
# Signed integers
|
|
30
|
+
"int8": pa.int8(),
|
|
31
|
+
"int16": pa.int16(),
|
|
32
|
+
"int32": pa.int32(),
|
|
33
|
+
"int64": pa.int64(),
|
|
34
|
+
# Unsigned integers
|
|
35
|
+
"uint8": pa.uint8(),
|
|
36
|
+
"uint16": pa.uint16(),
|
|
37
|
+
"uint32": pa.uint32(),
|
|
38
|
+
"uint64": pa.uint64(),
|
|
39
|
+
# Floating point
|
|
40
|
+
"float16": pa.float16(),
|
|
41
|
+
"float32": pa.float32(),
|
|
42
|
+
"float64": pa.float64(),
|
|
43
|
+
# Strings and binary
|
|
44
|
+
"string": pa.string(),
|
|
45
|
+
"utf8": pa.utf8(),
|
|
46
|
+
"large_string": pa.large_string(),
|
|
47
|
+
"binary": pa.binary(),
|
|
48
|
+
"large_binary": pa.large_binary(),
|
|
49
|
+
# Boolean
|
|
50
|
+
"bool": pa.bool_(),
|
|
51
|
+
"boolean": pa.bool_(),
|
|
52
|
+
# Date types
|
|
53
|
+
"date32": pa.date32(),
|
|
54
|
+
"date64": pa.date64(),
|
|
55
|
+
# Timestamp types (microsecond precision by default)
|
|
56
|
+
"timestamp": pa.timestamp("us"),
|
|
57
|
+
"timestamp_s": pa.timestamp("s"),
|
|
58
|
+
"timestamp_ms": pa.timestamp("ms"),
|
|
59
|
+
"timestamp_us": pa.timestamp("us"),
|
|
60
|
+
"timestamp_ns": pa.timestamp("ns"),
|
|
61
|
+
# Duration types
|
|
62
|
+
"duration": pa.duration("us"),
|
|
63
|
+
"duration_s": pa.duration("s"),
|
|
64
|
+
"duration_ms": pa.duration("ms"),
|
|
65
|
+
"duration_us": pa.duration("us"),
|
|
66
|
+
"duration_ns": pa.duration("ns"),
|
|
67
|
+
# Time types
|
|
68
|
+
"time32": pa.time32("ms"),
|
|
69
|
+
"time64": pa.time64("us"),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def hex_to_bytes(hex_string: str) -> bytes:
|
|
74
|
+
"""Convert a hex string to bytes.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
hex_string: Hexadecimal string (e.g., "deadbeef")
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Bytes representation
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
click.ClickException: If hex string is invalid
|
|
84
|
+
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
return bytes.fromhex(hex_string)
|
|
88
|
+
except ValueError as e:
|
|
89
|
+
raise click.ClickException(f"Invalid hex string '{hex_string}': {e}") from e
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def hex_to_attach_opaque_data(hex_string: str) -> AttachOpaqueData:
|
|
93
|
+
"""Convert a hex string to AttachOpaqueData.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
hex_string: Hexadecimal string (e.g., "deadbeef")
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
AttachOpaqueData
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
click.ClickException: If hex string is invalid
|
|
103
|
+
|
|
104
|
+
"""
|
|
105
|
+
return AttachOpaqueData(hex_to_bytes(hex_string))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def hex_to_transaction_opaque_data(hex_string: str) -> TransactionOpaqueData:
|
|
109
|
+
"""Convert a hex string to TransactionOpaqueData.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
hex_string: Hexadecimal string (e.g., "deadbeef")
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
TransactionOpaqueData
|
|
116
|
+
|
|
117
|
+
Raises:
|
|
118
|
+
click.ClickException: If hex string is invalid
|
|
119
|
+
|
|
120
|
+
"""
|
|
121
|
+
return TransactionOpaqueData(hex_to_bytes(hex_string))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def bytes_to_hex(data: bytes) -> str:
|
|
125
|
+
"""Convert bytes to a hex string.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
data: Bytes to convert
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Hexadecimal string representation
|
|
132
|
+
|
|
133
|
+
"""
|
|
134
|
+
return data.hex()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def json_to_arrow_schema(columns: list[dict[str, str]]) -> pa.Schema:
|
|
138
|
+
"""Convert JSON column definitions to PyArrow schema.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
columns: List of dicts with 'name' and 'type' keys.
|
|
142
|
+
Example: [{"name": "id", "type": "int64"}]
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
PyArrow Schema
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
click.ClickException: If type is unknown or column definition is invalid.
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
fields = []
|
|
152
|
+
for i, col in enumerate(columns):
|
|
153
|
+
if "name" not in col:
|
|
154
|
+
raise click.ClickException(f"Column {i} missing 'name' field: {json.dumps(col)}")
|
|
155
|
+
if "type" not in col:
|
|
156
|
+
raise click.ClickException(f"Column {i} missing 'type' field: {json.dumps(col)}")
|
|
157
|
+
|
|
158
|
+
type_name = col["type"]
|
|
159
|
+
if type_name not in ARROW_TYPE_MAP:
|
|
160
|
+
valid_types = ", ".join(sorted(ARROW_TYPE_MAP.keys()))
|
|
161
|
+
raise click.ClickException(
|
|
162
|
+
f"Unknown type '{type_name}' for column '{col['name']}'. Valid types: {valid_types}"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
fields.append(pa.field(col["name"], ARROW_TYPE_MAP[type_name]))
|
|
166
|
+
|
|
167
|
+
return pa.schema(fields)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def arrow_schema_to_json(serialized: bytes) -> list[dict[str, str | bool]]:
|
|
171
|
+
"""Convert serialized Arrow schema to JSON for display.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
serialized: Serialized Arrow schema bytes
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
List of column definitions with name, type, and optional flags (varargs, const)
|
|
178
|
+
|
|
179
|
+
"""
|
|
180
|
+
reader = pa.BufferReader(serialized)
|
|
181
|
+
schema = pa.ipc.read_schema(reader) # type: ignore[arg-type]
|
|
182
|
+
result: list[dict[str, str | bool]] = []
|
|
183
|
+
for f in schema:
|
|
184
|
+
type_str = str(f.type)
|
|
185
|
+
is_varargs = False
|
|
186
|
+
is_const = False
|
|
187
|
+
if f.metadata:
|
|
188
|
+
# Check for vgi:any metadata (output schema)
|
|
189
|
+
if f.metadata.get(b"vgi:any") == b"true":
|
|
190
|
+
type_str = "any"
|
|
191
|
+
# Check for vgi_type metadata (argument schema)
|
|
192
|
+
elif f.metadata.get(b"vgi_type") == b"table":
|
|
193
|
+
type_str = "table"
|
|
194
|
+
elif f.metadata.get(b"vgi_type") == b"any":
|
|
195
|
+
type_str = "any"
|
|
196
|
+
# Check for varargs metadata
|
|
197
|
+
if f.metadata.get(b"vgi_varargs") == b"true":
|
|
198
|
+
is_varargs = True
|
|
199
|
+
# Check for const metadata (ConstParam)
|
|
200
|
+
if f.metadata.get(b"vgi_const") == b"true":
|
|
201
|
+
is_const = True
|
|
202
|
+
|
|
203
|
+
entry: dict[str, str | bool] = {"name": f.name, "type": type_str}
|
|
204
|
+
if is_varargs:
|
|
205
|
+
entry["varargs"] = True
|
|
206
|
+
if is_const:
|
|
207
|
+
entry["const"] = True
|
|
208
|
+
result.append(entry)
|
|
209
|
+
return result
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def output_json(data: Any) -> None:
|
|
213
|
+
"""Output data as JSON to stdout.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
data: Data to serialize as JSON
|
|
217
|
+
|
|
218
|
+
"""
|
|
219
|
+
click.echo(json.dumps(data))
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def parse_json_option(value: str, option_name: str) -> Any:
|
|
223
|
+
"""Parse a JSON string from a CLI option.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
value: JSON string to parse
|
|
227
|
+
option_name: Name of the option (for error messages)
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Parsed JSON value
|
|
231
|
+
|
|
232
|
+
Raises:
|
|
233
|
+
click.ClickException: If JSON is invalid
|
|
234
|
+
|
|
235
|
+
"""
|
|
236
|
+
try:
|
|
237
|
+
return json.loads(value)
|
|
238
|
+
except json.JSONDecodeError as e:
|
|
239
|
+
raise click.ClickException(f"Invalid JSON for {option_name}: {e}") from e
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def schema_info_to_dict(schema_info: SchemaInfo) -> dict[str, Any]:
|
|
243
|
+
"""Convert SchemaInfo to a dictionary for JSON output.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
schema_info: SchemaInfo object from catalog
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Dictionary representation
|
|
250
|
+
|
|
251
|
+
"""
|
|
252
|
+
return {
|
|
253
|
+
"name": schema_info.name,
|
|
254
|
+
"comment": schema_info.comment,
|
|
255
|
+
"tags": dict(schema_info.tags),
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def table_info_to_dict(table_info: TableInfo) -> dict[str, Any]:
|
|
260
|
+
"""Convert TableInfo to a dictionary for JSON output.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
table_info: TableInfo object from catalog
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Dictionary representation
|
|
267
|
+
|
|
268
|
+
"""
|
|
269
|
+
return {
|
|
270
|
+
"name": table_info.name,
|
|
271
|
+
"schema_name": table_info.schema_name,
|
|
272
|
+
"columns": arrow_schema_to_json(table_info.columns),
|
|
273
|
+
"not_null_constraints": table_info.not_null_constraints,
|
|
274
|
+
"unique_constraints": table_info.unique_constraints,
|
|
275
|
+
"check_constraints": table_info.check_constraints,
|
|
276
|
+
"comment": table_info.comment,
|
|
277
|
+
"tags": dict(table_info.tags),
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def view_info_to_dict(view_info: ViewInfo) -> dict[str, Any]:
|
|
282
|
+
"""Convert ViewInfo to a dictionary for JSON output.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
view_info: ViewInfo object from catalog
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Dictionary representation
|
|
289
|
+
|
|
290
|
+
"""
|
|
291
|
+
return {
|
|
292
|
+
"name": view_info.name,
|
|
293
|
+
"schema_name": view_info.schema_name,
|
|
294
|
+
"definition": view_info.definition,
|
|
295
|
+
"comment": view_info.comment,
|
|
296
|
+
"tags": dict(view_info.tags),
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def function_info_to_dict(function_info: FunctionInfo) -> dict[str, Any]:
|
|
301
|
+
"""Convert FunctionInfo to a dictionary for JSON output.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
function_info: FunctionInfo object from catalog
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
Dictionary representation
|
|
308
|
+
|
|
309
|
+
"""
|
|
310
|
+
result: dict[str, Any] = {
|
|
311
|
+
"name": function_info.name,
|
|
312
|
+
"schema_name": function_info.schema_name,
|
|
313
|
+
"function_type": function_info.function_type.value,
|
|
314
|
+
"arguments": arrow_schema_to_json(function_info.arguments),
|
|
315
|
+
"description": function_info.description,
|
|
316
|
+
"tags": dict(function_info.tags),
|
|
317
|
+
# Scalar function behavior fields (None for non-scalar)
|
|
318
|
+
"stability": (function_info.stability.name if function_info.stability else None),
|
|
319
|
+
"null_handling": (function_info.null_handling.name if function_info.null_handling else None),
|
|
320
|
+
# Documentation fields (convert CatalogExample to dict for JSON)
|
|
321
|
+
"examples": [
|
|
322
|
+
{"sql": ex.sql, "description": ex.description} if hasattr(ex, "sql") else ex
|
|
323
|
+
for ex in function_info.examples
|
|
324
|
+
],
|
|
325
|
+
"categories": function_info.categories,
|
|
326
|
+
# Table function capabilities (None for scalar)
|
|
327
|
+
"projection_pushdown": function_info.projection_pushdown,
|
|
328
|
+
"filter_pushdown": function_info.filter_pushdown,
|
|
329
|
+
"order_preservation": (function_info.order_preservation.name if function_info.order_preservation else None),
|
|
330
|
+
"max_workers": function_info.max_workers,
|
|
331
|
+
# Aggregate function fields
|
|
332
|
+
"order_dependent": function_info.order_dependent.name,
|
|
333
|
+
"distinct_dependent": function_info.distinct_dependent.name,
|
|
334
|
+
# Settings
|
|
335
|
+
"required_settings": function_info.required_settings,
|
|
336
|
+
}
|
|
337
|
+
# Only include output_schema for scalar functions
|
|
338
|
+
if function_info.function_type.value == "scalar":
|
|
339
|
+
result["output_schema"] = arrow_schema_to_json(function_info.output_schema)
|
|
340
|
+
return result
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def catalog_attach_result_to_dict(result: CatalogAttachResult) -> dict[str, Any]:
|
|
344
|
+
"""Convert CatalogAttachResult to a dictionary for JSON output.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
result: CatalogAttachResult object
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
Dictionary representation with attach_opaque_data as hex
|
|
351
|
+
|
|
352
|
+
"""
|
|
353
|
+
return {
|
|
354
|
+
"attach_opaque_data": bytes_to_hex(result.attach_opaque_data),
|
|
355
|
+
"supports_transactions": result.supports_transactions,
|
|
356
|
+
"supports_time_travel": result.supports_time_travel,
|
|
357
|
+
"catalog_version_frozen": result.catalog_version_frozen,
|
|
358
|
+
"catalog_version": result.catalog_version,
|
|
359
|
+
"attach_opaque_data_required": result.attach_opaque_data_required,
|
|
360
|
+
"default_schema": result.default_schema,
|
|
361
|
+
"settings": [bytes_to_hex(s) for s in result.settings],
|
|
362
|
+
"resolved_data_version": result.resolved_data_version,
|
|
363
|
+
"resolved_implementation_version": result.resolved_implementation_version,
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def scan_function_result_to_dict(result: ScanFunctionResult) -> dict[str, Any]:
|
|
368
|
+
"""Convert ScanFunctionResult to a dictionary for JSON output.
|
|
369
|
+
|
|
370
|
+
ScanFunctionResult allows the VGI DuckDB extension to call any DuckDB
|
|
371
|
+
function with specified positional and named arguments, and load any
|
|
372
|
+
required extensions.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
result: ScanFunctionResult object
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
Dictionary representation with function_name, positional_arguments,
|
|
379
|
+
named_arguments, and required_extensions.
|
|
380
|
+
|
|
381
|
+
"""
|
|
382
|
+
return {
|
|
383
|
+
"function_name": result.function_name,
|
|
384
|
+
"positional_arguments": [arg.as_py() for arg in result.positional_arguments],
|
|
385
|
+
"named_arguments": {name: arg.as_py() for name, arg in result.named_arguments.items()},
|
|
386
|
+
"required_extensions": result.required_extensions,
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def get_attach_opaque_data_from_options(
|
|
391
|
+
client: Client,
|
|
392
|
+
attach_opaque_data: str | None,
|
|
393
|
+
catalog: str | None,
|
|
394
|
+
attach_options: dict[str, Any] | None,
|
|
395
|
+
) -> tuple[AttachOpaqueData, bool]:
|
|
396
|
+
"""Get attach_opaque_data from either explicit --attach-opaque-data or auto-attach via --catalog.
|
|
397
|
+
|
|
398
|
+
This helper supports two workflows:
|
|
399
|
+
1. Explicit attach_opaque_data: Use a pre-obtained attach_opaque_data (for stateful catalogs)
|
|
400
|
+
2. Auto-attach: Attach to catalog on-the-fly (for stateless catalogs)
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
client: VGI Client instance
|
|
404
|
+
attach_opaque_data: Hex-encoded attach ID (from --attach-opaque-data option)
|
|
405
|
+
catalog: Catalog name (from --catalog option)
|
|
406
|
+
attach_options: Options for catalog attach (from --attach-options option)
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
Tuple of (attach_opaque_data, is_stateful) where is_stateful indicates if
|
|
410
|
+
a warning should be shown for stateful catalogs using auto-attach.
|
|
411
|
+
|
|
412
|
+
Raises:
|
|
413
|
+
click.ClickException: If neither attach_opaque_data nor catalog is provided,
|
|
414
|
+
or if both are provided.
|
|
415
|
+
|
|
416
|
+
"""
|
|
417
|
+
if attach_opaque_data and catalog:
|
|
418
|
+
raise click.ClickException(
|
|
419
|
+
"Cannot specify both --attach-opaque-data and --catalog. "
|
|
420
|
+
"Use --attach-opaque-data for stateful catalogs or --catalog for auto-attach."
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
if not attach_opaque_data and not catalog:
|
|
424
|
+
raise click.ClickException(
|
|
425
|
+
"Must specify either --attach-opaque-data or --catalog. "
|
|
426
|
+
"Use --attach-opaque-data with a previously attached catalog, "
|
|
427
|
+
"or --catalog to auto-attach."
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
if attach_opaque_data:
|
|
431
|
+
return hex_to_attach_opaque_data(attach_opaque_data), False
|
|
432
|
+
|
|
433
|
+
# Auto-attach via --catalog
|
|
434
|
+
assert catalog is not None
|
|
435
|
+
options = attach_options or {}
|
|
436
|
+
result = client.catalog_attach(name=catalog, options=options, data_version_spec=None, implementation_version=None)
|
|
437
|
+
|
|
438
|
+
# Return the attach_opaque_data and whether this is a stateful catalog
|
|
439
|
+
# (is_stateful=True means caller should warn about using --catalog
|
|
440
|
+
# with a stateful catalog)
|
|
441
|
+
return result.attach_opaque_data, result.attach_opaque_data_required
|