Skip to content

Commit 22cfe08

Browse files
rustyconoverclaude
andcommitted
fix(client): Client.bind accepts secrets and at_unit/at_value
bind() hard-coded secrets=None and had no time-travel parameters, though _make_bind_request supports all three and table_function passes them. A caller discovering a function's output schema could therefore not bind a secret-backed function, nor see a versioned table's historical schema. bind() now takes secrets, at_unit and at_value and forwards them. Found by vgi-polars (time-travel scans skipped filter pushdown when the live bind's schema differed from the versioned one). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent f12422e commit 22cfe08

2 files changed

Lines changed: 46 additions & 1 deletion

File tree

‎tests/test_bind_request_at_clause.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,14 @@
1313

1414
from __future__ import annotations
1515

16+
from types import SimpleNamespace
17+
1618
import pyarrow as pa
1719
import pyarrow.ipc as ipc
20+
import pytest
1821

1922
from vgi.arguments import Arguments
23+
from vgi.client.client import Client
2024
from vgi.invocation import FunctionType
2125
from vgi.protocol import BindRequest, InitRequest
2226
from vgi.table_function import BindParams, ProcessParams, ResolvedSecrets
@@ -108,3 +112,31 @@ def test_process_params_accessor_none_when_no_init_call() -> None:
108112
)
109113
assert params.at_unit is None
110114
assert params.at_value is None
115+
116+
117+
def test_client_bind_forwards_at_clause_and_secrets(monkeypatch: pytest.MonkeyPatch) -> None:
118+
"""``Client.bind`` carries at_unit/at_value and secrets onto its BindRequest, as table_function does."""
119+
captured: list[BindRequest] = []
120+
client = Client("unused-worker")
121+
client._primary = SimpleNamespace(proxy=None) # type: ignore[assignment]
122+
123+
def fake_do_bind(_proxy: object, request: BindRequest, _callback: object) -> object:
124+
captured.append(request)
125+
return SimpleNamespace()
126+
127+
monkeypatch.setattr(client, "_do_bind", fake_do_bind)
128+
client.bind(
129+
function_name="sequence",
130+
schema_path=["main"],
131+
at_unit="VERSION",
132+
at_value="3",
133+
secrets={"token": "s3cr3t"},
134+
)
135+
(request,) = captured
136+
assert (request.at_unit, request.at_value) == ("VERSION", "3")
137+
assert request.secrets is not None
138+
139+
captured.clear()
140+
client.bind(function_name="sequence", schema_path=["main"])
141+
(request,) = captured
142+
assert (request.at_unit, request.at_value, request.secrets) == (None, None, None)

‎vgi/client/client.py‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2757,7 +2757,10 @@ def bind(
27572757
arguments: Arguments | None = None,
27582758
function_type: FunctionType = FunctionType.TABLE,
27592759
settings: dict[str, Any] | None = None,
2760+
secrets: dict[str, Any] | None = None,
27602761
transaction_opaque_data: bytes | None = None,
2762+
at_unit: str | None = None,
2763+
at_value: str | None = None,
27612764
) -> BindResponse:
27622765
"""Resolve a function's bind response without running init()/process().
27632766
@@ -2789,8 +2792,16 @@ def bind(
27892792
settings: Optional dictionary of settings/pragmas — some
27902793
functions' output schema depends on setting values (see
27912794
`Meta.required_settings`).
2795+
secrets: Optional dictionary of secret name to value pairs, as for
2796+
`table_function` — a function whose bind resolves secrets
2797+
(e.g. a secret-backed source) needs them here too.
27922798
transaction_opaque_data: Optional unique identifier for the
27932799
DuckDB transaction.
2800+
at_unit: Optional time travel unit (e.g. 'timestamp', 'version'):
2801+
bind the function as of a past point, as `table_function`'s
2802+
`at_unit` does, so a versioned table's historical schema can be
2803+
discovered. `None` binds live.
2804+
at_value: Optional time travel value, paired with `at_unit`.
27942805
27952806
Returns:
27962807
[`BindResponse`][] with `output_schema` and any opaque bind data —
@@ -2814,8 +2825,10 @@ def bind(
28142825
arguments=arguments,
28152826
function_type=function_type,
28162827
settings=settings,
2817-
secrets=None,
2828+
secrets=secrets,
28182829
transaction_opaque_data=transaction_opaque_data,
2830+
at_unit=at_unit,
2831+
at_value=at_value,
28192832
)
28202833
return self._do_bind(self._primary.proxy, bind_request, None)
28212834
except ClientError as e:

0 commit comments

Comments
 (0)