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,813 @@
|
|
|
1
|
+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
|
|
3
|
+
"""In-memory catalog implementation for testing and examples.
|
|
4
|
+
|
|
5
|
+
This module provides an in-memory implementation of CatalogInterface that can
|
|
6
|
+
be used for testing and as a reference implementation.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import uuid
|
|
12
|
+
from collections.abc import Sequence
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import TYPE_CHECKING, Any, Literal, overload
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
import pyarrow as pa
|
|
18
|
+
from vgi_rpc.rpc import CallContext
|
|
19
|
+
|
|
20
|
+
from vgi.catalog import (
|
|
21
|
+
AttachOpaqueData,
|
|
22
|
+
CatalogAttachResult,
|
|
23
|
+
CatalogInfo,
|
|
24
|
+
CatalogInterface,
|
|
25
|
+
FunctionInfo,
|
|
26
|
+
IndexInfo,
|
|
27
|
+
MacroInfo,
|
|
28
|
+
MacroType,
|
|
29
|
+
OnConflict,
|
|
30
|
+
SchemaInfo,
|
|
31
|
+
SchemaObjectType,
|
|
32
|
+
SerializedSchema,
|
|
33
|
+
TableInfo,
|
|
34
|
+
TransactionOpaqueData,
|
|
35
|
+
ViewInfo,
|
|
36
|
+
)
|
|
37
|
+
from vgi.worker import Worker
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class TableData:
|
|
42
|
+
"""In-memory storage for table metadata."""
|
|
43
|
+
|
|
44
|
+
info: TableInfo
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class ViewData:
|
|
49
|
+
"""In-memory storage for view metadata."""
|
|
50
|
+
|
|
51
|
+
info: ViewInfo
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class MacroData:
|
|
56
|
+
"""In-memory storage for macro metadata."""
|
|
57
|
+
|
|
58
|
+
info: MacroInfo
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class SchemaData:
|
|
63
|
+
"""In-memory storage for schema metadata."""
|
|
64
|
+
|
|
65
|
+
info: SchemaInfo
|
|
66
|
+
tables: dict[str, TableData] = field(default_factory=dict)
|
|
67
|
+
views: dict[str, ViewData] = field(default_factory=dict)
|
|
68
|
+
macros: dict[str, MacroData] = field(default_factory=dict)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class CatalogData:
|
|
73
|
+
"""In-memory storage for catalog metadata."""
|
|
74
|
+
|
|
75
|
+
name: str
|
|
76
|
+
schemas: dict[str, SchemaData] = field(default_factory=dict)
|
|
77
|
+
version: int = 1
|
|
78
|
+
comment: str | None = None
|
|
79
|
+
tags: dict[str, str] = field(default_factory=dict)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class InMemoryCatalog(CatalogInterface):
|
|
83
|
+
"""In-memory catalog implementation for testing.
|
|
84
|
+
|
|
85
|
+
This implementation stores all catalog, schema, table, and view data
|
|
86
|
+
in memory using Python dictionaries. It supports basic DDL operations
|
|
87
|
+
but does not support transactions.
|
|
88
|
+
|
|
89
|
+
Attach IDs are generated as random UUIDs.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(self) -> None:
|
|
93
|
+
"""Initialize the in-memory catalog."""
|
|
94
|
+
# Maps catalog name -> CatalogData
|
|
95
|
+
self._catalogs: dict[str, CatalogData] = {}
|
|
96
|
+
# Maps attach_opaque_data -> catalog_name
|
|
97
|
+
self._attachments: dict[AttachOpaqueData, str] = {}
|
|
98
|
+
# Create default "memory" catalog with "main" schema
|
|
99
|
+
self._create_default_catalog()
|
|
100
|
+
|
|
101
|
+
def _create_default_catalog(self) -> None:
|
|
102
|
+
"""Create the default memory catalog with main schema."""
|
|
103
|
+
catalog = CatalogData(name="memory")
|
|
104
|
+
# Create a placeholder attach_opaque_data for internal use
|
|
105
|
+
placeholder_attach_opaque_data = AttachOpaqueData(b"\x00" * 16)
|
|
106
|
+
catalog.schemas["main"] = SchemaData(
|
|
107
|
+
info=SchemaInfo(
|
|
108
|
+
attach_opaque_data=placeholder_attach_opaque_data,
|
|
109
|
+
name="main",
|
|
110
|
+
comment=None,
|
|
111
|
+
tags={},
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
self._catalogs["memory"] = catalog
|
|
115
|
+
|
|
116
|
+
def _get_catalog(self, attach_opaque_data: AttachOpaqueData) -> CatalogData:
|
|
117
|
+
"""Get the catalog for the given attach_opaque_data."""
|
|
118
|
+
catalog_name = self._attachments.get(attach_opaque_data)
|
|
119
|
+
if catalog_name is None:
|
|
120
|
+
msg = f"No catalog attached with id {attach_opaque_data!r}"
|
|
121
|
+
raise ValueError(msg)
|
|
122
|
+
catalog = self._catalogs.get(catalog_name)
|
|
123
|
+
if catalog is None:
|
|
124
|
+
msg = f"Catalog {catalog_name!r} not found"
|
|
125
|
+
raise ValueError(msg)
|
|
126
|
+
return catalog
|
|
127
|
+
|
|
128
|
+
def _get_schema(self, attach_opaque_data: AttachOpaqueData, schema_name: str) -> SchemaData:
|
|
129
|
+
"""Get the schema for the given attach_opaque_data and schema name."""
|
|
130
|
+
catalog = self._get_catalog(attach_opaque_data)
|
|
131
|
+
schema = catalog.schemas.get(schema_name)
|
|
132
|
+
if schema is None:
|
|
133
|
+
msg = f"Schema {schema_name!r} not found in catalog"
|
|
134
|
+
raise ValueError(msg)
|
|
135
|
+
return schema
|
|
136
|
+
|
|
137
|
+
def _increment_version(self, attach_opaque_data: AttachOpaqueData) -> None:
|
|
138
|
+
"""Increment the catalog version after a modification."""
|
|
139
|
+
catalog = self._get_catalog(attach_opaque_data)
|
|
140
|
+
catalog.version += 1
|
|
141
|
+
|
|
142
|
+
# Required abstract methods
|
|
143
|
+
|
|
144
|
+
def catalogs(self) -> list[CatalogInfo]:
|
|
145
|
+
"""Get a list of catalog discovery records."""
|
|
146
|
+
return [CatalogInfo(name=name, implementation_version=None, data_version_spec=None) for name in self._catalogs]
|
|
147
|
+
|
|
148
|
+
def catalog_attach(
|
|
149
|
+
self,
|
|
150
|
+
*,
|
|
151
|
+
name: str,
|
|
152
|
+
options: dict[str, Any],
|
|
153
|
+
data_version_spec: str | None,
|
|
154
|
+
implementation_version: str | None,
|
|
155
|
+
ctx: CallContext | None = None,
|
|
156
|
+
) -> CatalogAttachResult:
|
|
157
|
+
"""Attach to a catalog with the given name.
|
|
158
|
+
|
|
159
|
+
This example has no version opinion: requested versions are ignored and
|
|
160
|
+
resolved_* fields echo back empty strings.
|
|
161
|
+
"""
|
|
162
|
+
del data_version_spec, implementation_version, ctx
|
|
163
|
+
if name not in self._catalogs:
|
|
164
|
+
msg = f"Catalog {name!r} not found"
|
|
165
|
+
raise ValueError(msg)
|
|
166
|
+
|
|
167
|
+
# Generate a unique attach_opaque_data
|
|
168
|
+
attach_opaque_data = AttachOpaqueData(uuid.uuid4().bytes)
|
|
169
|
+
self._attachments[attach_opaque_data] = name
|
|
170
|
+
|
|
171
|
+
catalog = self._catalogs[name]
|
|
172
|
+
return CatalogAttachResult(
|
|
173
|
+
attach_opaque_data=attach_opaque_data,
|
|
174
|
+
supports_transactions=False,
|
|
175
|
+
supports_time_travel=False,
|
|
176
|
+
catalog_version_frozen=False,
|
|
177
|
+
catalog_version=catalog.version,
|
|
178
|
+
comment=catalog.comment,
|
|
179
|
+
tags=dict(catalog.tags),
|
|
180
|
+
resolved_data_version=None,
|
|
181
|
+
resolved_implementation_version=None,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def catalog_detach(self, *, attach_opaque_data: AttachOpaqueData) -> None:
|
|
185
|
+
"""Detach from the catalog."""
|
|
186
|
+
self._attachments.pop(attach_opaque_data, None)
|
|
187
|
+
|
|
188
|
+
def schema_get(
|
|
189
|
+
self,
|
|
190
|
+
*,
|
|
191
|
+
attach_opaque_data: AttachOpaqueData,
|
|
192
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
193
|
+
name: str,
|
|
194
|
+
) -> SchemaInfo | None:
|
|
195
|
+
"""Get information about a schema."""
|
|
196
|
+
catalog = self._get_catalog(attach_opaque_data)
|
|
197
|
+
schema_data = catalog.schemas.get(name)
|
|
198
|
+
if schema_data is None:
|
|
199
|
+
return None
|
|
200
|
+
# Update the attach_opaque_data in the returned info
|
|
201
|
+
return SchemaInfo(
|
|
202
|
+
attach_opaque_data=attach_opaque_data,
|
|
203
|
+
name=schema_data.info.name,
|
|
204
|
+
comment=schema_data.info.comment,
|
|
205
|
+
tags=schema_data.info.tags,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
def table_get(
|
|
209
|
+
self,
|
|
210
|
+
*,
|
|
211
|
+
attach_opaque_data: AttachOpaqueData,
|
|
212
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
213
|
+
schema_name: str,
|
|
214
|
+
name: str,
|
|
215
|
+
at_unit: str | None = None,
|
|
216
|
+
at_value: str | None = None,
|
|
217
|
+
) -> TableInfo | None:
|
|
218
|
+
"""Get information about a table."""
|
|
219
|
+
catalog = self._get_catalog(attach_opaque_data)
|
|
220
|
+
schema_data = catalog.schemas.get(schema_name)
|
|
221
|
+
if schema_data is None:
|
|
222
|
+
return None
|
|
223
|
+
table_data = schema_data.tables.get(name)
|
|
224
|
+
if table_data is None:
|
|
225
|
+
return None
|
|
226
|
+
return table_data.info
|
|
227
|
+
|
|
228
|
+
def view_get(
|
|
229
|
+
self,
|
|
230
|
+
*,
|
|
231
|
+
attach_opaque_data: AttachOpaqueData,
|
|
232
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
233
|
+
schema_name: str,
|
|
234
|
+
name: str,
|
|
235
|
+
) -> ViewInfo | None:
|
|
236
|
+
"""Get information about a view."""
|
|
237
|
+
catalog = self._get_catalog(attach_opaque_data)
|
|
238
|
+
schema_data = catalog.schemas.get(schema_name)
|
|
239
|
+
if schema_data is None:
|
|
240
|
+
return None
|
|
241
|
+
view_data = schema_data.views.get(name)
|
|
242
|
+
if view_data is None:
|
|
243
|
+
return None
|
|
244
|
+
return view_data.info
|
|
245
|
+
|
|
246
|
+
def macro_get(
|
|
247
|
+
self,
|
|
248
|
+
*,
|
|
249
|
+
attach_opaque_data: AttachOpaqueData,
|
|
250
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
251
|
+
schema_name: str,
|
|
252
|
+
name: str,
|
|
253
|
+
) -> MacroInfo | None:
|
|
254
|
+
"""Get information about a macro."""
|
|
255
|
+
catalog = self._get_catalog(attach_opaque_data)
|
|
256
|
+
schema_data = catalog.schemas.get(schema_name)
|
|
257
|
+
if schema_data is None:
|
|
258
|
+
return None
|
|
259
|
+
macro_data = schema_data.macros.get(name)
|
|
260
|
+
if macro_data is None:
|
|
261
|
+
return None
|
|
262
|
+
return macro_data.info
|
|
263
|
+
|
|
264
|
+
# Optional methods with implementations
|
|
265
|
+
|
|
266
|
+
def catalog_version(
|
|
267
|
+
self,
|
|
268
|
+
*,
|
|
269
|
+
attach_opaque_data: AttachOpaqueData,
|
|
270
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
271
|
+
ctx: Any = None,
|
|
272
|
+
) -> int:
|
|
273
|
+
"""Get the current catalog version."""
|
|
274
|
+
del ctx
|
|
275
|
+
catalog = self._get_catalog(attach_opaque_data)
|
|
276
|
+
return catalog.version
|
|
277
|
+
|
|
278
|
+
def catalog_create(self, *, name: str, on_conflict: OnConflict, options: dict[str, Any]) -> None:
|
|
279
|
+
"""Create a new catalog."""
|
|
280
|
+
if name in self._catalogs:
|
|
281
|
+
if on_conflict == OnConflict.ERROR:
|
|
282
|
+
msg = f"Catalog {name!r} already exists"
|
|
283
|
+
raise ValueError(msg)
|
|
284
|
+
if on_conflict == OnConflict.IGNORE:
|
|
285
|
+
return
|
|
286
|
+
# REPLACE: fall through to create
|
|
287
|
+
|
|
288
|
+
catalog = CatalogData(name=name)
|
|
289
|
+
# Create a placeholder attach_opaque_data for internal use
|
|
290
|
+
placeholder_attach_opaque_data = AttachOpaqueData(b"\x00" * 16)
|
|
291
|
+
catalog.schemas["main"] = SchemaData(
|
|
292
|
+
info=SchemaInfo(
|
|
293
|
+
attach_opaque_data=placeholder_attach_opaque_data,
|
|
294
|
+
name="main",
|
|
295
|
+
comment=None,
|
|
296
|
+
tags={},
|
|
297
|
+
)
|
|
298
|
+
)
|
|
299
|
+
self._catalogs[name] = catalog
|
|
300
|
+
|
|
301
|
+
def catalog_drop(self, *, name: str) -> None:
|
|
302
|
+
"""Drop a catalog."""
|
|
303
|
+
if name not in self._catalogs:
|
|
304
|
+
msg = f"Catalog {name!r} not found"
|
|
305
|
+
raise ValueError(msg)
|
|
306
|
+
# Remove any attachments to this catalog
|
|
307
|
+
to_remove = [aid for aid, cname in self._attachments.items() if cname == name]
|
|
308
|
+
for aid in to_remove:
|
|
309
|
+
del self._attachments[aid]
|
|
310
|
+
del self._catalogs[name]
|
|
311
|
+
|
|
312
|
+
def schemas(
|
|
313
|
+
self, *, attach_opaque_data: AttachOpaqueData, transaction_opaque_data: TransactionOpaqueData | None
|
|
314
|
+
) -> list[SchemaInfo]:
|
|
315
|
+
"""Get a list of schemas in the catalog."""
|
|
316
|
+
catalog = self._get_catalog(attach_opaque_data)
|
|
317
|
+
result = []
|
|
318
|
+
for schema_data in catalog.schemas.values():
|
|
319
|
+
# Populate estimated_object_count from the in-memory population so
|
|
320
|
+
# the C++ side's eager-load gate has a real signal to act on. The
|
|
321
|
+
# InMemoryCatalog only models tables/views/macros — for kinds it
|
|
322
|
+
# cannot model at all (functions, indexes), emit explicit ``0``
|
|
323
|
+
# so the client triggers the zero-count RPC bypass instead of
|
|
324
|
+
# paying empty round-trips for kinds that genuinely cannot exist
|
|
325
|
+
# here. This is load-bearing for the bypass integration test.
|
|
326
|
+
estimated = {
|
|
327
|
+
"table": len(schema_data.tables),
|
|
328
|
+
"view": len(schema_data.views),
|
|
329
|
+
"macro": len(schema_data.macros),
|
|
330
|
+
"index": 0,
|
|
331
|
+
"scalar_function": 0,
|
|
332
|
+
"aggregate_function": 0,
|
|
333
|
+
"table_function": 0,
|
|
334
|
+
}
|
|
335
|
+
result.append(
|
|
336
|
+
SchemaInfo(
|
|
337
|
+
attach_opaque_data=attach_opaque_data,
|
|
338
|
+
name=schema_data.info.name,
|
|
339
|
+
comment=schema_data.info.comment,
|
|
340
|
+
tags=schema_data.info.tags,
|
|
341
|
+
estimated_object_count=estimated,
|
|
342
|
+
)
|
|
343
|
+
)
|
|
344
|
+
return result
|
|
345
|
+
|
|
346
|
+
def schema_create(
|
|
347
|
+
self,
|
|
348
|
+
*,
|
|
349
|
+
attach_opaque_data: AttachOpaqueData,
|
|
350
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
351
|
+
name: str,
|
|
352
|
+
on_conflict: OnConflict = OnConflict.ERROR,
|
|
353
|
+
comment: str | None,
|
|
354
|
+
tags: dict[str, str],
|
|
355
|
+
) -> None:
|
|
356
|
+
"""Create a new schema."""
|
|
357
|
+
catalog = self._get_catalog(attach_opaque_data)
|
|
358
|
+
if name in catalog.schemas:
|
|
359
|
+
msg = f"Schema {name!r} already exists"
|
|
360
|
+
raise ValueError(msg)
|
|
361
|
+
catalog.schemas[name] = SchemaData(
|
|
362
|
+
info=SchemaInfo(
|
|
363
|
+
attach_opaque_data=attach_opaque_data,
|
|
364
|
+
name=name,
|
|
365
|
+
comment=comment,
|
|
366
|
+
tags=tags,
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
self._increment_version(attach_opaque_data)
|
|
370
|
+
|
|
371
|
+
def schema_drop(
|
|
372
|
+
self,
|
|
373
|
+
*,
|
|
374
|
+
attach_opaque_data: AttachOpaqueData,
|
|
375
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
376
|
+
name: str,
|
|
377
|
+
ignore_not_found: bool,
|
|
378
|
+
cascade: bool,
|
|
379
|
+
) -> None:
|
|
380
|
+
"""Drop a schema."""
|
|
381
|
+
catalog = self._get_catalog(attach_opaque_data)
|
|
382
|
+
if name not in catalog.schemas:
|
|
383
|
+
if ignore_not_found:
|
|
384
|
+
return
|
|
385
|
+
msg = f"Schema {name!r} not found"
|
|
386
|
+
raise ValueError(msg)
|
|
387
|
+
schema_data = catalog.schemas[name]
|
|
388
|
+
if not cascade and (schema_data.tables or schema_data.views or schema_data.macros):
|
|
389
|
+
msg = f"Schema {name!r} is not empty, use CASCADE to drop"
|
|
390
|
+
raise ValueError(msg)
|
|
391
|
+
del catalog.schemas[name]
|
|
392
|
+
self._increment_version(attach_opaque_data)
|
|
393
|
+
|
|
394
|
+
@overload
|
|
395
|
+
def schema_contents(
|
|
396
|
+
self,
|
|
397
|
+
*,
|
|
398
|
+
attach_opaque_data: AttachOpaqueData,
|
|
399
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
400
|
+
name: str,
|
|
401
|
+
type: Literal[SchemaObjectType.TABLE],
|
|
402
|
+
) -> Sequence[TableInfo]: ...
|
|
403
|
+
|
|
404
|
+
@overload
|
|
405
|
+
def schema_contents(
|
|
406
|
+
self,
|
|
407
|
+
*,
|
|
408
|
+
attach_opaque_data: AttachOpaqueData,
|
|
409
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
410
|
+
name: str,
|
|
411
|
+
type: Literal[SchemaObjectType.VIEW],
|
|
412
|
+
) -> Sequence[ViewInfo]: ...
|
|
413
|
+
|
|
414
|
+
@overload
|
|
415
|
+
def schema_contents(
|
|
416
|
+
self,
|
|
417
|
+
*,
|
|
418
|
+
attach_opaque_data: AttachOpaqueData,
|
|
419
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
420
|
+
name: str,
|
|
421
|
+
type: Literal[
|
|
422
|
+
SchemaObjectType.SCALAR_FUNCTION,
|
|
423
|
+
SchemaObjectType.TABLE_FUNCTION,
|
|
424
|
+
SchemaObjectType.AGGREGATE_FUNCTION,
|
|
425
|
+
],
|
|
426
|
+
) -> Sequence[FunctionInfo]: ...
|
|
427
|
+
|
|
428
|
+
@overload
|
|
429
|
+
def schema_contents(
|
|
430
|
+
self,
|
|
431
|
+
*,
|
|
432
|
+
attach_opaque_data: AttachOpaqueData,
|
|
433
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
434
|
+
name: str,
|
|
435
|
+
type: Literal[SchemaObjectType.SCALAR_MACRO, SchemaObjectType.TABLE_MACRO],
|
|
436
|
+
) -> Sequence[MacroInfo]: ...
|
|
437
|
+
|
|
438
|
+
@overload
|
|
439
|
+
def schema_contents(
|
|
440
|
+
self,
|
|
441
|
+
*,
|
|
442
|
+
attach_opaque_data: AttachOpaqueData,
|
|
443
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
444
|
+
name: str,
|
|
445
|
+
type: Literal[SchemaObjectType.INDEX],
|
|
446
|
+
) -> Sequence[IndexInfo]: ...
|
|
447
|
+
|
|
448
|
+
def schema_contents(
|
|
449
|
+
self,
|
|
450
|
+
*,
|
|
451
|
+
attach_opaque_data: AttachOpaqueData,
|
|
452
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
453
|
+
name: str,
|
|
454
|
+
type: SchemaObjectType,
|
|
455
|
+
) -> Sequence[TableInfo | ViewInfo | FunctionInfo | MacroInfo | IndexInfo]:
|
|
456
|
+
"""Get the contents of a schema.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
attach_opaque_data: The attachment identifier.
|
|
460
|
+
transaction_opaque_data: The transaction identifier, if any.
|
|
461
|
+
name: The name of the schema.
|
|
462
|
+
type: The type of objects to return. Must be a SchemaObjectType enum.
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
An iterable of TableInfo, ViewInfo, FunctionInfo, or MacroInfo objects
|
|
466
|
+
depending on the type parameter.
|
|
467
|
+
|
|
468
|
+
"""
|
|
469
|
+
schema_data = self._get_schema(attach_opaque_data, name)
|
|
470
|
+
result: list[TableInfo | ViewInfo | FunctionInfo | MacroInfo | IndexInfo] = []
|
|
471
|
+
|
|
472
|
+
# Normalize type parameter (may be string from wire protocol)
|
|
473
|
+
type_enum = type if isinstance(type, SchemaObjectType) else SchemaObjectType(type)
|
|
474
|
+
|
|
475
|
+
# Return tables for TABLE type
|
|
476
|
+
if type_enum == SchemaObjectType.TABLE:
|
|
477
|
+
for table_data in schema_data.tables.values():
|
|
478
|
+
result.append(table_data.info)
|
|
479
|
+
|
|
480
|
+
# Return views for VIEW type
|
|
481
|
+
elif type_enum == SchemaObjectType.VIEW:
|
|
482
|
+
for view_data in schema_data.views.values():
|
|
483
|
+
result.append(view_data.info)
|
|
484
|
+
|
|
485
|
+
# Return macros for SCALAR_MACRO or TABLE_MACRO type
|
|
486
|
+
elif type_enum in (SchemaObjectType.SCALAR_MACRO, SchemaObjectType.TABLE_MACRO):
|
|
487
|
+
target_macro_type = MacroType.SCALAR if type_enum == SchemaObjectType.SCALAR_MACRO else MacroType.TABLE
|
|
488
|
+
for macro_data in schema_data.macros.values():
|
|
489
|
+
if macro_data.info.macro_type == target_macro_type:
|
|
490
|
+
result.append(macro_data.info)
|
|
491
|
+
|
|
492
|
+
# Note: This example catalog doesn't store functions,
|
|
493
|
+
# so SCALAR_FUNCTION and TABLE_FUNCTION types return nothing
|
|
494
|
+
|
|
495
|
+
return result
|
|
496
|
+
|
|
497
|
+
def table_create(
|
|
498
|
+
self,
|
|
499
|
+
*,
|
|
500
|
+
attach_opaque_data: AttachOpaqueData,
|
|
501
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
502
|
+
schema_name: str,
|
|
503
|
+
name: str,
|
|
504
|
+
columns: SerializedSchema,
|
|
505
|
+
on_conflict: OnConflict,
|
|
506
|
+
not_null_constraints: list[int],
|
|
507
|
+
unique_constraints: list[list[int]],
|
|
508
|
+
check_constraints: list[str],
|
|
509
|
+
primary_key_constraints: list[list[int]] | None = None,
|
|
510
|
+
foreign_key_constraints: list[bytes] | None = None,
|
|
511
|
+
) -> None:
|
|
512
|
+
"""Create a new table."""
|
|
513
|
+
schema_data = self._get_schema(attach_opaque_data, schema_name)
|
|
514
|
+
if name in schema_data.tables:
|
|
515
|
+
if on_conflict == OnConflict.ERROR:
|
|
516
|
+
msg = f"Table {name!r} already exists in schema {schema_name!r}"
|
|
517
|
+
raise ValueError(msg)
|
|
518
|
+
if on_conflict == OnConflict.IGNORE:
|
|
519
|
+
return
|
|
520
|
+
# REPLACE: fall through to create
|
|
521
|
+
|
|
522
|
+
schema_data.tables[name] = TableData(
|
|
523
|
+
info=TableInfo(
|
|
524
|
+
name=name,
|
|
525
|
+
schema_name=schema_name,
|
|
526
|
+
columns=columns,
|
|
527
|
+
not_null_constraints=not_null_constraints,
|
|
528
|
+
unique_constraints=unique_constraints,
|
|
529
|
+
check_constraints=check_constraints,
|
|
530
|
+
comment=None,
|
|
531
|
+
tags={},
|
|
532
|
+
)
|
|
533
|
+
)
|
|
534
|
+
self._increment_version(attach_opaque_data)
|
|
535
|
+
|
|
536
|
+
def table_drop(
|
|
537
|
+
self,
|
|
538
|
+
*,
|
|
539
|
+
attach_opaque_data: AttachOpaqueData,
|
|
540
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
541
|
+
schema_name: str,
|
|
542
|
+
name: str,
|
|
543
|
+
ignore_not_found: bool,
|
|
544
|
+
cascade: bool = False,
|
|
545
|
+
) -> None:
|
|
546
|
+
"""Drop a table."""
|
|
547
|
+
schema_data = self._get_schema(attach_opaque_data, schema_name)
|
|
548
|
+
if name not in schema_data.tables:
|
|
549
|
+
if ignore_not_found:
|
|
550
|
+
return
|
|
551
|
+
msg = f"Table {name!r} not found in schema {schema_name!r}"
|
|
552
|
+
raise ValueError(msg)
|
|
553
|
+
del schema_data.tables[name]
|
|
554
|
+
self._increment_version(attach_opaque_data)
|
|
555
|
+
|
|
556
|
+
def table_comment_set(
|
|
557
|
+
self,
|
|
558
|
+
*,
|
|
559
|
+
attach_opaque_data: AttachOpaqueData,
|
|
560
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
561
|
+
schema_name: str,
|
|
562
|
+
name: str,
|
|
563
|
+
comment: str | None,
|
|
564
|
+
ignore_not_found: bool,
|
|
565
|
+
) -> None:
|
|
566
|
+
"""Set the comment for a table."""
|
|
567
|
+
schema_data = self._get_schema(attach_opaque_data, schema_name)
|
|
568
|
+
table_data = schema_data.tables.get(name)
|
|
569
|
+
if table_data is None:
|
|
570
|
+
if ignore_not_found:
|
|
571
|
+
return
|
|
572
|
+
msg = f"Table {name!r} not found in schema {schema_name!r}"
|
|
573
|
+
raise ValueError(msg)
|
|
574
|
+
# Create a new TableInfo with the updated comment
|
|
575
|
+
old_info = table_data.info
|
|
576
|
+
schema_data.tables[name] = TableData(
|
|
577
|
+
info=TableInfo(
|
|
578
|
+
name=old_info.name,
|
|
579
|
+
schema_name=old_info.schema_name,
|
|
580
|
+
columns=old_info.columns,
|
|
581
|
+
not_null_constraints=old_info.not_null_constraints,
|
|
582
|
+
unique_constraints=old_info.unique_constraints,
|
|
583
|
+
check_constraints=old_info.check_constraints,
|
|
584
|
+
comment=comment,
|
|
585
|
+
tags=old_info.tags,
|
|
586
|
+
)
|
|
587
|
+
)
|
|
588
|
+
self._increment_version(attach_opaque_data)
|
|
589
|
+
|
|
590
|
+
def table_rename(
|
|
591
|
+
self,
|
|
592
|
+
*,
|
|
593
|
+
attach_opaque_data: AttachOpaqueData,
|
|
594
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
595
|
+
schema_name: str,
|
|
596
|
+
name: str,
|
|
597
|
+
new_name: str,
|
|
598
|
+
ignore_not_found: bool,
|
|
599
|
+
) -> None:
|
|
600
|
+
"""Rename a table."""
|
|
601
|
+
schema_data = self._get_schema(attach_opaque_data, schema_name)
|
|
602
|
+
if name not in schema_data.tables:
|
|
603
|
+
if ignore_not_found:
|
|
604
|
+
return
|
|
605
|
+
msg = f"Table {name!r} not found in schema {schema_name!r}"
|
|
606
|
+
raise ValueError(msg)
|
|
607
|
+
if new_name in schema_data.tables:
|
|
608
|
+
msg = f"Table {new_name!r} already exists in schema {schema_name!r}"
|
|
609
|
+
raise ValueError(msg)
|
|
610
|
+
table_data = schema_data.tables.pop(name)
|
|
611
|
+
# Create new TableInfo with updated name
|
|
612
|
+
old_info = table_data.info
|
|
613
|
+
schema_data.tables[new_name] = TableData(
|
|
614
|
+
info=TableInfo(
|
|
615
|
+
name=new_name,
|
|
616
|
+
schema_name=old_info.schema_name,
|
|
617
|
+
columns=old_info.columns,
|
|
618
|
+
not_null_constraints=old_info.not_null_constraints,
|
|
619
|
+
unique_constraints=old_info.unique_constraints,
|
|
620
|
+
check_constraints=old_info.check_constraints,
|
|
621
|
+
comment=old_info.comment,
|
|
622
|
+
tags=old_info.tags,
|
|
623
|
+
)
|
|
624
|
+
)
|
|
625
|
+
self._increment_version(attach_opaque_data)
|
|
626
|
+
|
|
627
|
+
def view_create(
|
|
628
|
+
self,
|
|
629
|
+
*,
|
|
630
|
+
attach_opaque_data: AttachOpaqueData,
|
|
631
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
632
|
+
schema_name: str,
|
|
633
|
+
name: str,
|
|
634
|
+
definition: str,
|
|
635
|
+
on_conflict: OnConflict,
|
|
636
|
+
) -> None:
|
|
637
|
+
"""Create a new view."""
|
|
638
|
+
schema_data = self._get_schema(attach_opaque_data, schema_name)
|
|
639
|
+
if name in schema_data.views:
|
|
640
|
+
if on_conflict == OnConflict.ERROR:
|
|
641
|
+
msg = f"View {name!r} already exists in schema {schema_name!r}"
|
|
642
|
+
raise ValueError(msg)
|
|
643
|
+
if on_conflict == OnConflict.IGNORE:
|
|
644
|
+
return
|
|
645
|
+
# REPLACE: fall through to create
|
|
646
|
+
|
|
647
|
+
schema_data.views[name] = ViewData(
|
|
648
|
+
info=ViewInfo(
|
|
649
|
+
name=name,
|
|
650
|
+
schema_name=schema_name,
|
|
651
|
+
definition=definition,
|
|
652
|
+
comment=None,
|
|
653
|
+
tags={},
|
|
654
|
+
)
|
|
655
|
+
)
|
|
656
|
+
self._increment_version(attach_opaque_data)
|
|
657
|
+
|
|
658
|
+
def view_drop(
|
|
659
|
+
self,
|
|
660
|
+
*,
|
|
661
|
+
attach_opaque_data: AttachOpaqueData,
|
|
662
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
663
|
+
schema_name: str,
|
|
664
|
+
name: str,
|
|
665
|
+
ignore_not_found: bool,
|
|
666
|
+
cascade: bool = False,
|
|
667
|
+
) -> None:
|
|
668
|
+
"""Drop a view."""
|
|
669
|
+
schema_data = self._get_schema(attach_opaque_data, schema_name)
|
|
670
|
+
if name not in schema_data.views:
|
|
671
|
+
if ignore_not_found:
|
|
672
|
+
return
|
|
673
|
+
msg = f"View {name!r} not found in schema {schema_name!r}"
|
|
674
|
+
raise ValueError(msg)
|
|
675
|
+
del schema_data.views[name]
|
|
676
|
+
self._increment_version(attach_opaque_data)
|
|
677
|
+
|
|
678
|
+
def view_rename(
|
|
679
|
+
self,
|
|
680
|
+
*,
|
|
681
|
+
attach_opaque_data: AttachOpaqueData,
|
|
682
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
683
|
+
schema_name: str,
|
|
684
|
+
name: str,
|
|
685
|
+
new_name: str,
|
|
686
|
+
ignore_not_found: bool,
|
|
687
|
+
) -> None:
|
|
688
|
+
"""Rename a view."""
|
|
689
|
+
schema_data = self._get_schema(attach_opaque_data, schema_name)
|
|
690
|
+
if name not in schema_data.views:
|
|
691
|
+
if ignore_not_found:
|
|
692
|
+
return
|
|
693
|
+
msg = f"View {name!r} not found in schema {schema_name!r}"
|
|
694
|
+
raise ValueError(msg)
|
|
695
|
+
if new_name in schema_data.views:
|
|
696
|
+
msg = f"View {new_name!r} already exists in schema {schema_name!r}"
|
|
697
|
+
raise ValueError(msg)
|
|
698
|
+
view_data = schema_data.views.pop(name)
|
|
699
|
+
# Create new ViewInfo with updated name
|
|
700
|
+
old_info = view_data.info
|
|
701
|
+
schema_data.views[new_name] = ViewData(
|
|
702
|
+
info=ViewInfo(
|
|
703
|
+
name=new_name,
|
|
704
|
+
schema_name=old_info.schema_name,
|
|
705
|
+
definition=old_info.definition,
|
|
706
|
+
comment=old_info.comment,
|
|
707
|
+
tags=old_info.tags,
|
|
708
|
+
)
|
|
709
|
+
)
|
|
710
|
+
self._increment_version(attach_opaque_data)
|
|
711
|
+
|
|
712
|
+
def view_comment_set(
|
|
713
|
+
self,
|
|
714
|
+
*,
|
|
715
|
+
attach_opaque_data: AttachOpaqueData,
|
|
716
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
717
|
+
schema_name: str,
|
|
718
|
+
name: str,
|
|
719
|
+
comment: str | None,
|
|
720
|
+
ignore_not_found: bool,
|
|
721
|
+
) -> None:
|
|
722
|
+
"""Set the comment for a view."""
|
|
723
|
+
schema_data = self._get_schema(attach_opaque_data, schema_name)
|
|
724
|
+
view_data = schema_data.views.get(name)
|
|
725
|
+
if view_data is None:
|
|
726
|
+
if ignore_not_found:
|
|
727
|
+
return
|
|
728
|
+
msg = f"View {name!r} not found in schema {schema_name!r}"
|
|
729
|
+
raise ValueError(msg)
|
|
730
|
+
# Create a new ViewInfo with the updated comment
|
|
731
|
+
old_info = view_data.info
|
|
732
|
+
schema_data.views[name] = ViewData(
|
|
733
|
+
info=ViewInfo(
|
|
734
|
+
name=old_info.name,
|
|
735
|
+
schema_name=old_info.schema_name,
|
|
736
|
+
definition=old_info.definition,
|
|
737
|
+
comment=comment,
|
|
738
|
+
tags=old_info.tags,
|
|
739
|
+
)
|
|
740
|
+
)
|
|
741
|
+
self._increment_version(attach_opaque_data)
|
|
742
|
+
|
|
743
|
+
def macro_create(
|
|
744
|
+
self,
|
|
745
|
+
*,
|
|
746
|
+
attach_opaque_data: AttachOpaqueData,
|
|
747
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
748
|
+
schema_name: str,
|
|
749
|
+
name: str,
|
|
750
|
+
macro_type: MacroType,
|
|
751
|
+
parameters: list[str],
|
|
752
|
+
definition: str,
|
|
753
|
+
on_conflict: OnConflict,
|
|
754
|
+
parameter_default_values: pa.RecordBatch | None = None,
|
|
755
|
+
) -> None:
|
|
756
|
+
"""Create a new macro."""
|
|
757
|
+
schema_data = self._get_schema(attach_opaque_data, schema_name)
|
|
758
|
+
if name in schema_data.macros:
|
|
759
|
+
if on_conflict == OnConflict.ERROR:
|
|
760
|
+
msg = f"Macro {name!r} already exists in schema {schema_name!r}"
|
|
761
|
+
raise ValueError(msg)
|
|
762
|
+
if on_conflict == OnConflict.IGNORE:
|
|
763
|
+
return
|
|
764
|
+
# REPLACE: fall through to create
|
|
765
|
+
|
|
766
|
+
schema_data.macros[name] = MacroData(
|
|
767
|
+
info=MacroInfo(
|
|
768
|
+
name=name,
|
|
769
|
+
schema_name=schema_name,
|
|
770
|
+
macro_type=macro_type,
|
|
771
|
+
parameters=parameters,
|
|
772
|
+
parameter_default_values=parameter_default_values,
|
|
773
|
+
definition=definition,
|
|
774
|
+
comment=None,
|
|
775
|
+
tags={},
|
|
776
|
+
)
|
|
777
|
+
)
|
|
778
|
+
self._increment_version(attach_opaque_data)
|
|
779
|
+
|
|
780
|
+
def macro_drop(
|
|
781
|
+
self,
|
|
782
|
+
*,
|
|
783
|
+
attach_opaque_data: AttachOpaqueData,
|
|
784
|
+
transaction_opaque_data: TransactionOpaqueData | None,
|
|
785
|
+
schema_name: str,
|
|
786
|
+
name: str,
|
|
787
|
+
ignore_not_found: bool,
|
|
788
|
+
) -> None:
|
|
789
|
+
"""Drop a macro."""
|
|
790
|
+
schema_data = self._get_schema(attach_opaque_data, schema_name)
|
|
791
|
+
if name not in schema_data.macros:
|
|
792
|
+
if ignore_not_found:
|
|
793
|
+
return
|
|
794
|
+
msg = f"Macro {name!r} not found in schema {schema_name!r}"
|
|
795
|
+
raise ValueError(msg)
|
|
796
|
+
del schema_data.macros[name]
|
|
797
|
+
self._increment_version(attach_opaque_data)
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
class InMemoryCatalogWorker(Worker):
|
|
801
|
+
"""Example worker with InMemoryCatalog support."""
|
|
802
|
+
|
|
803
|
+
catalog_interface = InMemoryCatalog
|
|
804
|
+
functions = [] # No functions, just catalog support
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
def main() -> None:
|
|
808
|
+
"""Run the in-memory catalog worker process."""
|
|
809
|
+
InMemoryCatalogWorker.main()
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
if __name__ == "__main__":
|
|
813
|
+
main()
|