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,321 @@
|
|
|
1
|
+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
|
|
3
|
+
"""Arithmetic scalar fixtures (multiply, double, add, sum, concat)."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Annotated, Any
|
|
8
|
+
|
|
9
|
+
import pyarrow as pa
|
|
10
|
+
import pyarrow.compute as pc
|
|
11
|
+
|
|
12
|
+
from vgi._test_fixtures.scalar._common import _is_addable_type, _is_multipliable_type, _promote_for_addition
|
|
13
|
+
from vgi.arguments import ConstParam, Param, Returns
|
|
14
|
+
from vgi.metadata import FunctionExample
|
|
15
|
+
from vgi.scalar_function import BindParameters, BindResult, ScalarFunction
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MultiplyFunction(ScalarFunction):
|
|
19
|
+
"""Multiplies a value by a constant factor.
|
|
20
|
+
|
|
21
|
+
This example demonstrates type inference with array classes:
|
|
22
|
+
- pa.Int64Array -> pa.int64() (inferred from Annotated type)
|
|
23
|
+
- ConstParam() for constant scalar input (receives Python value at runtime)
|
|
24
|
+
- Returns() output type is also inferred from pa.Int64Array
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
SQL: SELECT multiply(price, 2) FROM products
|
|
28
|
+
Input: price=[10, 20, 30]
|
|
29
|
+
Args: factor=2
|
|
30
|
+
Output: result=[20, 40, 60]
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
class Meta:
|
|
35
|
+
"""Function metadata."""
|
|
36
|
+
|
|
37
|
+
name = "multiply"
|
|
38
|
+
description = "Multiplies a value by a constant factor"
|
|
39
|
+
examples = [
|
|
40
|
+
FunctionExample(
|
|
41
|
+
sql="SELECT multiply(price, 2) FROM products",
|
|
42
|
+
description="Double all prices",
|
|
43
|
+
),
|
|
44
|
+
FunctionExample(
|
|
45
|
+
sql="SELECT multiply(quantity, 10) FROM inventory",
|
|
46
|
+
description="Scale quantities by 10",
|
|
47
|
+
),
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def compute(
|
|
52
|
+
cls,
|
|
53
|
+
value: Annotated[pa.Int64Array, Param(doc="Integer value to multiply")],
|
|
54
|
+
factor: Annotated[int, ConstParam("Multiplication factor")],
|
|
55
|
+
) -> Annotated[pa.Int64Array, Returns()]:
|
|
56
|
+
"""Multiply values by the constant factor."""
|
|
57
|
+
return pc.multiply(value, factor)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class DoubleFunction(ScalarFunction):
|
|
61
|
+
"""Doubles numeric values.
|
|
62
|
+
|
|
63
|
+
This example demonstrates the Annotated API with:
|
|
64
|
+
- AnyArrow type (arrow_type=None) with type_bound for flexible numeric input
|
|
65
|
+
- Dynamic output type computed in bind()
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
SQL: SELECT double(price) FROM products
|
|
69
|
+
Input: price=[1, 2, 3]
|
|
70
|
+
Output: result=[2, 4, 6]
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
class Meta:
|
|
75
|
+
"""Function metadata."""
|
|
76
|
+
|
|
77
|
+
name = "double"
|
|
78
|
+
description = "Doubles numeric values"
|
|
79
|
+
examples = [
|
|
80
|
+
FunctionExample(
|
|
81
|
+
sql="SELECT double(price) FROM products",
|
|
82
|
+
description="Double the price values",
|
|
83
|
+
),
|
|
84
|
+
FunctionExample(
|
|
85
|
+
sql="SELECT double(quantity) FROM inventory",
|
|
86
|
+
description="Double inventory quantities",
|
|
87
|
+
),
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def on_bind(cls, params: BindParameters) -> BindResult:
|
|
92
|
+
"""Compute output type from input value type."""
|
|
93
|
+
field = params.arguments_schema.field(0)
|
|
94
|
+
return BindResult(_promote_for_addition(field.type))
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def compute(
|
|
98
|
+
cls,
|
|
99
|
+
value: Annotated[
|
|
100
|
+
pa.Array[Any],
|
|
101
|
+
Param(doc="Numeric value to double", type_bound=_is_multipliable_type),
|
|
102
|
+
],
|
|
103
|
+
) -> Annotated[pa.Array[Any], Returns()]:
|
|
104
|
+
"""Double the input values."""
|
|
105
|
+
if pa.types.is_decimal(value.type):
|
|
106
|
+
# pc.multiply on decimals follows the SQL rule
|
|
107
|
+
# decimal(p1,s1) * decimal(p2,s2) -> decimal(p1+p2+1, s1+s2);
|
|
108
|
+
# multiplying by the literal 2 (decimal128(19, 0)) blows past
|
|
109
|
+
# decimal128's 38-digit cap for any input wider than ~18 digits.
|
|
110
|
+
# Compute `value + value` (which only adds 1 to precision) and
|
|
111
|
+
# cast the result to the declared output type. We do the add at
|
|
112
|
+
# the input precision, then cast — for inputs at the 38-digit
|
|
113
|
+
# cap we need decimal256 just to hold the +1 intermediate.
|
|
114
|
+
in_p, in_s = value.type.precision, value.type.scale
|
|
115
|
+
work_type = pa.decimal256(in_p, in_s) if in_p >= 38 else value.type
|
|
116
|
+
casted = pc.cast(value, work_type) if work_type != value.type else value
|
|
117
|
+
summed: pa.Array[Any] = pc.add(casted, casted) # decimal(p+1, s)
|
|
118
|
+
out_type = _promote_for_addition(value.type)
|
|
119
|
+
if summed.type == out_type:
|
|
120
|
+
return summed
|
|
121
|
+
return pc.cast(summed, out_type)
|
|
122
|
+
result: pa.Array[Any] = pc.multiply(value, 2)
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class AddValuesFunction(ScalarFunction):
|
|
127
|
+
"""Adds two numeric values together.
|
|
128
|
+
|
|
129
|
+
This example demonstrates:
|
|
130
|
+
- Multiple Param() annotations with type_bound validation
|
|
131
|
+
- Dynamic output type with type promotion for overflow safety
|
|
132
|
+
|
|
133
|
+
Validates that both values are numeric types (integer, float, decimal, or
|
|
134
|
+
temporal) at compute time, raising SchemaValidationError if not.
|
|
135
|
+
|
|
136
|
+
Example:
|
|
137
|
+
SQL: SELECT add_values(price, tax) FROM orders
|
|
138
|
+
Input: price=[1, 2, 3], tax=[10, 20, 30]
|
|
139
|
+
Output: result=[11, 22, 33]
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
SchemaValidationError: If either value is not a numeric type.
|
|
143
|
+
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
class Meta:
|
|
147
|
+
"""Function metadata."""
|
|
148
|
+
|
|
149
|
+
name = "add_values"
|
|
150
|
+
description = "Adds two numeric values"
|
|
151
|
+
examples = [
|
|
152
|
+
FunctionExample(
|
|
153
|
+
sql="SELECT add_values(price, tax) FROM orders",
|
|
154
|
+
description="Calculate total by adding price and tax",
|
|
155
|
+
),
|
|
156
|
+
FunctionExample(
|
|
157
|
+
sql="SELECT add_values(quantity, reserved) FROM inventory",
|
|
158
|
+
description="Sum quantity and reserved amounts",
|
|
159
|
+
),
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
_output_type: pa.DataType
|
|
163
|
+
|
|
164
|
+
@classmethod
|
|
165
|
+
def on_bind(cls, params: BindParameters) -> BindResult:
|
|
166
|
+
"""Compute output type from input value types."""
|
|
167
|
+
field1 = params.arguments_schema.field(0)
|
|
168
|
+
field2 = params.arguments_schema.field(1)
|
|
169
|
+
|
|
170
|
+
# Compute the output type by promoting to the wider of the two types,
|
|
171
|
+
# then promoting again to reduce overflow risk.
|
|
172
|
+
common_type = pc.add(pa.nulls(1, type=field1.type), pa.nulls(1, type=field2.type)).type
|
|
173
|
+
return BindResult(_promote_for_addition(common_type))
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def compute(
|
|
177
|
+
cls,
|
|
178
|
+
col1: Annotated[
|
|
179
|
+
pa.Array[Any],
|
|
180
|
+
Param(doc="First numeric value", type_bound=_is_addable_type),
|
|
181
|
+
],
|
|
182
|
+
col2: Annotated[
|
|
183
|
+
pa.Array[Any],
|
|
184
|
+
Param(doc="Second numeric value", type_bound=_is_addable_type),
|
|
185
|
+
],
|
|
186
|
+
) -> Annotated[pa.Array[Any], Returns()]:
|
|
187
|
+
"""Add the two values together."""
|
|
188
|
+
result: pa.Array[Any] = pc.add(col1, col2)
|
|
189
|
+
return result
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class SumValuesFunction(ScalarFunction):
|
|
193
|
+
"""Sums multiple numeric values.
|
|
194
|
+
|
|
195
|
+
This example demonstrates:
|
|
196
|
+
- varargs=True to accept variable number of values
|
|
197
|
+
- type_bound validation on all varargs values
|
|
198
|
+
- Dynamic output type computed in bind()
|
|
199
|
+
|
|
200
|
+
Example:
|
|
201
|
+
SQL: SELECT sum_values(price, tax, shipping) FROM orders
|
|
202
|
+
Input: price=[1, 2], tax=[10, 20], shipping=[100, 200]
|
|
203
|
+
Output: result=[111, 222]
|
|
204
|
+
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
class Meta:
|
|
208
|
+
"""Function metadata."""
|
|
209
|
+
|
|
210
|
+
name = "sum_values"
|
|
211
|
+
description = "Sum multiple numeric values"
|
|
212
|
+
examples = [
|
|
213
|
+
FunctionExample(
|
|
214
|
+
sql="SELECT sum_values(price, tax, shipping) FROM orders",
|
|
215
|
+
description="Calculate total cost from multiple values",
|
|
216
|
+
),
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
@classmethod
|
|
220
|
+
def on_bind(
|
|
221
|
+
cls,
|
|
222
|
+
params: BindParameters,
|
|
223
|
+
) -> BindResult:
|
|
224
|
+
"""Compute output type from first value, promoted for overflow safety."""
|
|
225
|
+
first_type = params.arguments_schema.field(0).type
|
|
226
|
+
return BindResult(_promote_for_addition(first_type))
|
|
227
|
+
|
|
228
|
+
@classmethod
|
|
229
|
+
def compute(
|
|
230
|
+
cls,
|
|
231
|
+
values: Annotated[
|
|
232
|
+
list[pa.Array[Any]],
|
|
233
|
+
Param(
|
|
234
|
+
doc="Numeric values to sum",
|
|
235
|
+
type_bound=_is_addable_type,
|
|
236
|
+
varargs=True,
|
|
237
|
+
),
|
|
238
|
+
],
|
|
239
|
+
) -> Annotated[pa.Array[Any], Returns()]:
|
|
240
|
+
"""Sum all specified values."""
|
|
241
|
+
result: pa.Array[Any] = values[0]
|
|
242
|
+
for val in values[1:]:
|
|
243
|
+
result = pc.add(result, val)
|
|
244
|
+
return result
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class ConcatValuesIntFunction(ScalarFunction):
|
|
248
|
+
"""Concatenate integer column values as their string sum.
|
|
249
|
+
|
|
250
|
+
Varargs overload for integer columns: sums all values per row and
|
|
251
|
+
returns the result as a string.
|
|
252
|
+
|
|
253
|
+
Example:
|
|
254
|
+
SQL: SELECT concat_values(a, b, c) FROM t
|
|
255
|
+
Input: a=[1, 2], b=[10, 20], c=[100, 200]
|
|
256
|
+
Output: result=['111', '222']
|
|
257
|
+
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
class Meta:
|
|
261
|
+
"""Function metadata."""
|
|
262
|
+
|
|
263
|
+
name = "concat_values"
|
|
264
|
+
description = "Sum integer varargs and return as string"
|
|
265
|
+
|
|
266
|
+
@classmethod
|
|
267
|
+
def on_bind(cls, params: BindParameters) -> BindResult:
|
|
268
|
+
"""Output is always string."""
|
|
269
|
+
return BindResult(pa.string())
|
|
270
|
+
|
|
271
|
+
@classmethod
|
|
272
|
+
def compute(
|
|
273
|
+
cls,
|
|
274
|
+
values: Annotated[
|
|
275
|
+
list[pa.Int64Array],
|
|
276
|
+
Param(doc="Integer values to sum", varargs=True),
|
|
277
|
+
],
|
|
278
|
+
) -> Annotated[pa.Array[Any], Returns()]:
|
|
279
|
+
"""Sum all integer columns and return as string."""
|
|
280
|
+
result: pa.Array[Any] = values[0]
|
|
281
|
+
for val in values[1:]:
|
|
282
|
+
result = pc.add(result, val)
|
|
283
|
+
return pc.cast(result, pa.string())
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class ConcatValuesStrFunction(ScalarFunction):
|
|
287
|
+
"""Concatenate string column values.
|
|
288
|
+
|
|
289
|
+
Varargs overload for string columns: concatenates all string values per row.
|
|
290
|
+
|
|
291
|
+
Example:
|
|
292
|
+
SQL: SELECT concat_values(a, b) FROM t
|
|
293
|
+
Input: a=['hello', 'foo'], b=[' world', 'bar']
|
|
294
|
+
Output: result=['hello world', 'foobar']
|
|
295
|
+
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
class Meta:
|
|
299
|
+
"""Function metadata."""
|
|
300
|
+
|
|
301
|
+
name = "concat_values"
|
|
302
|
+
description = "Concatenate string varargs"
|
|
303
|
+
|
|
304
|
+
@classmethod
|
|
305
|
+
def on_bind(cls, params: BindParameters) -> BindResult:
|
|
306
|
+
"""Output is always string."""
|
|
307
|
+
return BindResult(pa.string())
|
|
308
|
+
|
|
309
|
+
@classmethod
|
|
310
|
+
def compute(
|
|
311
|
+
cls,
|
|
312
|
+
values: Annotated[
|
|
313
|
+
list[pa.StringArray],
|
|
314
|
+
Param(doc="String values to concatenate", varargs=True),
|
|
315
|
+
],
|
|
316
|
+
) -> Annotated[pa.Array[Any], Returns()]:
|
|
317
|
+
"""Concatenate all string columns."""
|
|
318
|
+
result: pa.StringArray = values[0]
|
|
319
|
+
for val in values[1:]:
|
|
320
|
+
result = pc.binary_join_element_wise(result, val, "") # type: ignore[call-overload]
|
|
321
|
+
return result
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
|
|
3
|
+
"""Binary-payload and string-cased scalar fixtures (binary_packet, upper_case)."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Annotated, Any
|
|
8
|
+
|
|
9
|
+
import pyarrow as pa
|
|
10
|
+
import pyarrow.compute as pc
|
|
11
|
+
|
|
12
|
+
from vgi.arguments import ConstParam, Param, Returns
|
|
13
|
+
from vgi.metadata import FunctionExample
|
|
14
|
+
from vgi.scalar_function import ScalarFunction
|
|
15
|
+
|
|
16
|
+
_CONFIG_STRUCT_TYPE = pa.struct([("label", pa.string()), ("version", pa.int64())])
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BinaryPacketFunction(ScalarFunction):
|
|
20
|
+
"""Builds binary packets with header, payload, and config metadata.
|
|
21
|
+
|
|
22
|
+
This example demonstrates complex ConstParam types:
|
|
23
|
+
- header (binary): Constant prefix bytes at the start
|
|
24
|
+
- payload (binary column): Variable binary data per row
|
|
25
|
+
- config (struct): Constant metadata struct at the end
|
|
26
|
+
|
|
27
|
+
The constant parameters bracket the column parameter (first and last).
|
|
28
|
+
|
|
29
|
+
The function concatenates: header + payload + config.label encoded + version byte
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
SQL: SELECT binary_packet(x'CAFE', data, {label: 'v1', version: 1}) FROM t
|
|
33
|
+
Input: data=[x'0102', x'0304']
|
|
34
|
+
Args: header=x'CAFE', config={label: 'v1', version: 1}
|
|
35
|
+
Output: result=[x'CAFE0102763101', x'CAFE0304763101']
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
class Meta:
|
|
40
|
+
"""Function metadata."""
|
|
41
|
+
|
|
42
|
+
name = "binary_packet"
|
|
43
|
+
description = "Build binary packets with header, payload, and config"
|
|
44
|
+
examples = [
|
|
45
|
+
FunctionExample(
|
|
46
|
+
sql="SELECT binary_packet(x'FF', payload, {label: 'msg', version: 1}) FROM t",
|
|
47
|
+
description="Build packets with 0xFF header",
|
|
48
|
+
),
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def compute(
|
|
53
|
+
cls,
|
|
54
|
+
header: Annotated[
|
|
55
|
+
bytes,
|
|
56
|
+
ConstParam("Header bytes to prepend", arrow_type=pa.binary()),
|
|
57
|
+
],
|
|
58
|
+
payload: Annotated[pa.BinaryArray, Param(doc="Binary payload data")],
|
|
59
|
+
config: Annotated[
|
|
60
|
+
dict[str, Any],
|
|
61
|
+
ConstParam("Config {label, version}", arrow_type=_CONFIG_STRUCT_TYPE),
|
|
62
|
+
],
|
|
63
|
+
) -> Annotated[pa.BinaryArray, Returns()]:
|
|
64
|
+
"""Build binary packets from header, payload, and config."""
|
|
65
|
+
# Extract config fields
|
|
66
|
+
label: str = config["label"]
|
|
67
|
+
version: int = config["version"]
|
|
68
|
+
|
|
69
|
+
# Build suffix from config: label bytes + version as single byte
|
|
70
|
+
suffix = label.encode("utf-8") + bytes([version & 0xFF])
|
|
71
|
+
|
|
72
|
+
# Concatenate header + payload + suffix for each row
|
|
73
|
+
results: list[bytes] = []
|
|
74
|
+
for i in range(len(payload)):
|
|
75
|
+
if payload[i].is_valid:
|
|
76
|
+
payload_bytes: bytes = payload[i].as_py()
|
|
77
|
+
results.append(header + payload_bytes + suffix)
|
|
78
|
+
else:
|
|
79
|
+
results.append(header + suffix) # Empty payload for nulls
|
|
80
|
+
|
|
81
|
+
return pa.array(results, type=pa.binary())
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class UpperCaseFunction(ScalarFunction):
|
|
85
|
+
"""Converts string values to uppercase.
|
|
86
|
+
|
|
87
|
+
This example demonstrates type inference with pa.StringArray:
|
|
88
|
+
- pa.StringArray -> pa.string() (inferred from Annotated type)
|
|
89
|
+
- Returns() output type is also inferred from pa.StringArray
|
|
90
|
+
|
|
91
|
+
Example:
|
|
92
|
+
SQL: SELECT upper_case(name) FROM users
|
|
93
|
+
Input: name=["alice", "bob", "charlie"]
|
|
94
|
+
Output: result=["ALICE", "BOB", "CHARLIE"]
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
class Meta:
|
|
99
|
+
"""Function metadata."""
|
|
100
|
+
|
|
101
|
+
name = "upper_case"
|
|
102
|
+
description = "Converts string values to uppercase"
|
|
103
|
+
examples = [
|
|
104
|
+
FunctionExample(
|
|
105
|
+
sql="SELECT upper_case(name) FROM users",
|
|
106
|
+
description="Convert user names to uppercase",
|
|
107
|
+
),
|
|
108
|
+
FunctionExample(
|
|
109
|
+
sql="SELECT upper_case(status) FROM orders WHERE id = 1",
|
|
110
|
+
description="Uppercase the status field",
|
|
111
|
+
),
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def compute(
|
|
116
|
+
cls,
|
|
117
|
+
value: Annotated[pa.StringArray, Param(doc="String value to uppercase")],
|
|
118
|
+
) -> Annotated[pa.StringArray, Returns()]:
|
|
119
|
+
"""Convert the string values to uppercase."""
|
|
120
|
+
return pc.utf8_upper(value)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
|
|
3
|
+
"""Number/string formatting scalar fixtures (format_number_*, smart_format_*)."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Annotated
|
|
8
|
+
|
|
9
|
+
import pyarrow as pa
|
|
10
|
+
|
|
11
|
+
from vgi.arguments import ConstParam, Param, Returns
|
|
12
|
+
from vgi.metadata import FunctionExample
|
|
13
|
+
from vgi.scalar_function import ScalarFunction
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FormatNumberDefaultFunction(ScalarFunction):
|
|
17
|
+
"""Format a number with default precision (0 decimal places).
|
|
18
|
+
|
|
19
|
+
Overload with 0 ConstParams: just a column input.
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
SQL: SELECT format_number(price) FROM products
|
|
23
|
+
Input: price=[3.14, 2.718, 100.5]
|
|
24
|
+
Output: result=['3', '3', '100']
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
class Meta:
|
|
29
|
+
"""Function metadata."""
|
|
30
|
+
|
|
31
|
+
name = "format_number"
|
|
32
|
+
description = "Format number with default precision (0 decimals)"
|
|
33
|
+
examples = [
|
|
34
|
+
FunctionExample(
|
|
35
|
+
sql="SELECT format_number(price) FROM products",
|
|
36
|
+
description="Format prices with no decimal places",
|
|
37
|
+
),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def compute(
|
|
42
|
+
cls,
|
|
43
|
+
value: Annotated[pa.DoubleArray, Param(doc="Number to format")],
|
|
44
|
+
) -> Annotated[pa.StringArray, Returns()]:
|
|
45
|
+
"""Format each value with 0 decimal places."""
|
|
46
|
+
return pa.array(
|
|
47
|
+
[f"{v:.0f}" if v is not None else None for v in value.to_pylist()],
|
|
48
|
+
type=pa.string(),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class FormatNumberPrecisionFunction(ScalarFunction):
|
|
53
|
+
"""Format a number with specified precision.
|
|
54
|
+
|
|
55
|
+
Overload with 1 ConstParam: precision.
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
SQL: SELECT format_number(2, price) FROM products
|
|
59
|
+
Input: price=[3.14159, 2.718, 100.5]
|
|
60
|
+
Args: precision=2
|
|
61
|
+
Output: result=['3.14', '2.72', '100.50']
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
class Meta:
|
|
66
|
+
"""Function metadata."""
|
|
67
|
+
|
|
68
|
+
name = "format_number"
|
|
69
|
+
description = "Format number with specified precision"
|
|
70
|
+
examples = [
|
|
71
|
+
FunctionExample(
|
|
72
|
+
sql="SELECT format_number(2, price) FROM products",
|
|
73
|
+
description="Format prices with 2 decimal places",
|
|
74
|
+
),
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def compute(
|
|
79
|
+
cls,
|
|
80
|
+
precision: Annotated[int, ConstParam("Number of decimal places")],
|
|
81
|
+
value: Annotated[pa.DoubleArray, Param(doc="Number to format")],
|
|
82
|
+
) -> Annotated[pa.StringArray, Returns()]:
|
|
83
|
+
"""Format each value with the specified precision."""
|
|
84
|
+
return pa.array(
|
|
85
|
+
[f"{v:.{precision}f}" if v is not None else None for v in value.to_pylist()],
|
|
86
|
+
type=pa.string(),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class FormatNumberFullFunction(ScalarFunction):
|
|
91
|
+
"""Format a number with precision and prefix.
|
|
92
|
+
|
|
93
|
+
Overload with 2 ConstParams: precision and prefix.
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
SQL: SELECT format_number(2, '$', price) FROM products
|
|
97
|
+
Input: price=[3.14, 2.718, 100.5]
|
|
98
|
+
Args: precision=2, prefix='$'
|
|
99
|
+
Output: result=['$3.14', '$2.72', '$100.50']
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
class Meta:
|
|
104
|
+
"""Function metadata."""
|
|
105
|
+
|
|
106
|
+
name = "format_number"
|
|
107
|
+
description = "Format number with precision and prefix"
|
|
108
|
+
examples = [
|
|
109
|
+
FunctionExample(
|
|
110
|
+
sql="SELECT format_number(2, '$', price) FROM products",
|
|
111
|
+
description="Format prices with dollar sign and 2 decimals",
|
|
112
|
+
),
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def compute(
|
|
117
|
+
cls,
|
|
118
|
+
precision: Annotated[int, ConstParam("Number of decimal places")],
|
|
119
|
+
prefix: Annotated[str, ConstParam("Prefix string")],
|
|
120
|
+
value: Annotated[pa.DoubleArray, Param(doc="Number to format")],
|
|
121
|
+
) -> Annotated[pa.StringArray, Returns()]:
|
|
122
|
+
"""Format each value with prefix and specified precision."""
|
|
123
|
+
return pa.array(
|
|
124
|
+
[f"{prefix}{v:.{precision}f}" if v is not None else None for v in value.to_pylist()],
|
|
125
|
+
type=pa.string(),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class SmartFormatWidthFunction(ScalarFunction):
|
|
130
|
+
"""Right-align a double in a field of given width.
|
|
131
|
+
|
|
132
|
+
Overload with int ConstParam.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
class Meta:
|
|
136
|
+
"""Function metadata."""
|
|
137
|
+
|
|
138
|
+
name = "smart_format"
|
|
139
|
+
description = "Right-align value in field of given width"
|
|
140
|
+
|
|
141
|
+
@classmethod
|
|
142
|
+
def compute(
|
|
143
|
+
cls,
|
|
144
|
+
width: Annotated[int, ConstParam("Field width")],
|
|
145
|
+
value: Annotated[pa.DoubleArray, Param(doc="Value to format")],
|
|
146
|
+
) -> Annotated[pa.StringArray, Returns()]:
|
|
147
|
+
"""Right-align value in field of given width."""
|
|
148
|
+
return pa.array(
|
|
149
|
+
[f"{v:>{width}}" if v is not None else None for v in value.to_pylist()],
|
|
150
|
+
type=pa.string(),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class SmartFormatPrefixFunction(ScalarFunction):
|
|
155
|
+
"""Prepend a prefix string to a formatted double.
|
|
156
|
+
|
|
157
|
+
Overload with str ConstParam.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
class Meta:
|
|
161
|
+
"""Function metadata."""
|
|
162
|
+
|
|
163
|
+
name = "smart_format"
|
|
164
|
+
description = "Prepend prefix to formatted value"
|
|
165
|
+
|
|
166
|
+
@classmethod
|
|
167
|
+
def compute(
|
|
168
|
+
cls,
|
|
169
|
+
prefix: Annotated[str, ConstParam("Prefix string")],
|
|
170
|
+
value: Annotated[pa.DoubleArray, Param(doc="Value to format")],
|
|
171
|
+
) -> Annotated[pa.StringArray, Returns()]:
|
|
172
|
+
"""Prepend prefix to formatted value."""
|
|
173
|
+
return pa.array(
|
|
174
|
+
[f"{prefix}{v}" if v is not None else None for v in value.to_pylist()],
|
|
175
|
+
type=pa.string(),
|
|
176
|
+
)
|