Skip to content

fix: make column mapping order deterministic - #21

Merged
funcpp merged 2 commits into
mainfrom
fix/deterministic-column-order
Sep 21, 2026
Merged

funcpp merged 2 commits into
mainfrom
fix/deterministic-column-order

Conversation

@funcpp

@funcpp funcpp commented Sep 21, 2026

Copy link
Copy Markdown
Owner

Cherry-picked from #12 (56677a0), one of the contributions in #15. Authorship preserved; the only change on rebase was a conflict caused by the rustfmt pass in #17.

What

resolve collected output nodes into a HashSet<NodeId> and built mappings by iterating it, so construction order followed hash order with a per-process random seed. The sort_by_key afterwards keyed on a HashMap<String, usize> of output names, so duplicate names collided and same-named mappings stayed in whatever order the set produced.

The loop now walks ordered_cols, which already carries projection order. That makes the sort redundant, so it goes too.

SELECT a.id, b.id FROM a JOIN b ON a.id = b.bid

Both mappings target id; which came first varied between runs.

Verification

duplicate_output_names_preserve_projection_order, run 15 times against the pre-fix resolver and the fixed one: 14/15 fail before, 0/15 fail after.

(#12 also carried a three-duplicate variant of the same test. It covered the same path — with the HashSet gone, order follows ordered_cols structurally — so a second commit here drops it. It was measured too: 12/15 fail pre-fix, 0/15 post-fix.)

Checked for behavior change beyond ordering, since removing the HashSet also removes its deduplication and removing the sort could reposition star-expanded columns. Neither shows up — output is identical pre- and post-fix for:

SELECT a, a FROM t                                     ["a", "a"]
SELECT a AS x, a AS x FROM t                           ["x", "x"]
SELECT id, id FROM users                    (catalog)  ["id", "id"]
SELECT *, id FROM users                     (catalog)  ["id", "id", "name"]
SELECT id, * FROM users                     (catalog)  ["id", "id", "name"]
SELECT *, id FROM users                    (no catalog) ["*", "id"]
SELECT users.*, orders.x FROM users JOIN orders (catalog) ["x", "id", "name"]

Each projection item gets its own Output node, so the HashSet was never deduplicating anything; and ordered_cols already placed star nodes where the old sort pushed their expansions.

cargo fmt --all --check                                           exit 0
cargo clippy --workspace --all-targets --all-features -D warnings exit 0
cargo test --workspace --all-features                             exit 0  (91 tests)

Compatibility

No public type or signature change. Results change only for previously nondeterministic cases, where one of the orderings is now always chosen.

🤖 Generated with Claude Code

eitsupi and others added 2 commits September 21, 2026 14:14
The order of `ColumnLineage.mappings` varied between runs of the same
binary whenever a query had duplicate output column names:

    SELECT a.id, b.id FROM a JOIN b ON a.id = b.bid
      sometimes [ id <- a.id , id <- b.id ]
      sometimes [ id <- b.id , id <- a.id ]

With three duplicates, four distinct orderings showed up across six runs.
Callers that cache or diff results see the same input produce different
output.

`resolve` collected the output nodes into a `HashSet<NodeId>` and built
the mappings by iterating it, so construction order followed hash order
with a per-process random seed. The sort afterwards could not undo this:
it keyed on a `HashMap<String, usize>` of output names, so duplicate
names collided and one index won, leaving same-named mappings in
whatever order the set had produced.

`ordered_cols` already holds the projection order, so build the mappings
straight from it. That makes the sort redundant — it can only reproduce
the order the loop now has, and it is the reason duplicates were
reordered in the first place — so it goes too.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
`three_duplicate_output_names_preserve_projection_order` covered the same
path as the two-column test. It earned its place while the bug existed,
when a wrong order could pass by luck; with the `HashSet` gone, mapping
order follows `ordered_cols` structurally and a third duplicate exercises
nothing new.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

2 participants