vgi-python 0.48.2__py3-none-any.whl → 0.49.1__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/_test_fixtures/bad_protocol.py +10 -7
- vgi/_test_fixtures/table/splits.py +66 -7
- vgi/argument_spec.py +5 -3
- vgi/arguments.py +12 -5
- vgi/protocol.py +144 -21
- vgi/protocol_version.txt +1 -1
- {vgi_python-0.48.2.dist-info → vgi_python-0.49.1.dist-info}/METADATA +1 -1
- {vgi_python-0.48.2.dist-info → vgi_python-0.49.1.dist-info}/RECORD +11 -11
- {vgi_python-0.48.2.dist-info → vgi_python-0.49.1.dist-info}/WHEEL +0 -0
- {vgi_python-0.48.2.dist-info → vgi_python-0.49.1.dist-info}/entry_points.txt +0 -0
- {vgi_python-0.48.2.dist-info → vgi_python-0.49.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -15,10 +15,11 @@ own ``__dict__``, not an inherited attribute — so the redeclaration on this
|
|
|
15
15
|
subclass's body is what takes effect.
|
|
16
16
|
|
|
17
17
|
A normal client (Python ``Client`` or the C++ extension) declares
|
|
18
|
-
``protocol_version = "1.0
|
|
19
|
-
worker
|
|
20
|
-
boundary raises ``ProtocolVersionError`` with a directional
|
|
21
|
-
|
|
18
|
+
``protocol_version = "3.1.0"`` and sends it on every request. Because this
|
|
19
|
+
worker defaults to ``"99.0.0"``, the major/minor versions differ and the
|
|
20
|
+
dispatch boundary raises ``ProtocolVersionError`` with a directional upgrade
|
|
21
|
+
message that round-trips back to the caller. Tests may override the worker to
|
|
22
|
+
``"3.0.0"`` to exercise the real previous-minor rollout boundary.
|
|
22
23
|
|
|
23
24
|
Otherwise this is a drop-in replacement for ``vgi-fixture-worker``: it
|
|
24
25
|
inherits every function and the catalog from :class:`ExampleWorker`, so any
|
|
@@ -30,15 +31,17 @@ Registered as the ``vgi-fixture-bad-protocol-worker`` entry point.
|
|
|
30
31
|
|
|
31
32
|
from __future__ import annotations
|
|
32
33
|
|
|
34
|
+
import os
|
|
33
35
|
from typing import ClassVar
|
|
34
36
|
|
|
35
37
|
from vgi._test_fixtures.worker import ExampleWorker
|
|
36
38
|
from vgi.protocol import VgiProtocol
|
|
37
39
|
|
|
38
|
-
# A major-version bump guarantees a mismatch against any real client
|
|
39
|
-
#
|
|
40
|
+
# A major-version bump guarantees a mismatch against any real client. Tests can
|
|
41
|
+
# override this with a previous minor (for example 3.0.0 versus 3.1.0) to prove
|
|
42
|
+
# that the production rolling-upgrade boundary is enforced too. Declared on
|
|
40
43
|
# this class body so ``vars(BadProtocol)["protocol_version"]`` resolves to it.
|
|
41
|
-
BAD_PROTOCOL_VERSION = "99.0.0"
|
|
44
|
+
BAD_PROTOCOL_VERSION = os.environ.get("VGI_BAD_PROTOCOL_VERSION", "99.0.0")
|
|
42
45
|
|
|
43
46
|
|
|
44
47
|
class BadProtocol(VgiProtocol):
|
|
@@ -37,7 +37,18 @@ from vgi.arguments import Arg
|
|
|
37
37
|
from vgi.cache_control import CacheControl
|
|
38
38
|
from vgi.filter_v2 import replaying_accepted_state
|
|
39
39
|
from vgi.metadata import FunctionExample, PartitionKind
|
|
40
|
-
from vgi.protocol import
|
|
40
|
+
from vgi.protocol import (
|
|
41
|
+
FieldPath,
|
|
42
|
+
KeyGrouping,
|
|
43
|
+
OrderByDirection,
|
|
44
|
+
OrderByNullOrder,
|
|
45
|
+
PlanResponse,
|
|
46
|
+
RangeKey,
|
|
47
|
+
RangePartitioning,
|
|
48
|
+
ScanSplit,
|
|
49
|
+
TransformSpec,
|
|
50
|
+
VgiOutputCollector,
|
|
51
|
+
)
|
|
41
52
|
from vgi.schema_utils import partition_field, schema
|
|
42
53
|
|
|
43
54
|
if TYPE_CHECKING:
|
|
@@ -858,6 +869,14 @@ class SplitPartitionArgs:
|
|
|
858
869
|
"""Arguments for the partitioned split fixture."""
|
|
859
870
|
|
|
860
871
|
rows_per_country: Annotated[int, Arg("rows_per_country", default=5, doc="Rows in each partition", ge=0)]
|
|
872
|
+
key_mode: Annotated[
|
|
873
|
+
str,
|
|
874
|
+
Arg(
|
|
875
|
+
"key_mode",
|
|
876
|
+
default="valid",
|
|
877
|
+
doc="Test-only Protocol 3.1 key shape: valid, missing, two_rows, or wrong_type",
|
|
878
|
+
),
|
|
879
|
+
]
|
|
861
880
|
|
|
862
881
|
|
|
863
882
|
@bind_fixed_schema
|
|
@@ -889,17 +908,51 @@ class SplitPartitionedFunction(TableFunctionGenerator[SplitPartitionArgs, PartSp
|
|
|
889
908
|
|
|
890
909
|
@classmethod
|
|
891
910
|
def on_plan(cls, params: BindParams[SplitPartitionArgs], request: Any) -> PlanResponse:
|
|
892
|
-
"""One split per country,
|
|
911
|
+
"""One split per country, carrying its exact protocol-3.1 key."""
|
|
912
|
+
|
|
913
|
+
def key_value(country: str) -> pa.RecordBatch | None:
|
|
914
|
+
if params.args.key_mode == "missing":
|
|
915
|
+
return None
|
|
916
|
+
if params.args.key_mode == "two_rows":
|
|
917
|
+
return pa.RecordBatch.from_pydict({"country": [country, country]})
|
|
918
|
+
if params.args.key_mode == "wrong_type":
|
|
919
|
+
return pa.RecordBatch.from_pydict({"country": [1]})
|
|
920
|
+
if params.args.key_mode != "valid":
|
|
921
|
+
msg = f"unknown split_partitioned key_mode: {params.args.key_mode}"
|
|
922
|
+
raise ValueError(msg)
|
|
923
|
+
return pa.RecordBatch.from_pydict({"country": [country]})
|
|
924
|
+
|
|
925
|
+
identity = TransformSpec(
|
|
926
|
+
id="vgi.identity/1",
|
|
927
|
+
inputs=[FieldPath(components=["country"]).serialize_to_bytes()],
|
|
928
|
+
).serialize_to_bytes()
|
|
929
|
+
key_grouping = KeyGrouping(keys=[identity]).serialize_to_bytes()
|
|
930
|
+
range_key = RangeKey(
|
|
931
|
+
transform=identity,
|
|
932
|
+
direction=OrderByDirection.ASC,
|
|
933
|
+
null_order=OrderByNullOrder.NULLS_LAST,
|
|
934
|
+
).serialize_to_bytes()
|
|
935
|
+
range_partitioning = RangePartitioning(
|
|
936
|
+
keys=[range_key],
|
|
937
|
+
# Equality belongs to the range on the right, so these boundaries
|
|
938
|
+
# isolate BR, DE, JP and US into ranges 0 through 3 respectively.
|
|
939
|
+
split_points=pa.RecordBatch.from_pydict({"country": ["DE", "JP", "US"]}),
|
|
940
|
+
).serialize_to_bytes()
|
|
941
|
+
range_index = {"BR": 0, "DE": 1, "JP": 2, "US": 3}
|
|
893
942
|
return PlanResponse(
|
|
894
943
|
splits=[
|
|
895
944
|
ScanSplit(
|
|
896
945
|
payload=country.encode("ascii"),
|
|
897
946
|
estimated_rows=params.args.rows_per_country,
|
|
898
947
|
rows_exact=True,
|
|
948
|
+
key_value=key_value(country),
|
|
949
|
+
range_index=range_index[country],
|
|
899
950
|
)
|
|
900
951
|
for country in _PART_COUNTRIES
|
|
901
952
|
],
|
|
902
953
|
estimated_total_splits=len(_PART_COUNTRIES),
|
|
954
|
+
key_grouping=key_grouping,
|
|
955
|
+
range_partitioning=range_partitioning,
|
|
903
956
|
)
|
|
904
957
|
|
|
905
958
|
@classmethod
|
|
@@ -940,12 +993,18 @@ class SplitPartitionedFunction(TableFunctionGenerator[SplitPartitionArgs, PartSp
|
|
|
940
993
|
# assertion unchanged — the file could not detect the exact failure
|
|
941
994
|
# it exists to detect.
|
|
942
995
|
base = _PART_COUNTRIES.index(country) * 100
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
schema=_PART_ROW,
|
|
947
|
-
)
|
|
996
|
+
batch = pa.RecordBatch.from_pydict(
|
|
997
|
+
{"country": [country] * rows, "sales": [base + i + 1 for i in range(rows)]},
|
|
998
|
+
schema=_PART_ROW,
|
|
948
999
|
)
|
|
1000
|
+
# Bypass the legacy per-batch vgi_partition_values metadata. The
|
|
1001
|
+
# DuckDB adapter must source this split's SINGLE_VALUE partition
|
|
1002
|
+
# tuple from ScanSplit.key_value; if it falls back, the existing
|
|
1003
|
+
# C++ missing-metadata check makes this integration test fail.
|
|
1004
|
+
inner = out
|
|
1005
|
+
while hasattr(inner, "_inner"):
|
|
1006
|
+
inner = inner._inner
|
|
1007
|
+
inner.emit(batch)
|
|
949
1008
|
return
|
|
950
1009
|
|
|
951
1010
|
|
vgi/argument_spec.py
CHANGED
|
@@ -85,7 +85,8 @@ VGI_DOC_KEY = b"vgi_doc"
|
|
|
85
85
|
# vgi_choices — JSON array (the closed set of allowed values)
|
|
86
86
|
# vgi_range — interval notation built from ge/le/gt/lt (e.g. "[0, 100]",
|
|
87
87
|
# "(0, +inf)", "[1, 10)"); a discovery surface, not the raw bounds
|
|
88
|
-
# vgi_pattern — raw regex the value must match (open set)
|
|
88
|
+
# vgi_pattern — raw regex the value must match (open set); evaluated as an
|
|
89
|
+
# unanchored search (JSON Schema `pattern`), anchor with ^ / $
|
|
89
90
|
# Kept byte-for-byte in sync with the C++ reader in the vgi extension
|
|
90
91
|
# (src/vgi_function_arguments_function.cpp / src/include/vgi_protocol_constants.hpp).
|
|
91
92
|
VGI_DEFAULT_KEY = b"vgi_default"
|
|
@@ -144,8 +145,9 @@ class ArgumentSpec:
|
|
|
144
145
|
range_notation: Optional interval-notation string built from the arg's
|
|
145
146
|
numeric bounds (``vgi_range``, e.g. ``"[0, 100]"``); None when
|
|
146
147
|
unbounded.
|
|
147
|
-
pattern: Optional regex the value must match (``vgi_pattern``)
|
|
148
|
-
when there is no pattern
|
|
148
|
+
pattern: Optional regex the value must match (``vgi_pattern``),
|
|
149
|
+
evaluated as an unanchored search; None when there is no pattern
|
|
150
|
+
constraint.
|
|
149
151
|
|
|
150
152
|
Note:
|
|
151
153
|
For named arguments, the Python attribute name (``name``) and the SQL
|
vgi/arguments.py
CHANGED
|
@@ -954,7 +954,9 @@ class Arg[ArgT]:
|
|
|
954
954
|
gt: Value must be > this (for numeric types).
|
|
955
955
|
lt: Value must be < this (for numeric types).
|
|
956
956
|
choices: Value must be one of these options.
|
|
957
|
-
pattern:
|
|
957
|
+
pattern: Regex the value must contain (for strings). Unanchored search
|
|
958
|
+
(JSON Schema ``pattern`` semantics): ``'https?://'`` matches
|
|
959
|
+
anywhere in the value; use ``^`` and ``$`` to anchor.
|
|
958
960
|
|
|
959
961
|
Note:
|
|
960
962
|
For named arguments (string position), the Python attribute name should
|
|
@@ -1038,7 +1040,9 @@ class Arg[ArgT]:
|
|
|
1038
1040
|
gt: Minimum value (exclusive). Value must be > this.
|
|
1039
1041
|
lt: Maximum value (exclusive). Value must be < this.
|
|
1040
1042
|
choices: Allowed values. Value must be one of these.
|
|
1041
|
-
pattern: Regex
|
|
1043
|
+
pattern: Regex for string validation. Evaluated as an unanchored
|
|
1044
|
+
search (``re.search``, JSON Schema ``pattern`` semantics); use
|
|
1045
|
+
``^`` and ``$`` to anchor the whole value.
|
|
1042
1046
|
varargs: If True, collect all remaining positional arguments from this
|
|
1043
1047
|
position onwards. Returns tuple[ArgT, ...]. Requires at least 1 value.
|
|
1044
1048
|
Must be positional (not named).
|
|
@@ -1350,7 +1354,7 @@ class Arg[ArgT]:
|
|
|
1350
1354
|
valid_range=valid_range,
|
|
1351
1355
|
default=self.default,
|
|
1352
1356
|
)
|
|
1353
|
-
if not self._compiled_pattern.
|
|
1357
|
+
if not self._compiled_pattern.search(value):
|
|
1354
1358
|
raise ArgumentValidationError(
|
|
1355
1359
|
f"Argument '{arg_name}' does not match the required pattern.",
|
|
1356
1360
|
arg_name=self._name,
|
|
@@ -1447,7 +1451,7 @@ class Arg[ArgT]:
|
|
|
1447
1451
|
doc=self.doc if self.doc else None,
|
|
1448
1452
|
valid_range=valid_range,
|
|
1449
1453
|
)
|
|
1450
|
-
if not self._compiled_pattern.
|
|
1454
|
+
if not self._compiled_pattern.search(value):
|
|
1451
1455
|
raise ArgumentValidationError(
|
|
1452
1456
|
f"Argument '{arg_name}' element {index} does not match pattern.",
|
|
1453
1457
|
arg_name=self._name,
|
|
@@ -1634,7 +1638,8 @@ class Param:
|
|
|
1634
1638
|
le: Value must be <= this (inclusive upper bound). Advisory on `Param`.
|
|
1635
1639
|
gt: Value must be > this (exclusive lower bound). Advisory on `Param`.
|
|
1636
1640
|
lt: Value must be < this (exclusive upper bound).
|
|
1637
|
-
pattern: Regex the value must match (for string parameters).
|
|
1641
|
+
pattern: Regex the value must match (for string parameters). Unanchored
|
|
1642
|
+
search; use ``^`` and ``$`` to anchor.
|
|
1638
1643
|
|
|
1639
1644
|
Example (ScalarFunction compute() - position inferred):
|
|
1640
1645
|
class AddColumns(ScalarFunction):
|
|
@@ -1698,6 +1703,8 @@ class ConstParam:
|
|
|
1698
1703
|
gt: Value must be > this (exclusive lower bound); enforced at bind.
|
|
1699
1704
|
lt: Value must be < this (exclusive upper bound); enforced at bind.
|
|
1700
1705
|
pattern: Regex the value must match (string params); enforced at bind.
|
|
1706
|
+
Unanchored search (JSON Schema ``pattern``); use ``^`` and ``$`` to
|
|
1707
|
+
anchor.
|
|
1701
1708
|
|
|
1702
1709
|
"""
|
|
1703
1710
|
|
vgi/protocol.py
CHANGED
|
@@ -101,6 +101,8 @@ from vgi.typed_values import TypedValueBatch
|
|
|
101
101
|
|
|
102
102
|
__all__ = [
|
|
103
103
|
"BindRequest",
|
|
104
|
+
"OrderByDirection",
|
|
105
|
+
"OrderByNullOrder",
|
|
104
106
|
"TableFunctionProjectionRequest",
|
|
105
107
|
"TableFunctionProjectionResponse",
|
|
106
108
|
"ClientCapabilities",
|
|
@@ -511,20 +513,12 @@ class TableFunctionStatisticsRequest(ArrowSerializableDataclass):
|
|
|
511
513
|
|
|
512
514
|
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
513
515
|
class PartitionTransform(ArrowSerializableDataclass):
|
|
514
|
-
"""
|
|
515
|
-
|
|
516
|
-
Spark's ``KeyGroupedPartitioning`` needs the transform *expression*, not just the
|
|
517
|
-
partition values: ``country=US`` does not say whether partitions are
|
|
518
|
-
``identity(country)`` or ``bucket(16, user_id)``. This is Iceberg's vocabulary,
|
|
519
|
-
which ``Expressions.bucket(n, col)`` maps onto directly.
|
|
520
|
-
|
|
521
|
-
Attributes:
|
|
522
|
-
column: Name of the partition column in the function's output schema.
|
|
523
|
-
transform: Transform kind — one of ``identity``, ``bucket``, ``truncate``,
|
|
524
|
-
``year``, ``month``, ``day``, ``hour``.
|
|
525
|
-
param: Bucket count for ``bucket`` / width for ``truncate``; ``None`` for
|
|
526
|
-
the rest.
|
|
516
|
+
"""Deprecated protocol-2 partition-transform description.
|
|
527
517
|
|
|
518
|
+
Protocol 3.1 producers should use :class:`TransformSpec` inside
|
|
519
|
+
:class:`KeyGrouping` or :class:`RangePartitioning`. This record remains
|
|
520
|
+
available so existing producers can continue to populate the deprecated
|
|
521
|
+
:attr:`PlanResponse.partitioning` field while they migrate.
|
|
528
522
|
"""
|
|
529
523
|
|
|
530
524
|
column: str
|
|
@@ -532,6 +526,122 @@ class PartitionTransform(ArrowSerializableDataclass):
|
|
|
532
526
|
param: int | None = None
|
|
533
527
|
|
|
534
528
|
|
|
529
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
530
|
+
class FieldPath(ArrowSerializableDataclass):
|
|
531
|
+
"""One raw, component-preserving field reference used by a transform."""
|
|
532
|
+
|
|
533
|
+
components: list[str]
|
|
534
|
+
|
|
535
|
+
def __post_init__(self) -> None:
|
|
536
|
+
if not self.components or any(not component for component in self.components):
|
|
537
|
+
raise ValueError("field path must contain only non-empty components")
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
541
|
+
class TransformSpec(ArrowSerializableDataclass):
|
|
542
|
+
"""A versioned, registry-defined partition transform.
|
|
543
|
+
|
|
544
|
+
``id`` identifies normative semantics (for example ``vgi.identity/1`` or
|
|
545
|
+
``iceberg.bucket/1``). ``inputs`` contains serialized :class:`FieldPath`
|
|
546
|
+
records. ``parameters`` is either absent or a one-row Arrow batch, which
|
|
547
|
+
permits any number of named, typed parameters without admitting arbitrary
|
|
548
|
+
recursive expressions.
|
|
549
|
+
"""
|
|
550
|
+
|
|
551
|
+
id: str
|
|
552
|
+
inputs: Annotated[list[bytes], ArrowType(pa.list_(pa.binary()))] = field(default_factory=list)
|
|
553
|
+
parameters: Annotated[pa.RecordBatch | None, ArrowType(pa.binary())] = None
|
|
554
|
+
|
|
555
|
+
def __post_init__(self) -> None:
|
|
556
|
+
if not self.id or "/" not in self.id:
|
|
557
|
+
raise ValueError("transform id must be a non-empty, versioned name")
|
|
558
|
+
if not self.inputs:
|
|
559
|
+
raise ValueError("transform must reference at least one input field")
|
|
560
|
+
for value in self.inputs:
|
|
561
|
+
FieldPath.deserialize_from_bytes(value)
|
|
562
|
+
if self.parameters is not None and self.parameters.num_rows != 1:
|
|
563
|
+
raise ValueError("transform parameters must contain exactly one row")
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
567
|
+
class KeyGrouping(ArrowSerializableDataclass):
|
|
568
|
+
"""Transforms whose exact result tuple is constant within every split."""
|
|
569
|
+
|
|
570
|
+
keys: Annotated[list[bytes], ArrowType(pa.list_(pa.binary()))]
|
|
571
|
+
|
|
572
|
+
def __post_init__(self) -> None:
|
|
573
|
+
if not self.keys:
|
|
574
|
+
raise ValueError("key grouping must contain at least one transform")
|
|
575
|
+
for value in self.keys:
|
|
576
|
+
TransformSpec.deserialize_from_bytes(value)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
580
|
+
class RangeKey(ArrowSerializableDataclass):
|
|
581
|
+
"""One transform and comparator component of a range key."""
|
|
582
|
+
|
|
583
|
+
transform: Annotated[bytes, ArrowType(pa.binary())]
|
|
584
|
+
direction: OrderByDirection
|
|
585
|
+
null_order: OrderByNullOrder
|
|
586
|
+
|
|
587
|
+
def __post_init__(self) -> None:
|
|
588
|
+
TransformSpec.deserialize_from_bytes(self.transform)
|
|
589
|
+
OrderByDirection(self.direction)
|
|
590
|
+
OrderByNullOrder(self.null_order)
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
594
|
+
class RangePartitioning(ArrowSerializableDataclass):
|
|
595
|
+
"""Exact half-open range distribution shared by all splits in a plan.
|
|
596
|
+
|
|
597
|
+
``N`` split-point rows define ``N + 1`` ranges. Values equal to point ``i``
|
|
598
|
+
belong to range ``i + 1``. Each split names its range with
|
|
599
|
+
:attr:`ScanSplit.range_index`.
|
|
600
|
+
"""
|
|
601
|
+
|
|
602
|
+
keys: Annotated[list[bytes], ArrowType(pa.list_(pa.binary()))]
|
|
603
|
+
split_points: Annotated[pa.RecordBatch, ArrowType(pa.binary())]
|
|
604
|
+
|
|
605
|
+
def __post_init__(self) -> None:
|
|
606
|
+
if not self.keys:
|
|
607
|
+
raise ValueError("range partitioning must contain at least one key")
|
|
608
|
+
decoded_keys = [RangeKey.deserialize_from_bytes(value) for value in self.keys]
|
|
609
|
+
if self.split_points.num_columns != len(self.keys):
|
|
610
|
+
raise ValueError("range split-point width must match the range key width")
|
|
611
|
+
for row in range(1, self.split_points.num_rows):
|
|
612
|
+
if _compare_range_rows(self.split_points, row - 1, row, decoded_keys) >= 0:
|
|
613
|
+
raise ValueError("range split points must be strictly ordered by the declared comparator")
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def _compare_range_rows(
|
|
617
|
+
batch: pa.RecordBatch,
|
|
618
|
+
left_row: int,
|
|
619
|
+
right_row: int,
|
|
620
|
+
keys: list[RangeKey],
|
|
621
|
+
) -> int:
|
|
622
|
+
"""Compare two split-point rows using the protocol's lexicographic order."""
|
|
623
|
+
for column, key in zip(batch.columns, keys, strict=True):
|
|
624
|
+
left = column[left_row]
|
|
625
|
+
right = column[right_row]
|
|
626
|
+
if not left.is_valid or not right.is_valid:
|
|
627
|
+
if not left.is_valid and not right.is_valid:
|
|
628
|
+
continue
|
|
629
|
+
left_first = key.null_order == OrderByNullOrder.NULLS_FIRST
|
|
630
|
+
return (-1 if left_first else 1) if not left.is_valid else (1 if left_first else -1)
|
|
631
|
+
try:
|
|
632
|
+
if pc.equal(left, right).as_py() is True:
|
|
633
|
+
continue
|
|
634
|
+
less = pc.less(left, right).as_py() is True
|
|
635
|
+
greater = pc.greater(left, right).as_py() is True
|
|
636
|
+
except (pa.ArrowInvalid, pa.ArrowNotImplementedError, TypeError) as error:
|
|
637
|
+
raise ValueError("range split points contain values that cannot be ordered") from error
|
|
638
|
+
if not less and not greater:
|
|
639
|
+
raise ValueError("range split points contain unordered values")
|
|
640
|
+
result = -1 if less else 1
|
|
641
|
+
return -result if key.direction == OrderByDirection.DESC else result
|
|
642
|
+
return 0
|
|
643
|
+
|
|
644
|
+
|
|
535
645
|
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
536
646
|
class SortField(ArrowSerializableDataclass):
|
|
537
647
|
"""Ordering guaranteed *within* each split.
|
|
@@ -578,10 +688,15 @@ class ScanSplit(ArrowSerializableDataclass):
|
|
|
578
688
|
estimated_bytes: Byte estimate. Load-bearing for engines that bin-pack
|
|
579
689
|
(DataFusion weight, Trino SplitWeight); ``None`` degrades them to
|
|
580
690
|
round-robin by count. DuckDB claims greedily and does not need it.
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
691
|
+
key_value: One-row Arrow batch aligned with ``PlanResponse.key_grouping``.
|
|
692
|
+
Every row emitted by this split evaluates to this exact transformed
|
|
693
|
+
tuple under null-safe equality.
|
|
694
|
+
range_index: Zero-based range owned by this split when
|
|
695
|
+
``PlanResponse.range_partitioning`` is present.
|
|
696
|
+
column_bounds: 2-row (min, max) Arrow batch used only for containment and
|
|
697
|
+
pruning. It is not an exact grouping key or a range assignment.
|
|
698
|
+
partition_bounds: Deprecated compatibility spelling for older protocol-2
|
|
699
|
+
producers. New protocol-3 producers use ``column_bounds``.
|
|
585
700
|
column_statistics: Per-column statistics blob (same encoding as
|
|
586
701
|
``table_function_statistics``).
|
|
587
702
|
location_ids: Indices into ``PlanResponse.locations`` naming hosts where
|
|
@@ -603,6 +718,9 @@ class ScanSplit(ArrowSerializableDataclass):
|
|
|
603
718
|
estimated_rows: int | None = None
|
|
604
719
|
rows_exact: bool = False
|
|
605
720
|
estimated_bytes: int | None = None
|
|
721
|
+
key_value: Annotated[pa.RecordBatch | None, ArrowType(pa.binary())] = None
|
|
722
|
+
range_index: int | None = None
|
|
723
|
+
column_bounds: Annotated[pa.RecordBatch | None, ArrowType(pa.binary())] = None
|
|
606
724
|
partition_bounds: Annotated[pa.RecordBatch | None, ArrowType(pa.binary())] = None
|
|
607
725
|
column_statistics: Annotated[bytes | None, ArrowType(pa.binary())] = None
|
|
608
726
|
location_ids: list[int] | None = None
|
|
@@ -720,9 +838,12 @@ class PlanResponse(ArrowSerializableDataclass):
|
|
|
720
838
|
``transaction`` (reuses the CACHE_SCOPE_* vocabulary). Transaction-scoped
|
|
721
839
|
plans are not cacheable and are not redeemable after commit or rollback.
|
|
722
840
|
locations: Hoisted host list; splits index into it via ``location_ids``.
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
841
|
+
key_grouping: Serialized :class:`KeyGrouping` when each split has one
|
|
842
|
+
exact transformed key tuple.
|
|
843
|
+
range_partitioning: Serialized :class:`RangePartitioning` containing the
|
|
844
|
+
exact ordered split points and comparator.
|
|
845
|
+
partitioning: Deprecated protocol-2 partition-transform list. Protocol-3
|
|
846
|
+
producers use ``key_grouping`` and/or ``range_partitioning``.
|
|
726
847
|
sort_order: Serialized [`SortField`][] records describing ordering *within*
|
|
727
848
|
each split.
|
|
728
849
|
cache_max_age_seconds: How long this plan may be reused. Ignored for
|
|
@@ -757,6 +878,8 @@ class PlanResponse(ArrowSerializableDataclass):
|
|
|
757
878
|
scope: str = CACHE_SCOPE_CATALOG
|
|
758
879
|
|
|
759
880
|
locations: list[str] | None = None
|
|
881
|
+
key_grouping: Annotated[bytes | None, ArrowType(pa.binary())] = None
|
|
882
|
+
range_partitioning: Annotated[bytes | None, ArrowType(pa.binary())] = None
|
|
760
883
|
partitioning: Annotated[list[bytes] | None, ArrowType(pa.list_(pa.binary()))] = None
|
|
761
884
|
sort_order: Annotated[list[bytes] | None, ArrowType(pa.list_(pa.binary()))] = None
|
|
762
885
|
|
|
@@ -3483,7 +3606,7 @@ class VgiProtocol(Protocol):
|
|
|
3483
3606
|
# ``VgiProtocol``, Java and C# ``VgiService``, Go the framework default
|
|
3484
3607
|
# ``Service``, TypeScript ``vgi`` -- so no client could address them all.
|
|
3485
3608
|
protocol_name: ClassVar[str] = "vgi.v3"
|
|
3486
|
-
protocol_version: ClassVar[str] = "3.
|
|
3609
|
+
protocol_version: ClassVar[str] = "3.1.0"
|
|
3487
3610
|
|
|
3488
3611
|
def bind(self, request: BindRequest) -> BindResponse:
|
|
3489
3612
|
"""Resolve output schema and validate arguments."""
|
vgi/protocol_version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vgi-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.49.1
|
|
4
4
|
Summary: Vector Gateway Interface - Connect DuckDB to external programs via Apache Arrow
|
|
5
5
|
Project-URL: Homepage, https://query.farm
|
|
6
6
|
Project-URL: Repository, https://github.com/Query-farm/vgi-python
|
|
@@ -3,8 +3,8 @@ vgi/_duckdb.py,sha256=Tu8Yb4vABRv4yOMo7qul8LxG_wH5ZqZuOCSSjyiMgbY,2198
|
|
|
3
3
|
vgi/_redact.py,sha256=i3p_9huXsNRVL3a7MsWKlPvX8-YwTQMtF-j5obNEJGA,1470
|
|
4
4
|
vgi/_storage_profile.py,sha256=VkTsXojuE0tHEzurmteQSAiL1vI3CZSgYkL6D_h8GvE,5061
|
|
5
5
|
vgi/aggregate_function.py,sha256=NYxudbvQ9ZLkP2w5hRdd4YgsGUZ_0cVfqqXnlmJPfls,27389
|
|
6
|
-
vgi/argument_spec.py,sha256=
|
|
7
|
-
vgi/arguments.py,sha256=
|
|
6
|
+
vgi/argument_spec.py,sha256=5VqXDuHFcvOVwf9PQPCH-Xe-ya0TL4ticlVmcNmxCR4,34374
|
|
7
|
+
vgi/arguments.py,sha256=frv3cmUaHFnUfsmhJMLib8ivaVkn9W3M2BQd76hlUSs,76548
|
|
8
8
|
vgi/attach_capabilities.py,sha256=udeIRYQWdyhdnTgGuBNGoedNRi8_9Yvh5JKZXuCqYAA,4225
|
|
9
9
|
vgi/attach_header.py,sha256=sxpzCOVa5fl_-kQSCZgeUxS5I9Kuv8VCUjAOF5s1pr0,3782
|
|
10
10
|
vgi/attach_ticket.py,sha256=7ycf7HGFpZ6SUqCKwpRmhg6m4yk2CDbJzcFRSBMODsk,28243
|
|
@@ -27,8 +27,8 @@ vgi/meta_worker.py,sha256=QctITX8rP2qv_3pTCqIgqhoC8zQ26U82LC7fov8Huog,38641
|
|
|
27
27
|
vgi/metadata.py,sha256=nk0tE5w0mlOA_5sVSCNJTWC09bcZkjXYnvEziGguYc8,77829
|
|
28
28
|
vgi/otel.py,sha256=JlUDnk2c0UXSo_j1PJdS67Y--t55sXPg054ADuvHANc,15452
|
|
29
29
|
vgi/profiling.py,sha256=rK7vij3oHhXC3JHI2BsMySsXh7_hrXi0aXpCW8iFSIc,3671
|
|
30
|
-
vgi/protocol.py,sha256=
|
|
31
|
-
vgi/protocol_version.txt,sha256=
|
|
30
|
+
vgi/protocol.py,sha256=uBqWXxPM85er7xm8Owi_41mG5YdcL3jV1zJOylWawX8,194395
|
|
31
|
+
vgi/protocol_version.txt,sha256=svRNO24p-LG3PqRzXwBq_8TRmOH9nH1Q5zYVmx72NsY,6
|
|
32
32
|
vgi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
vgi/query_execution.py,sha256=zsaRAKDsaoq4wvavHzeokBICAo3CCid86WDJVY04zxc,21353
|
|
34
34
|
vgi/query_parameters.py,sha256=nnMH_SnF_fAh4y0mI87aAQdl3G4JqP2NibEcFX5pijI,7926
|
|
@@ -52,7 +52,7 @@ vgi/_test_fixtures/__init__.py,sha256=xQq-QQLk8USQ6TZHsPiPcZldNNUdcJ2gToXMPGzScL
|
|
|
52
52
|
vgi/_test_fixtures/attach_options.py,sha256=mfzHW44G1hw5gazuAMwVHgp3VTRCXBifhWszMs7Tndc,12636
|
|
53
53
|
vgi/_test_fixtures/attach_ticket_vectors.json,sha256=Ny0U9Cyn-_2G-ZYlC3ZpeG6bCux_bhtJZXGanti47fw,143027
|
|
54
54
|
vgi/_test_fixtures/bad_enum.py,sha256=b8l-afmXgGR6iqgIUiVeqHu0C0F8Of8ahEmdQGtqd1g,2916
|
|
55
|
-
vgi/_test_fixtures/bad_protocol.py,sha256=
|
|
55
|
+
vgi/_test_fixtures/bad_protocol.py,sha256=nTlxgMc921CLZHKPhyieBvk5Pd-w_2j40arrW4YSy-Y,3228
|
|
56
56
|
vgi/_test_fixtures/cancellable.py,sha256=hetTRLyT4kkazPNcxAdfzj0pYzEmegMksamJbR0w3Ho,12151
|
|
57
57
|
vgi/_test_fixtures/catalog.py,sha256=Klz1BwU6iD9qa9k0Z0yZH-r1B95o-qCyx0jLvbSxzyM,31089
|
|
58
58
|
vgi/_test_fixtures/catalog_contents.py,sha256=FTX-UtBBIGxFvhSYe4ZLryL-yM6ULK7dmkk0aAdEkbw,12094
|
|
@@ -127,7 +127,7 @@ vgi/_test_fixtures/table/same_name_cached.py,sha256=JMXSXTcqyO7XwWqIizgddlTvLYuU
|
|
|
127
127
|
vgi/_test_fixtures/table/same_name_schemas.py,sha256=yRti9HXO3-Y9ahiLIHoaZ31LvWjtPSbjxmPJyoQ_0mQ,4930
|
|
128
128
|
vgi/_test_fixtures/table/sequence.py,sha256=0kOckCwRu19RMKgFrUcnnnDR8e0utjX_E2kD3w6NnRU,26352
|
|
129
129
|
vgi/_test_fixtures/table/settings.py,sha256=C4J5OKY3vgNbB09QAiQlzwhI3Tc5wtewHHLFhO5xvo4,16689
|
|
130
|
-
vgi/_test_fixtures/table/splits.py,sha256=
|
|
130
|
+
vgi/_test_fixtures/table/splits.py,sha256=Vc9whd_xJjR3Gi61JDV9dzvC86xvxD9miXwGAkHzTGQ,51415
|
|
131
131
|
vgi/_test_fixtures/table/transaction_storage.py,sha256=cGSaM25cE6brwbUJ0-mchocX0UfwOb7uscCATKLYeME,6086
|
|
132
132
|
vgi/_test_fixtures/table/tt_pushdown.py,sha256=jMELJ2HkG5-yIuYqKRYaPSouJxs3N-tiWRPthoPZ9ko,7420
|
|
133
133
|
vgi/_test_fixtures/table/typed_probe.py,sha256=JjgLeHuhkXAVnCcezgqR7ayXMp3tTTixegftCwjKOSY,5122
|
|
@@ -206,8 +206,8 @@ vgi/transactor/_duckdb_compat.py,sha256=sXVZ9JLKAQyGR1BjWczSwdQEavtr-TcZPoVZZnTr
|
|
|
206
206
|
vgi/transactor/client.py,sha256=7DTeMksogsw6ANjQjGOPpKYrV76rg4_kGjktMJf54jg,4486
|
|
207
207
|
vgi/transactor/protocol.py,sha256=koHi8ZH_QvZRBjFI_m_o1SpbcilBv_wAjmaGq0k61BY,5201
|
|
208
208
|
vgi/transactor/server.py,sha256=fJi72oaS8PiOI3rFW0MdetGBFEzFBkLdlDAMmbB50Uw,32663
|
|
209
|
-
vgi_python-0.
|
|
210
|
-
vgi_python-0.
|
|
211
|
-
vgi_python-0.
|
|
212
|
-
vgi_python-0.
|
|
213
|
-
vgi_python-0.
|
|
209
|
+
vgi_python-0.49.1.dist-info/METADATA,sha256=NrvfU1Jq1e1Jw_3rpF7eIQ9ZvI_MrwbyBxoeYxlMIJ8,26066
|
|
210
|
+
vgi_python-0.49.1.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
211
|
+
vgi_python-0.49.1.dist-info/entry_points.txt,sha256=3Kz1vgodw3pOL_xjtSyDB55-ZRy-U2X-X_Bdr582x0Q,165
|
|
212
|
+
vgi_python-0.49.1.dist-info/licenses/LICENSE,sha256=pbJb4zZasP6n5ifEV81wFu017TarjydaYVmGbHcehtY,6103
|
|
213
|
+
vgi_python-0.49.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|