Skip to content

Commit a698bfa

Browse files
Rusty Conoverclaude
andcommitted
fix(deps): pin the filter engine to the vgi.duckdb.standard.v1 oracle
4cad555 relocked duckdb 1.5.6. The filter evaluator only accepts DuckDB 1.5.5 / Haybarn 1.5.5-rc1 -- the spec pins vgi.duckdb.standard.v1 to the v1.5.5 oracle, "not a reference to the moving 1.5.x release family" -- so in an env without haybarn (the default `uv sync` the vgi integration suite uses) every pushed-down filter failed to bind. vgi-python's own CI masked it: --all-extras installs haybarn, which the engine resolver prefers. 1.5.6 does pass the runtime corpus, but it is not the oracle: it ships newer tzdata (2026c/2026d, observable under vgi.duckdb.session.v1) and changes integer-literal and printf behavior. So keep the gate and make the dependencies honor it instead of widening it: - duckdb/haybarn extras and the dev extra pin ==1.5.5 / ==1.5.5rc1; uv.lock back to duckdb 1.5.5. - The accepted builds are a module constant, STANDARD_V1_ENGINE_VERSIONS; a test keeps the pyproject pins in step with it, and another checks the gate accepts the oracle builds and refuses 1.5.4/1.5.6. - Dependabot ignores duckdb/haybarn: a bump is a profile decision. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 5798baa commit a698bfa

5 files changed

Lines changed: 123 additions & 27 deletions

File tree

‎.github/dependabot.yml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ updates:
2727
# failing on an impossible update; the alerts stay visible in the
2828
# security tab. The real fix is lifting the cap upstream in vgi-rpc.
2929
- dependency-name: "aiohttp"
30+
# The filter evaluator implements vgi.duckdb.standard.v1, pinned to
31+
# DuckDB 1.5.5 / Haybarn 1.5.5-rc1 (vgi/filter_v2.py
32+
# STANDARD_V1_ENGINE_VERSIONS); any other engine build refuses every
33+
# pushed-down filter. Bumping them is a deliberate profile decision, not
34+
# a routine update: locking duckdb 1.5.6 broke filter pushdown in the
35+
# vgi integration suite.
36+
- dependency-name: "duckdb"
37+
- dependency-name: "haybarn"
3038

3139
# GitHub Actions used by the workflows in .github/workflows/.
3240
- package-ecosystem: "github-actions"

