Skip to content

DuckLake: test the writes a pipeline makes into DuckDB-created tables - #14

Open
fuziontech wants to merge 2 commits into
masterfrom
james/ducklake-write-round-trips
Open

DuckLake: test the writes a pipeline makes into DuckDB-created tables#14
fuziontech wants to merge 2 commits into
masterfrom
james/ducklake-write-round-trips

Conversation

@fuziontech

Copy link
Copy Markdown
Member

Summary

  • New TestDuckLakeWriteRoundTrips (9 tests): DuckDB creates the table through duckdb_jdbc + the ducklake extension against the testcontainers Postgres catalog, Trino writes, both engines read back.
  • Small fixture refactor: TestingDuckLakeCatalog.rows()/scalar() replace a private helper in TestDuckLakeWrites.

What it establishes

Behavior Result
INSERT into a table DuckDB partitioned by a VARCHAR column, and by year(ts)/day(ts) transforms one file set per partition, values recorded in ducklake_file_partition_value; a DuckDB insert lands under the same values
Whole-partition DELETE on a VARCHAR/INTEGER key metadata only: 0 rows read, files ended, no delete file
Partial-partition DELETE one delete file, rows correct in DuckDB
Whole-day DELETE on a DATE key, and on a TIMESTAMP WITH TIME ZONE key reads every row of those daysisEnforceableType admits only integer, boolean and unbounded varchar keys, so temporal partition predicates are never enforced and the delete takes the row-level path. The assertions pin the current cost (getProcessedInputPositions() == 3) and are the ones to flip to 0 when temporal keys become enforceable.
STRUCT (incl. $-named fields), HUGEINT, UBIGINT, JSON, TIMESTAMPTZ with microseconds, DECIMAL(18,3), UUID, VARCHAR[], MAP exact types and values in both directions
Trino CREATE OR REPLACE VIEW over a DuckDB view, then DROP VIEW old row ended, one trino-dialect row, nothing left after drop; stored sql is Trino's formatted text with a literal catalog prefix, which DuckDB cannot bind (Catalog "ducklake" does not exist)
INSERT listing all columns after DuckDB ADD COLUMN old rows read with NULL in the new column

Why

The first real write workload on the cell is a customer's SQLMesh pipeline writing into tables DuckDB created and keeps reading. These tests pin what holds today and turn the two gaps (temporal partition enforcement, catalog-qualified view SQL) into concrete assertions for the follow-up changes.

Not in this PR

  • No connector behavior change. The two gaps above are separate follow-ups.
  • TestDuckLakeMetastoreConnectionPool.testIdleConnectionsAreReleased flaked once in a full-module run (0L to be greater than 0L, timing on connectorConnectionCount()); it is untouched here and passed on re-runs.

Test plan

  • ./mvnw -pl plugin/trino-ducklake test -Dtest='TestDuckLakeWriteRoundTrips*' → 9 tests, 0 failures (JDK 25 container, testcontainers Postgres).
  • -P ci checks (license, checkstyle, modernizer) pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WCY5Jf2BQPCVKJTZU1TpEe

fuziontech and others added 2 commits September 3, 2026 23:09
A pipeline that writes from Trino into a DuckLake catalog DuckDB owns hits
paths the existing write tests do not: every table there was created by the
other engine, so its partitioning, its column types and its views are ones
this connector has to accept rather than choose.

TestDuckLakeWriteRoundTrips creates each table through DuckDB, writes it
through Trino, and reads it back through both. It covers partitioned inserts
and deletes, the types an event table carries, replacing a view DuckDB wrote,
and inserting after DuckDB adds a column.

Two behaviors the tests pin down are worth knowing before relying on them:

- A DELETE covering whole partitions of a table filed by a DATE column reads
  every row of them. isEnforceableType excludes DATE, so the predicate is
  never enforced by the connector and the partition values cannot decide it.
  The files are still dropped, because nothing is left in them, but the
  statement pays for a full scan of the days it removes. The same delete over
  a VARCHAR or INTEGER key touches no row.
- The query text stored in ducklake_view is the query as Trino formats it,
  and keeps whatever catalog name the view was written with. A view naming
  its tables through the Trino catalog does not bind in DuckDB, which has no
  database of that name.

The DuckDB read helpers move from TestDuckLakeWrites onto the catalog itself,
where both test classes reach them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCY5Jf2BQPCVKJTZU1TpEe
A pipeline that materializes DATE_TRUNC('day', ts)::TIMESTAMPTZ files its
tables by a timestamp, not by a DATE. isEnforceableType admits only TINYINT,
SMALLINT, INTEGER, BIGINT, BOOLEAN and unbounded VARCHAR, so that key is
excluded for the same reason DATE is, and a DELETE covering whole days reads
every row of them.

The test pins the current cost down: the delete reports the rows of the two
days it removes as processed input positions. That number becomes 0 once a
predicate on a temporal partition column is enforced.

It also covers the write path the exclusion says nothing about. An identity
partition key of this type is writable, one file per day, and the value is
recorded in UTC whatever zone the row carried; a row DuckDB inserts afterwards
lands under the value Trino recorded for that day.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCY5Jf2BQPCVKJTZU1TpEe
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant