Skip to content

fix(subset): deduplicate CTE names when a table is reachable via multiple FK paths - #430

Open
joshuabaird wants to merge 1 commit into
GreenmaskIO:mainfrom
joshuabaird:fix/cte-duplicate-names
Open

joshuabaird wants to merge 1 commit into
GreenmaskIO:mainfrom
joshuabaird:fix/cte-duplicate-names

Conversation

@joshuabaird

@joshuabaird joshuabaird commented Apr 8, 2026

Copy link
Copy Markdown

Problem

When the FK graph contains a diamond pattern — a shared dependency reachable from the subset root via two or more distinct FK paths — cteQuery.addItem was called multiple times with the same CTE name. This produced a WITH clause with duplicate names, which PostgreSQL rejects with error 42712:

WITH query name "public__<schema>__<table>__ids" specified more than once

Reproduction

Any schema where a single table is reachable via multiple FK paths from the subset root triggers this. A concrete real-world example using dj-stripe:

  • Subset root: djstripe_customer with subset_conds
  • djstripe_paymentmethod.customer_iddjstripe_customer (direct path)
  • djstripe_invoice.customer_iddjstripe_customer (direct path)
  • djstripe_invoice.default_payment_method_iddjstripe_paymentmethod (indirect path)

When Greenmask walks the graph from djstripe_customer, it encounters djstripe_paymentmethod via two routes and calls addItem("public__djstripe_paymentmethod__ids", ...) twice, producing an invalid WITH clause.

I believe this is related to #265.

Fix

Track seen CTE names in a map[string]struct{} on cteQuery and skip addItem if the name is already registered. The first definition wins — both paths produce equivalent content for the same table.

Test plan

  • Run greenmask dump against a schema with diamond-shaped FK dependencies and subset_conds on the shared ancestor — confirm no 42712 error
  • Confirm existing subset tests still pass

…iple FK paths

When the FK graph contains a diamond pattern — a shared dependency
reachable from the subset root via two or more distinct FK paths —
`cteQuery.addItem` was called multiple times with the same CTE name.
This produced a WITH clause with duplicate names, which PostgreSQL
rejects with error 42712 ("WITH query name ... specified more than once").

Example: subsetting `djstripe_customer` where `djstripe_paymentmethod`
is reachable both directly (via `paymentmethod.customer_id`) and
indirectly (via `invoice.default_payment_method_id`) causes
`public__djstripe_paymentmethod__ids` to be added to the CTE list twice.

Fix: track seen CTE names in a `map[string]struct{}` on `cteQuery` and
skip `addItem` if the name is already registered. The first definition
wins, which is correct since both paths produce equivalent content for
the same table.

Fixes: GreenmaskIO#265
Made-with: Cursor

@wwoytenko wwoytenko left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey! Thank you for your MR. Could you provide please database schema with data that can reproduce it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants