Repository navigation
fix(duckdb): run on DuckDB 2.x as well as 1.5.x - #740
Merged
Merged
Conversation
…test.py leaks tests/cli/conftest.py sets FLUID_LOG_LEVEL=DEBUG, and some xdist workers keep it. The CLI subprocesses here inherited it, so the loader's DEBUG copy of a warning the command reports was printed too. The tests that count the warning once then failed with `assert 2 == 1`, as #734's TestValidateStrict did on main's CI run for 1320619. The subprocesses now drop FLUID_LOG_LEVEL, FLUID_DEBUG and FLUID_SHOW_REGISTRATION, as tests/output_ports already do, with API keys as before. Same change as #735's 57abff3, so #735 rebases onto it cleanly.
DuckDB 2.0.0 (2026-10-21) turned main red: 51 tests across Test 3.11-3.14,
Coverage and the DuckDB e2e legs. Python 3.10 stayed green because DuckDB 2.0
needs 3.11, so pip resolves 1.5.6 there. The floor stays duckdb>=1.5.0 and no
ceiling is added; one small seam, providers/_duckdb_compat.py (the shape of
fluid_build/_mcp_compat.py), holds what differs.
- Relations have no fetchone() in 2.0, and a con.execute() result streams and
owns its connection until read to the end: after fetchone() on a one-row
COUNT(*), the next relation read, register or create_function raises
ResourceInUseException. first_row() reads to the end on both versions; all
18 DuckDB fetchone sites use it (verify, local provider, contract_tests,
duckdb runner, late-arrival split, embedded-SQL parse, local validation,
diff live, verify masking). An AST fence in tests/providers/test_duckdb_compat.py
refuses fetchone/fetchmany/fetch_df_chunk/fetch_arrow_table/fetch_record_batch
anywhere in fluid_build except modules of other database clients.
- A database file 2.0 creates uses storage version 69, which 1.5.6 cannot open.
secure_duckdb_connect now passes storage_compatibility_version='v1.5.0' at
connect on 2.x only (a SET after open is too late for the main file), so the
files forge-cli creates stay readable by every supported DuckDB; a file that
exists keeps its format, a caller's own setting wins, and the setting is
locked with the rest. 1.5.x behaviour is unchanged.
- 2.0's sqlite scanner goes through DuckDB's file system, so the sandbox
allowlist now bounds it (outside file, ./.., symlink and ATTACH refused). The
pin that documents 1.5's unbounded scanner runs on 1.5 only, and a new one
pins 2.0's bound. The engine still keeps contract SQL off any connection
that loads a scanner (postgres stays unbounded).
Sources: duckdb-python v2.0.0 release notes ("Upgrading from 1.x");
https://duckdb.org/docs/2.0/clients/python/migration.html;
https://duckdb.org/docs/2.0/operations_manual/migration.html;
narwhals-dev/narwhals#4078 (the fetchall pattern).
On DuckDB 2.0, con.sql() of SQL whose last statement returns no rows (CREATE TABLE ... AS, an INSERT without RETURNING) returns a relation over the row count where 1.5 returned None. A local SQL action or contract_tests action with such SQL then wrote a one-row "Count" file as its output and reported ok; on 1.5 it failed on 'NoneType' object has no attribute 'write_csv'. query_relation() in providers/_duckdb_compat.py restores 1.5's answer on 2.x (by the last statement's type, then by the Count/Success shape for DML), and both call sites now fail with a message saying to end the SQL with a SELECT. Found by two read-only audits of fluid_build against the 2.0 change list. Also from those audits: - providers/local/mocks.py: DuckDB 2.0 refuses CREATE SCHEMA information_schema (a system catalog); on 1.5 it was a no-op. Dropped. - cli/verify.py closes its DuckDB connection when reading the file fails. - confine_declared's docstring no longer says the sqlite scanner is unbounded on every version. - New tests pin what the fetchone fix restored but nothing asserted: a local SQL action's row count and a materialize step that stays "materialize" (on 2.0 it fell back to a skipped noop after writing its file). - tests/test_contract_tests_branches.py's mock pinned the 1.x fetchone call.
A verification sweep ran the port against duckdb 1.5.6 and 2.0.0, on
Python 3.10, 3.12 and 3.14: sandbox attack probes, about 900 live fluid CLI
runs on the 13 quickstart templates and the local examples, a CI-job replay,
mutation testing, a drift diff and concurrency probes. Its findings that
the port or DuckDB 2.0 caused, fixed:
- is_unloaded_function: DuckDB 2.0 no longer names postgres_scan's
extension ("Table Function with name postgres_scan does not exist!"), so
contract SQL calling it got a bare catalog error instead of the sandbox
hint; mysql_scan never had one. Both read as a refusal now. Access was
never affected.
- MCP DuckDBDriver.health_check ran on the shared connection; on 2.0 a
second thread there refuses ("connection is busy") instead of waiting.
It takes a cursor, as execute() already does. A concurrency test pins
both.
- The partial-read fence also catches rel.arrow(), fetch_arrow_reader(),
record_batch() and a relation's removed execute(), and fails when it
scans nothing (a mutant pointing it at an empty tree survived).
- Tests for what mutants showed unpinned: first_row returns the first of
several rows; contract_tests' dry run counts a real relation; the
duckdb runner's remote-landing probe leaves its connection usable.
- CHANGELOG: `fluid demo` (not `fluid init`) creates .fluid/db.duckdb; the
pinned format opens on DuckDB 1.5.0 and later; dbt-duckdb and dlt files
follow the DuckDB that writes them; contract SQL changed in 2.0 (the
`->` lambda, integer // and % by zero).
Seen in a live `fluid apply --mode amend-and-build` of a quickstart whose build SQL was a CREATE TABLE ... AS: under the build's heading the error said "SQL action 0 ... also set output_table". A contract author writes neither actions nor output_table. Both call sites now print one message from _duckdb_compat.NO_ROWS_TO_WRITE that ends with a working example (CREATE TABLE t AS SELECT ...; SELECT * FROM t), and a test runs that example through local apply on both DuckDB versions.
pyproject.toml takes duckdb>=1.5.0 with no ceiling, and DuckDB 2.0 needs Python 3.11, so 1.5.x ran only on the Python 3.10 test leg, which resolves the newest 1.5 patch (1.5.6), never the declared floor; and Python 3.10 reached end of life in October 2026. The job installs exactly the floor the sandbox enforces (MIN_DUCKDB_VERSION, read from the code, so the two cannot drift) on Python 3.12, asserts it is the version that runs, and runs the compat seam, sandbox, MCP DuckDB driver and local end-to-end tests.
…uckDB 2.0
DuckDB 2.0's postgres extension dropped postgres_scan(dsn, schema, table), so
a DuckDB acquisition build reading Postgres failed on every Python 3.11+
install ("Table Function with name postgres_scan does not exist!"); main
fails the same. Found by the documentation dimension of the world-class
inspection, then reproduced live against a local Postgres 16 container.
The runner now attaches the source before the sandbox's lock, as it attaches
MySQL and SQLite sources, and reads <alias>.<schema>.<table>:
- ATTACH '<libpq dsn>' AS postgres_<build> (TYPE postgres, SECRET
postgres_<build>, READ_ONLY). READ_ONLY: postgres_execute and INSERT on the
alias are refused (probed on 1.5.6 and 2.0.0), so nothing on the
connection can write to the source.
- An empty CREATE TEMPORARY SECRET named in the ATTACH: without one, the
extension looks for a default secret in every storage, and the persistent
storage the sandbox turns off answers "Unknown secret storage found:
'local_file'" on 1.5.0, 1.5.6 and 2.0.0 alike. The DSN carries the
credentials and stays authoritative (a wrong password is refused).
- connection.secretRef is resolved into the password, as the MySQL branch
already did; the shared DSN builder still refuses control characters and a
non-numeric port.
- A connect error is raised as _SourceAttachError with the password masked
(one helper for the postgres and mysql attaches), and _execute turns it into
a FAILED run with a run record, so `fluid apply` reports "duckdb build ...
failed: source: ..." instead of "Unexpected error during execution".
Live proof, examples/source-aligned-postgres-duckdb through the fluid CLI
against postgres:16-alpine: validate, apply --mode amend-and-build (5 rows)
and verify --strict pass on duckdb 2.0.0, 1.5.6 and 1.5.0; on main 2.0.0 the
apply fails as above. TestDuckdbPostgresLive passes on 3.10/1.5.6,
3.12/1.5.0, 3.12/1.5.6, 3.12/2.0.0 and 3.14/2.0.0. A wrong password fails the
build with the password masked in the output, logs and run record. A new
offline test runs the runner against an unreachable server and asserts a
connection failure (it fails on 2.0 with the old postgres_scan read).
The example's README now uses `fluid apply --mode amend-and-build
--build-id`, which replaced `--build`.
Reference: https://duckdb.org/docs/2.0/core_extensions/postgres/overview.html
(ATTACH, postgres_query and postgres_execute; no postgres_scan).
- Strict mypy (CI's typecheck-strict job) failed on cli/_verify_masking.py: first_row() is Optional. New one_row() raises on an empty result and is used where an aggregate's row is indexed straight away (verify, verify masking, local provider, contract_tests, late-arrival counts). - query_relation() still let two DuckDB 2.0 shapes through as results: a CREATE TABLE ... AS PIVOT (typed MULTI, it returns a Success flag) and SQL ending in DML after a query (2.0's con.sql() returns the earlier query's rows). On 2.x every statement but the last now runs first and the last one alone decides; MULTI is judged by its shape. On 1.5 it is con.sql itself, the reference. Parametrized cases for DML, ALTER, SET, DROP and BEGIN...COMMIT endings, and the local apply refuses all three shapes. - The no-rows message said "CREATE TABLE t AS ...; SELECT * FROM t", which fails silently when the planner also sets output_table (its CTAS wrap cannot hold several statements); it now says to write just the SELECT. - The storage pin is v1.0.0, the format DuckDB 1.5 itself writes (storage version 64, which DuckDB 1.0 and later open), not v1.5.0 (version 68, which 1.4 refuses and which accepts 2.0-only types such as VARIANT that 1.5 refuses). A byte-level test pins header version 64 on both DuckDB versions. - Reverted the is_unloaded_function broadening of 9c29f7d: postgres_scan is gone from DuckDB 2.0's extension, not unadvertised, and mysql_scan never existed, so "does not exist" there is not a refusal. A 2.0 test pins that postgres_scan is absent even with the extension loaded. - The sandbox's scanner bullet is version-qualified with what was replayed on 1.5.0, 1.5.6 and 2.0.0: through 1.5 postgres_scan reaches any host and the sqlite scanner any file; 2.0 has no postgres_scan and bounds sqlite; on both an ATTACH of postgres or mysql after the lock is refused, so postgres_query / mysql_query reach only what the engine attached. The postgres pin runs on 1.5 only, and a new pin covers the refused ATTACH. - The partial-read fence also flags an execute() of a query whose result is thrown away (local_validation's SELECT 1 probe now reads it), and fails if an exempt module starts using DuckDB. The MagicMock dry-run test that mirrored first_row's internals is gone; a real-relation test covers it. - contract_tests' docstring no longer says CREATE TABLE AS SQL is accepted. - CHANGELOG: the legacy module is named fluid_build.contract_tests; the Postgres fix; the conversion recipe (COPY FROM DATABASE on 2.x, verified readable by 1.5.6); files forge-cli 0.21.0 wrote on DuckDB 2.0.
A mutation pass on the round-2 code showed _NO_ROWS_STATEMENTS redundant: once the last statement runs alone, every DDL, SET, ATTACH, LOAD and transaction statement returns a Count or Success shape on DuckDB 2.0 (probed for 27 statement kinds), so the set never changed an answer. It is gone; a query type (SELECT, EXPLAIN, CALL, PRAGMA) keeps its relation, as DuckDB 1.5 does even for CHECKPOINT's Success flag (now a test case), and anything else is judged by its shape. The same pass showed no test of a postgres source that cannot connect and has no password: the unreachable-source test now runs with and without one, so an attach error that escapes _execute as a raw exception fails it.
…fail the run Found by the defect scan's recon, then reproduced live against MySQL 8.4: every DuckDB acquisition build reading MySQL or MariaDB failed before it connected, on DuckDB 1.5.6 and 2.0.0 alike (main too), with "Unknown secret storage found: 'local_file'". Without a secret named in the ATTACH, the mysql extension looks for a default one in every secret storage, and the sandbox turns the persistent one off; the postgres attach hit the same and got an empty temporary secret in bd71a01. The mysql attach now gets one too (_empty_secret_sql, shared), and is READ_ONLY like the postgres one: CREATE and mysql_execute on the alias are refused (probed live). A connection block the DSN builder refuses, or a secretRef that does not resolve, raised a bare ValueError out of the connect, which `fluid apply` printed as "Unexpected error during execution" with no run record. It is now a _SourceSetupError (a _SourceAttachError and still a ValueError), so the run fails with the reason; an extension that did not load raises _SourceAttachError too. The messages name the field or the reference, never a value. TestDuckdbMysqlLive and TestDuckdbPostgresLive pass on 3.10/1.5.6, 3.12/1.5.0, 3.12/1.5.6, 3.12/2.0.0 and 3.14/2.0.0 (local mysql:8 and postgres:16-alpine). The offline unreachable-source test now runs for postgres and mysql, with and without a password, and asserts each extension's own connection failure: against main's code it fails 4/4 on 2.0.0 and the 2 mysql cases on 1.5.6.
- A secretRef whose backend fails (vault, aws, gcp, azure or file raise
ConfigurationError, not ValueError) escaped the runner, so `fluid apply`
ended with no run record and skipped its later builds. Any error resolving
the secretRef now fails the source setup like a bad port or an unset
variable does. A new end-to-end test checks exit 1 and a failed run record
for a bad port, an unset env:// variable and a file-backend refusal, on
postgres and mysql; narrowing the catch back to ValueError fails 4 tests.
- _postgres_attach_sql's docstring said READ_ONLY refuses postgres_execute.
It does not: inside an explicit transaction, postgres_execute('COMMIT')
and then a CREATE TABLE wrote to a live Postgres 16 on DuckDB 1.5.6 and
2.0.0. It now says READ_ONLY refuses writes through the attached catalog,
and that only the engine's own SQL runs on that connection.
- The duckdb-floor job retries its installs as the other jobs do, and also
runs the DuckDB runner's tests and every suite the DuckDB end-to-end job
runs (quickstarts, examples, the local happy path). The new
tests/test_ci_duckdb_floor.py fails when the end-to-end job runs a file
the floor job does not, or when a test that reads the DuckDB version is
missing from it. On duckdb 1.5.0 the list gives 455 passed; the 2 failures
are the macOS-only /var tests that fail the same on main.
- VARIANT is not a 2.0-only type (DuckDB 1.5.0 has it): the CHANGELOG and
the storage-pin comment now say a VARIANT column needs storage v1.5.0 and
is refused in the pinned format on 1.5 and 2.x alike.
- CHANGELOG: only a MySQL or MariaDB source used to end `fluid apply` with
"Unexpected error during execution"; a Postgres source already failed the
build.
- Two sandbox test docstrings no longer say the sqlite scanner is never
bounded and that postgres stays unbounded.
Postgres sources (regressions of bd71a01's ATTACH): - An enum column: postgres_scan typed it VARCHAR, the ATTACH-ed catalog types it ENUM(...), so a contract declaring the column VARCHAR failed the schema gate (mood/type_changed->fail) on DuckDB 1.5.6 and 2.0.0 where 0.21.0 passed. The Postgres SELECT now casts enum columns to VARCHAR (an enum array to VARCHAR[]), as the masking projection replaces columns. Live, the cross-file hunter's contract fails on 084f5b8 and passes here on both versions, landing mood as VARCHAR. - The schema gate fingerprints on a connection of its own and swallows a failed fingerprint, so a fingerprint that could not attach the source would land drifted data; nothing pinned it. A sqlite source with a strict schema it drifts from now has a test (SchemaDriftError, nothing landed). - CHANGELOG: an upgrade note that a Postgres secretRef is now resolved (0.21.0 ignored it and libpq's PGPASSWORD supplied the password), that an enum column still reads as text, and that stream names match without regard to case. query_relation on DuckDB 2.x, against 1.5's answers: - SQL whose later statement does not parse ran the statements ahead of it (2.0's con.sql() runs them before it raises); 1.5 runs none. It now raises from extract_statements before anything runs. - A comment after the last ';' broke the relation 2.0 returns: aggregate, limit and write_csv re-read the statement's text and fail ("syntax error at or near ';'"). The trailing comments are cut, found with duckdb.tokenize (byte offsets, so a multibyte string is cut right). - Statements ahead of the last are run with 2.0's complete() instead of fetchall(), so a large SELECT among them is not read into Python. relation_view: 2.0 refuses a view over rows it already holds (a PIVOT without an IN list, nextval(), RETURNING), which failed a local SQL action's s3:// output and contract_tests' Parquet output. relation_view falls back to a table; contract_tests' view, which only its unreachable COPY used, may fail and its pandas fallback writes the file. Also: detect-secrets flagged `secret == "CREATE TEMPORARY SECRET ..."` in two tests and the shifted baseline line of test_security_regressions.py (CI's Lint job would have failed); MySQL docstrings name the SECRET and READ_ONLY attach; scripts/verify_engines.sh read a relation with fetchone(); the live round trips' skip reason no longer says CI runs them; the example README says READ_ONLY is no write barrier and suggests a read-only role; the new CI guard test has the blank line the license writer expects. Each fix has a test that fails without it (7 mutants, all killed).
…tion parses The full suite on a2506ac found two test-side breaks of the third round: - test_every_duckdb_connection_goes_through_the_sandbox flags every call on the duckdb module, and _duckdb_compat now calls duckdb.tokenize to find a statement's trailing comments. tokenize takes no connection and runs no SQL (a nanobind function of _duckdb, unlike the default-connection wrappers such as duckdb.execute, which document a connection parameter). The fence exempts that one name; a test pins that tokenize documents no connection while execute does, and that a near name is still a bypass. - test_a_failed_sql_action_logs_its_error_redacted's fake connection had only sql(); on DuckDB 2.x query_relation parses with extract_statements first, so the fake raises its error there too.
📄 Documentation ReminderThis PR appears to be missing a documentation reference. Our docs live in a separate repo. Please update the PR description with one of:
See the Contributing Guide for details. |
CodeQL's py/implicit-string-concatenation-in-list flagged two parametrize entries split over two lines (a MERGE and a CTAS-then-INSERT); a '+' makes each one string on purpose.
This was referenced Oct 22, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DuckDB 2.0.0 (2026-10-21) turned
mainred: 51 tests across Test 3.11–3.14, Coverage and the DuckDB end-to-end legs. pip gives DuckDB 2.0 to every Python 3.11+ install, so forge-cli 0.21.0 breaks there too. This PR makes forge-cli run on DuckDB 2.x and keeps 1.5.x working. The floor staysduckdb>=1.5.0, with no ceiling.What changed
The compat seam. What differs between the versions lives in one module,
fluid_build/providers/_duckdb_compat.py, modelled onfluid_build/_mcp_compat.py:first_row/one_row: every DuckDBfetchone()influid_build(18 sites) now reads the result to the end. On 2.0, relations have nofetchone(), and a result read only partway keeps its connection busy. An AST fence test keeps partial reads out offluid_build.query_relation: gives 1.5's answers on 2.x. It returnsNonefor SQL ending in DDL or DML, where 2.0 hands back aCountrelation, aSuccessrelation or an earlier query's rows. SQL that does not parse raises before any of it runs. A comment after the final;is cut, since otherwise 2.0's aggregate, limit and write fail. Earlier statements are drained with 2.0'scomplete().relation_view: 2.0 refuses a view over rows it already holds (aPIVOTwithout anINlist,nextval(),RETURNING), so the rows go to a table instead.storage_compatibility_version=v1.0.0. That is storage version 64, the version 1.5 writes, so a 1.5 install can open them.Postgres and MySQL acquisition sources. 2.0 dropped
postgres_scan, so the runner now attaches the source read-only before the sandbox's lock:ATTACH ... (TYPE postgres|mysql, SECRET <alias>, READ_ONLY).ATTACHnames an empty temporary secret. Without it, the attach fails under the sandbox withUnknown secret storage found: 'local_file'on every version. That is why MySQL sources never worked.VARCHAR, aspostgres_scantyped it, so the schema gate and the landed type stay the same.secretRefis now resolved.CI. A new
duckdb-floorjob installs exactlyMIN_DUCKDB_VERSION(read from the code) on Python 3.12. It runs the compat, sandbox, MCP-driver and DuckDB-runner tests plus every suite the DuckDB end-to-end job runs.tests/test_ci_duckdb_floor.pyfails if that list falls behind.Upgrade notes and Fixed entries are under
[Unreleased]inCHANGELOG.md. There is no version bump.Borrowed, not invented
complete()/abort(), relation API removals.COPY FROM DATABASEconversion recipe in the CHANGELOG.ATTACH,postgres_queryandpostgres_execute; there is nopostgres_scan.DuckDBPyRelation.fetchone, removed in DuckDB 2.0 narwhals-dev/narwhals#4078: read-to-the-end in place offetchone.fluid_build/_mcp_compat.py: the in-repo pattern for a single version seam.Validation
Full suite (
-n auto --dist loadscope, plus perf run serially) ata2506ac4. Every failure was rerun atbc3e2b41(which changes tests only) and on unmodifiedmain(1320619b) in the same venv:maindoes not shareFixed in
bc3e2b41: the run ata2506ac4found two test-side breaks. The sandbox fence flagged the connectionlessduckdb.tokenize, and a fake connection lackedextract_statements.Fail the same on
main:test_duckdb_sandbox_workspace_root.py× 2, which is macOS's/varlayout and passes on Linux CI;test_coding_agent_live.py, which needs a live Claude CLI.Passed on rerun: a hypothesis property test (load), and the
--helpwall-clock budgets, which also pass onmainunder the same load (load average about 150).Floor: the
duckdb-floortest list on duckdb 1.5.0 (Python 3.12): 455 passed, 6 skipped. The 2 failures are the macOS-only/vartests intest_duckdb_sandbox_workspace_root.py, which fail the same onmainon macOS and pass on Linux CI.Live, through the
fluidCLI, against Postgres 16 and MySQL 8.4 containers:examples/source-aligned-postgres-duckdb(validate,apply --mode amend-and-buildwith 5 rows,verify --strict) passes on 2.0.0, 1.5.0 and 1.5.6 (Python 3.10).mainon 2.0 fails withTable Function with name postgres_scan does not exist!.VARCHAR: exit 1 at084f5b87(mood/type_changed→fail), exit 0 here on both versions.Sandbox: the attack battery (a file outside the allowlist,
./.., a symlink,ATTACHafter the lock, functions of extensions that were not loaded) refuses the same things on 1.5.0, 1.5.6 and 2.0.0. 2.0's sqlite scanner is now bounded by the allowlist, so it is stricter.Mutation:
Gates: ruff, black 24.10.0, the strict-mypy allowlist, import-linter, the cold-path module budget, detect-secrets on the changed files (CI's exact command), and the license-header writer.
Reviews
9217f6ec..a2506ac4with probes: hostile Postgres column names and enum labels, 41k tokenizer and fuzz inputs, therelation_viewfallback under the sandbox, and secretRef error text through every output path.9217f6ec,084f5b87anda2506ac4. That includes the detect-secrets hit that would have failed Lint, setup errors escaping the run record, the floor job's coverage, and the CHANGELOG overclaims.READ_ONLYis no write barrier againstpostgres_executein an explicit transaction. Only engine SQL runs on that connection; the docstring and the example README say so.query_relationshapes (CREATE SECRET,EXECUTEof a preparedcount(*) AS "Count", a PIVOT whose only column isCount).Follow-ups (not in this PR)
temp_directory/.tmpwidening: a separate security PR, as decided.ATTACHfails under the sandbox (Unknown secret storage found: 'local_file'), sofluid init --discover postgres://is broken on every version. It needs the runner's temporary-secret fix.Documentation
postgres_scan. No documented DuckDB requirement changes:duckdb>=1.5.0stands.