fix(merge): preserve unified schema on merge/upsert (no column drop or type narrowing)#25
Conversation
combine_tables rebuilt the merged batch with pa.Table.from_pylist, which infers the schema from the first row -- silently dropping columns present only in incoming rows (existing rows sort first and lack them) and re-inferring/narrowing column types. The latter could fail the load with a 409 type conflict (e.g. decimal(12,2) -> decimal(5,2)). Build the merged batch with the unified schema of the existing and incoming tables instead. Also clarify the write-modes docs: dlt resources accept append/replace/merge only; merge is upsert-by-PK; insert-only is not selectable per resource. Verified live: a merge that adds a new column and uses a decimal column now promotes the column and preserves decimal(12,2) with no 409.
There was a problem hiding this comment.
Fix is correct on both paths. The unified schema preserves incoming-only columns (existing rows sort first via merge_rows, so first-row inference dropped them) and avoids type re-inference/narrowing. Tests cover column promotion, type preservation, and insert-only. promote_options="permissive" is already used elsewhere in the file, so pyarrow support is confirmed. Docs/CHANGELOG match SUPPORTED_WRITE_DISPOSITIONS.
There was a problem hiding this comment.
Verified the merge schema-preservation fix. unify_schemas(..., promote_options="permissive") correctly preserves incoming-only columns and the widest type on the merge/upsert path, and the insert-only path applies the incoming schema before permissive concat. Tests cover column promotion and decimal type preservation for both paths. LGTM.
Summary
Fixes a correctness bug in the merge/upsert/insert-only path uncovered by a comprehensive live E2E of the destination.
combine_tablesrebuilt the merged batch withpa.Table.from_pylist(merged)(merge.py), which infers the Arrow schema from the first row only. Because existing rows sort first and the merge round-trips throughto_pylist(), this had two failure modes on amergeload:decimal(12, 2)column becamedecimal(5, 2); the backend rejects narrowing with409 CONFLICT("only widening is applied"), failing the load after retries.Fix
Build the merged batch with the unified schema of the existing and incoming tables (
pa.unify_schemas(..., promote_options="permissive")+from_pylist(merged, schema=...)). Same treatment for theinsert-onlynew-rows table. Columns and types are now preserved;appendalready usedpa.concat_tables(promote_options="permissive")and was unaffected.Tests
decimal(12, 2)type; insert-only promotes a new column. Full suite: 54 passed, ruff clean.tiercolumn and uses a decimal column now promotes the column (id1=null, id2/id4=gold) and preservesdecimal128(12, 2)— no 409. (This exact scenario 409'd before the fix.)Docs
Clarified the write-modes section: dlt resources accept
append/replace/mergeonly;mergeis upsert-by-primary-key (whatupsertresolves to);insert-onlyis implemented in the combine logic but not selectable as a resourcewrite_disposition.Notes
Independent of #24 (additive schema evolution). mypy on
merge.pygains 4 errors of the same pre-existing pyarrow-Anycategory the file already has (mypy isn't CI-enforced; the file was never clean).