Conversation
Greptile SummaryThis PR fixes a bug where table-level CHECK constraints containing Confidence Score: 5/5Safe to merge — the fix correctly resolves the reported bug and all remaining findings are P2 robustness notes. The fix addresses the root cause precisely and is well-tested with a new dump fixture. The one P2 note (function-call parens being stripped before the space-check) is a theoretical edge case that PostgreSQL never triggers with its auto-generated NOT NULL constraints, so it does not affect real-world correctness. ir/inspector.go — the paren-stripping logic has a theoretical edge case worth hardening in a follow-up. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[checkClause from pg_get_constraintdef] --> B["TrimPrefix 'CHECK '"]
B --> C[Strip ALL parentheses]
C --> D{"Ends with ' IS NOT NULL'?"}
D -- No --> G[Keep constraint]
D -- Yes --> E{"Prefix has spaces or tabs?"}
E -- Yes --> G
E -- No --> F[Skip constraint as simple NOT NULL]
F -.->|"Edge case: lower(col) becomes lowercol, no spaces, wrongly skipped"| H[Theoretical gap]
Reviews (1): Last reviewed commit: "fix: table-level CHECK constraints with ..." | Re-trigger Greptile |
ir/inspector.go
Outdated
| // expression is a simple "identifier IS NOT NULL" — not complex expressions | ||
| // that happen to contain IS NOT NULL (e.g., "status = 'x' OR reason IS NOT NULL"). | ||
| inner := strings.TrimPrefix(strings.TrimSpace(checkClause), "CHECK ") | ||
| inner = strings.TrimSpace(strings.NewReplacer("(", "", ")", "").Replace(inner)) |
There was a problem hiding this comment.
Parenthesis stripping can fuse function-call names into a single token
strings.NewReplacer("(", "", ")", "").Replace(inner) removes every paren character from the whole string before the space-check. For an expression like CHECK ((lower(col) IS NOT NULL)) the call becomes lowercol IS NOT NULL — prefix lowercol has no spaces, so the constraint is silently skipped even though it is a user-defined constraint, not a system-generated NOT NULL check.
A regex anchored to the canonical double-paren form PostgreSQL emits (CHECK ((ident IS NOT NULL))) would be more precise and avoids this class of false positives:
var simpleNotNullRe = regexp.MustCompile(
`^CHECK\s*\(\s*\(\s*[A-Za-z_][A-Za-z0-9_$]*\s+IS NOT NULL\s*\)\s*\)$`,
)
// …
if simpleNotNullRe.MatchString(strings.TrimSpace(checkClause)) {
continue
}In practice, PostgreSQL does not auto-generate NOT NULL checks that involve function calls, so the current code is safe for the described issue. This is a robustness note for future-proofing.
There was a problem hiding this comment.
Already addressed — the pushed commit (ad88724) replaced the blanket strings.NewReplacer approach with balanced outer-paren peeling using isBalancedParentheses, which only strips matched outer parentheses and leaves inner parens (like function calls) intact. So CHECK ((lower(col) IS NOT NULL)) correctly peels to lower(col) IS NOT NULL, and the space in lower(col) prevents it from being skipped.
…from dump (#396) The inspector was using strings.Contains(checkClause, "IS NOT NULL") to skip system-generated NOT NULL constraints, but this incorrectly filtered out any CHECK constraint whose expression contained IS NOT NULL — including complex user-defined constraints like CHECK (status = 'active' OR reason IS NOT NULL). Narrowed the filter to only skip simple single-identifier NOT NULL checks (e.g., CHECK ((value IS NOT NULL))) by stripping the CHECK wrapper and parentheses and verifying the prefix is a single word with no spaces. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
51deaab to
ad88724
Compare
There was a problem hiding this comment.
Pull request overview
Fixes a schema-dump correctness bug where table-level CHECK constraints containing IS NOT NULL inside more complex boolean expressions were being incorrectly filtered out during inspection, and adds an integration test fixture for issue #396.
Changes:
- Narrowed the inspector’s “skip redundant NOT NULL CHECK constraints” logic to avoid dropping complex
CHECKexpressions that merely containIS NOT NULL. - Added a new dump integration test case + fixture dataset reproducing issue #396.
- Registered the new integration test in
cmd/dump’s test suite.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
ir/inspector.go |
Updates the CHECK constraint filtering logic intended to skip redundant NOT NULL checks. |
cmd/dump/dump_integration_test.go |
Adds a new integration test to run the issue #396 fixture. |
testdata/dump/issue_396_check_constraint_is_not_null/raw.sql |
Defines the reproduction schema (complex CHECK with IS NOT NULL). |
testdata/dump/issue_396_check_constraint_is_not_null/pgdump.sql |
Captures the pg_dump input used by the integration test harness. |
testdata/dump/issue_396_check_constraint_is_not_null/pgschema.sql |
Captures the expected pgschema dump output asserting the CHECK is preserved. |
testdata/dump/issue_396_check_constraint_is_not_null/manifest.json |
Documents the fixture and links it to GitHub issue #396. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| inner = strings.TrimSpace(inner) | ||
| for len(inner) > 2 && inner[0] == '(' && inner[len(inner)-1] == ')' && isBalancedParentheses(inner[1:len(inner)-1]) { | ||
| inner = strings.TrimSpace(inner[1 : len(inner)-1]) | ||
| } | ||
| if prefix, ok := strings.CutSuffix(inner, " IS NOT NULL"); ok { |
There was a problem hiding this comment.
The NOT NULL CHECK skipping logic is still too permissive: removing all parentheses and then only checking for whitespace means expressions like CHECK (lower(col) IS NOT NULL) or CHECK (foo(bar) IS NOT NULL) become lowercol IS NOT NULL / foobar IS NOT NULL and will be incorrectly treated as a “simple identifier IS NOT NULL”, causing legitimate CHECK constraints to be omitted again. Consider stripping only the outer CHECK wrapper/parentheses (balanced), then matching the remaining expression against a strict identifier regex (quoted or unquoted) + IS NOT NULL, rather than deleting all parentheses in the expression.
There was a problem hiding this comment.
This concern applied to the first commit but is already fixed in the current code. The pushed version uses isBalancedParentheses to peel only matched outer parentheses — it does not remove all parens. After peeling, CHECK ((lower(col) IS NOT NULL)) becomes lower(col) IS NOT NULL, the prefix lower(col) contains a space, and the constraint is correctly preserved.
Summary
IS NOT NULLin complex expressions were silently dropped frompgschema dumpoutputstrings.Contains(checkClause, "IS NOT NULL")inir/inspector.gowas too broad — it skipped ANY check constraint mentioningIS NOT NULL, not just simple system-generated NOT NULL checksCHECK ((identifier IS NOT NULL))patterns by stripping the CHECK wrapper/parentheses and verifying the prefix is a single wordFixes #396
Test plan
issue_396_check_constraint_is_not_nullwith a table containing a multi-condition CHECK usingIS NOT NULLgo test -v ./cmd/dump -run TestDumpCommand_Issue396🤖 Generated with Claude Code