Skip to content

fix: EXCLUDE constraint incorrectly dumped as regular INDEX (#281)#289

Merged
tianzhou merged 2 commits intomainfrom
fix/issue-281-exclude-constraint
Feb 15, 2026
Merged

fix: EXCLUDE constraint incorrectly dumped as regular INDEX (#281)#289
tianzhou merged 2 commits intomainfrom
fix/issue-281-exclude-constraint

Conversation

@tianzhou
Copy link
Contributor

Summary

  • EXCLUDE USING gist constraints were being converted to regular CREATE INDEX statements during dump, losing the exclusion semantics (WITH =, WITH && operators)
  • Now EXCLUDE constraints are properly recognized via contype='x' in pg_catalog queries and rendered as inline table constraints using pg_get_constraintdef()
  • Exclusion constraint backing indexes are excluded from the index queries to avoid duplicate output

Fixes #281

Test plan

  • Dump test: go test -v ./cmd/dump -run TestDumpCommand_Issue281
  • Diff test: PGSCHEMA_TEST_FILTER="create_table/issue_281_exclude_constraint" go test -v ./internal/diff -run TestDiffFromFiles
  • Integration test: PGSCHEMA_TEST_FILTER="create_table/issue_281_exclude_constraint" go test -v ./cmd -run TestPlanAndApply
  • Full regression: go test ./...

🤖 Generated with Claude Code

EXCLUDE USING gist constraints were being converted to regular CREATE INDEX
statements during dump, losing exclusion semantics (WITH operators). Now they
are properly recognized as constraints and rendered inline in CREATE TABLE.

- Add ConstraintTypeExclusion and ExclusionDefinition to IR
- Query pg_get_constraintdef() for exclusion definitions (contype='x')
- Exclude exclusion constraint indexes from index queries
- Handle EXCLUDE in diff generation (inline, add, modify)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 15, 2026 15:54
The diff integration test (TestPlanAndApply) already exercises the full
inspector + diff pipeline, making the separate dump test unnecessary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a bug where EXCLUDE constraints were incorrectly dumped as regular CREATE INDEX statements, losing their exclusion semantics (the WITH operators that define the exclusion behavior). The fix ensures EXCLUDE constraints are properly recognized via contype='x' in PostgreSQL catalog queries and rendered as inline table constraints using the full definition from pg_get_constraintdef().

Changes:

  • Added support for EXCLUDE constraint type throughout the IR and diff generation pipeline
  • Modified index queries to exclude indexes backing EXCLUDE constraints (preventing duplicates)
  • Added comprehensive test coverage including dump, diff, and integration tests

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
testdata/dump/issue_281_exclude_constraint/* Test data for dump command validation
testdata/diff/create_table/issue_281_exclude_constraint/* Test data for diff and plan command validation
ir/queries/queries.sql Added exclusion_definition field to constraint queries; excluded 'x' type from index queries
ir/queries/queries.sql.go Generated Go code from SQL query changes
ir/ir.go Added ExclusionDefinition field to Constraint struct and ConstraintTypeExclusion constant
ir/inspector.go Added EXCLUDE constraint type handling and population of ExclusionDefinition field
ir/normalize.go Added normalizeExclusionDefinition function for constraint normalization
internal/diff/constraint.go Added EXCLUDE constraint case in generateConstraintSQL and getInlineConstraintsForTable; updated column comparison logic
internal/diff/table.go Added EXCLUDE constraint handling in ALTER TABLE statement generation
cmd/dump/dump_integration_test.go Added integration test for issue #281

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +66 to +67
// Use the full definition from pg_get_constraintdef()
return fmt.Sprintf("CONSTRAINT %s %s", ir.QuoteIdentifier(constraint.Name), constraint.ExclusionDefinition)
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

The EXCLUDE constraint generation does not handle the NOT VALID state. While pg_get_constraintdef() returns the constraint definition, it does not include the validation state. Similar to CHECK constraints (lines 56-64), EXCLUDE constraints should append " NOT VALID" when constraint.IsValid is false to properly represent invalidated exclusion constraints.

Suggested change
// Use the full definition from pg_get_constraintdef()
return fmt.Sprintf("CONSTRAINT %s %s", ir.QuoteIdentifier(constraint.Name), constraint.ExclusionDefinition)
// Use the full definition from pg_get_constraintdef() and append NOT VALID if needed
result := fmt.Sprintf("CONSTRAINT %s %s", ir.QuoteIdentifier(constraint.Name), constraint.ExclusionDefinition)
if !constraint.IsValid {
result += " NOT VALID"
}
return result

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

PostgreSQL does not support NOT VALID for EXCLUDE constraints — only CHECK, FOREIGN KEY, and NOT NULL constraints support it (docs). pg_get_constraintdef() will never include "NOT VALID" for exclusion constraints, so this handling is unnecessary.

Comment on lines +893 to +895
// We keep it as-is since both desired and current state come from pg_get_constraintdef().
func normalizeExclusionDefinition(definition string) string {
return strings.TrimSpace(definition)
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

The normalizeExclusionDefinition function should strip the " NOT VALID" suffix from the definition, similar to how normalizeCheckClause handles it (lines 904-911). PostgreSQL's pg_get_constraintdef() may include " NOT VALID" at the end for invalidated constraints, but the validation state should be controlled via the IsValid field for consistency with other constraint types.

Suggested change
// We keep it as-is since both desired and current state come from pg_get_constraintdef().
func normalizeExclusionDefinition(definition string) string {
return strings.TrimSpace(definition)
// We keep it as-is since both desired and current state come from pg_get_constraintdef(),
// except for stripping a trailing " NOT VALID" suffix to match normalizeCheckClause behavior.
func normalizeExclusionDefinition(definition string) string {
normalized := strings.TrimSpace(definition)
// Strip " NOT VALID" suffix if present (mimicking pg_dump behavior)
// PostgreSQL's pg_get_constraintdef may include NOT VALID at the end,
// but the validation state should be represented via the IsValid field.
const notValidSuffix = " NOT VALID"
if strings.HasSuffix(normalized, notValidSuffix) {
normalized = strings.TrimSpace(strings.TrimSuffix(normalized, notValidSuffix))
}
return normalized

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as above — PostgreSQL does not support NOT VALID for EXCLUDE constraints, so pg_get_constraintdef() will never return a "NOT VALID" suffix for them. Stripping it would be dead code.

@tianzhou tianzhou merged commit 14d53d6 into main Feb 15, 2026
1 check passed
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.

EXCLUDE constraint incorrectly dumped as regular INDEX

1 participant