‎pyproject.toml‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,14 @@ dependencies = [
4949
# evaluation, the transactor, and the statistics fixtures. It is resolved
5050
# lazily in vgi/_duckdb.py: haybarn is preferred, stock duckdb is the
5151
# fallback. Neither is a hard dependency — install one via these extras.
52-
haybarn = ["haybarn>=1.5.5rc1"]
53-
duckdb = ["duckdb>=1.5.3"]
52+
# Both are pinned EXACTLY: expression-filter evaluation implements
53+
# vgi.duckdb.standard.v1, whose oracle is upstream DuckDB 1.5.5 (Haybarn
54+
# 1.5.5-rc1), and vgi/filter_v2.py refuses any other engine build
55+
# (STANDARD_V1_ENGINE_VERSIONS). A looser range lets a resolver pick e.g.
56+
# duckdb 1.5.6, and every pushed-down filter then fails to bind. Change these
57+
# only together with STANDARD_V1_ENGINE_VERSIONS (a test keeps them in step).
58+
haybarn = ["haybarn==1.5.5rc1"]
59+
duckdb = ["duckdb==1.5.5"]
5460
http = ["vgi-rpc[http]"]
5561
iroh = ["vgi-rpc[http,iroh]"]
5662
# transport="launch": vgi_rpc.launcher imports filelock unconditionally at
@@ -84,7 +90,7 @@ test-fixtures-writable = [
8490
"vgi-python[transactor]",
8591
]
8692
dev = [
87-
"duckdb>=1.5.3",
93+
"duckdb==1.5.5", # pinned with the `duckdb` extra above
8894
"mypy",
8995
"pyarrow-stubs",
9096
# pydoclint is intentionally NOT a project dependency: it pulls

‎tests/test_filter_v2.py‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,3 +696,74 @@ def test_an_unrenderable_filter_raises_rather_than_vanishing() -> None:
696696

697697
with pytest.raises(FilterSQLUnsupportedError, match="no SQL rendering"):
698698
filters.to_sql()
699+
700+
701+
def test_engine_pins_match_standard_v1_oracle() -> None:
702+
"""The duckdb/haybarn extras pin exactly the builds the evaluator accepts.
703+
704+
A looser range let a lock bump resolve duckdb 1.5.6, which the evaluator
705+
(correctly) refuses, so every pushed-down filter failed to bind.
706+
"""
707+
import pathlib
708+
import re
709+
import tomllib
710+
711+
from vgi.filter_v2 import STANDARD_V1_ENGINE_VERSIONS
712+
713+
pyproject = tomllib.loads((pathlib.Path(__file__).parents[1] / "pyproject.toml").read_text())
714+
extras = pyproject["project"]["optional-dependencies"]
715+
for engine in ("duckdb", "haybarn"):
716+
specs = [spec for spec in extras[engine] if re.match(rf"{engine}\b", spec)]
717+
assert len(specs) == 1, specs
718+
match = re.fullmatch(rf"{engine}==(\S+)", specs[0].replace(" ", ""))
719+
assert match, f"{engine} extra must pin an exact version, got {specs[0]!r}"
720+
assert match.group(1) in STANDARD_V1_ENGINE_VERSIONS[engine]
721+
dev_duckdb = [spec for spec in extras["dev"] if re.match(r"duckdb\b", spec)]
722+
assert [spec.replace(" ", "") for spec in dev_duckdb] == list(extras["duckdb"])
723+
724+
725+
@pytest.mark.parametrize(
726+
("engine", "version", "accepted"),
727+
[
728+
("duckdb", "1.5.5", True),
729+
("haybarn", "1.5.5rc1", True),
730+
("duckdb", "1.5.6", False),
731+
("duckdb", "1.5.4", False),
732+
("haybarn", "1.5.6rc1", False),
733+
],
734+
)
735+
def test_standard_v1_evaluator_requires_oracle_engine(
736+
monkeypatch: pytest.MonkeyPatch, engine: str, version: str, accepted: bool
737+
) -> None:
738+
"""Only the pinned oracle builds may evaluate ``vgi.duckdb.standard.v1``."""
739+
import re
740+
import threading
741+
import types
742+
743+
import vgi._duckdb
744+
from vgi import filter_v2
745+
746+
opened: list[object] = []
747+
748+
class _Connection:
749+
def execute(self, *_args: object) -> None:
750+
return None
751+
752+
fake = types.ModuleType(engine)
753+
fake.__version__ = version # type: ignore[attr-defined]
754+
monkeypatch.setattr(vgi._duckdb, "engine_module", lambda: fake)
755+
756+
def _connect(**_kw: object) -> _Connection:
757+
opened.append(1)
758+
return _Connection()
759+
760+
monkeypatch.setattr(vgi._duckdb, "connect", _connect)
761+
monkeypatch.setattr(filter_v2, "_evaluation_local", threading.local())
762+
context = filter_v2.EvaluationContext(profile=filter_v2.NO_EVALUATION_CONTEXT)
763+
if accepted:
764+
filter_v2._get_evaluation_connection(context)
765+
assert opened == [1]
766+
else:
767+
with pytest.raises(filter_v2.FilterV2Error, match=f"found {engine} {re.escape(version)}"):
768+
filter_v2._get_evaluation_connection(context)
769+
assert opened == []

‎uv.lock‎

Lines changed: 19 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vgi/filter_v2.py‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,21 @@
2323
NO_EVALUATION_CONTEXT = "vgi.none.v1"
2424
DUCKDB_SESSION_CONTEXT = "vgi.duckdb.session.v1"
2525

26+
#: Engine builds whose observable SQL behavior *is* the ``vgi.duckdb.standard.v1``
27+
#: oracle, by distribution name -> exact ``__version__`` strings. The profile is
28+
#: pinned to upstream DuckDB v1.5.5 (Haybarn v1.5.5-rc1 is its reference
29+
#: distribution) and is "not a reference to the moving 1.5.x release family"
30+
#: (vgi-filter-encoding-v2-spec.md section 6). A later patch release is not
31+
#: added here merely because the conformance corpus passes: DuckDB 1.5.6, for
32+
#: one, ships newer time-zone data (2026c/2026d) and changes integer-literal and
33+
#: printf behavior, so it is not the oracle. The ``duckdb``/``haybarn`` extras in
34+
#: pyproject.toml pin exactly these versions; tests/test_filter_v2.py keeps the
35+
#: two in step.
36+
STANDARD_V1_ENGINE_VERSIONS: dict[str, frozenset[str]] = {
37+
"duckdb": frozenset({"1.5.5"}),
38+
"haybarn": frozenset({"1.5.5rc1", "1.5.5-rc1"}),
39+
}
40+
2641
MAX_JSON_BYTES = 1 << 20
2742
MAX_DEPTH = 64
2843
MAX_NODES = 10_000
@@ -1168,11 +1183,7 @@ def _get_evaluation_connection(context: EvaluationContext) -> Any:
11681183
engine = engine_module()
11691184
engine_name = engine.__name__.split(".", 1)[0]
11701185
engine_version = str(getattr(engine, "__version__", ""))
1171-
accepted_versions = {
1172-
"duckdb": {"1.5.5"},
1173-
"haybarn": {"1.5.5rc1", "1.5.5-rc1"},
1174-
}
1175-
if engine_version not in accepted_versions.get(engine_name, set()):
1186+
if engine_version not in STANDARD_V1_ENGINE_VERSIONS.get(engine_name, frozenset()):
11761187
raise FilterV2Error(
11771188
f"{DUCKDB_STANDARD_V1} requires DuckDB 1.5.5 or Haybarn 1.5.5-rc1; "
11781189
f"found {engine_name} {engine_version or '<unknown>'}"

0 commit comments

Comments
 (0)