Skip to content

Deparser drops WHERE clause from EXCLUDE constraints #287

@devin-ai-integration

Description

@devin-ai-integration

Bug Description

The deparser silently drops the WHERE clause from EXCLUDE constraints during parse → deparse cycles.

Input SQL

CREATE TABLE test_exclude_where (
  id uuid PRIMARY KEY,
  database_id uuid NOT NULL,
  status text NOT NULL DEFAULT 'pending',
  EXCLUDE USING btree (database_id WITH =)
    WHERE (status = 'pending')
);

Deparsed output

CREATE TABLE test_exclude_where (id uuid PRIMARY KEY, database_id uuid NOT NULL, status text NOT NULL DEFAULT 'pending', EXCLUDE USING btree (database_id WITH =))

The WHERE (status = 'pending') predicate is completely lost.

Root Cause

The CONSTR_EXCLUSION handler in packages/deparser/src/deparser.ts (lines ~3038-3070) handles access_method and exclusions but never checks or outputs node.where_clause.

The parser correctly preserves where_clause in the AST (verified — the constraint node has a full where_clause A_Expr), but the deparser never visits it.

Impact

This silently changes constraint semantics:

  • With WHERE: "only one pending row per database_id" (partial exclusion)
  • Without WHERE: "only one row per database_id ever" (total exclusion)

Discovered in constructive-db's database_transfer table — pgpm package uses the deparser to bundle SQL, and the bundled output was missing the WHERE clause.

Suggested Fix

In the CONSTR_EXCLUSION case, after the exclusions block and before the break:

if (node.where_clause) {
  output.push('WHERE');
  output.push('(' + this.visit(node.where_clause, context) + ')');
}

Failing Test

PR #286 adds a failing test case (misc/issues-18.sql) that reproduces this bug.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions