Skip to content

Conversation

@tianzhou
Copy link
Contributor

@tianzhou tianzhou commented Jan 30, 2026

Fix #269

When a varchar column has a CHECK constraint with IN (...), PostgreSQL stores it internally as col::text = ANY (ARRAY['a'::varchar, ...]::text[]). The old code failed to handle the ::text[] cast at the end, producing invalid SQL.

This fix:

  • Adds convertAnyArrayToSimpleIn() that strips type casts from values for idempotency
  • Fixes findArrayClose() to handle optional array type casts
  • Updates convertAnyArrayToIn() to properly find closing paren after optional type casts

Copilot AI review requested due to automatic review settings January 30, 2026 18:08
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 pull request fixes handling of CHECK constraints with IN clauses on varchar columns to ensure idempotency during schema migrations. PostgreSQL stores these constraints using the = ANY (ARRAY[...]) syntax, and the previous code failed to properly normalize them back to the user's original IN (...) format.

Changes:

  • Introduced convertAnyArrayToSimpleIn() to strip type casts from array values for idempotent normalization
  • Fixed findArrayClose() to return the position of ] without requiring an immediate following )
  • Updated both conversion functions to properly handle optional array type casts when finding the closing parenthesis

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

File Description
ir/normalize.go Added convertAnyArrayToSimpleIn() function, updated findArrayClose() and convertAnyArrayToIn() to handle optional array type casts, improved documentation
testdata/diff/online/add_constraint/* Added test case for varchar column with IN constraint
testdata/diff/migrate/v2/* Updated expected output to show type casts stripped from IN clause values

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

When a varchar column has a CHECK constraint with IN (...), PostgreSQL
stores it internally as `col::text = ANY (ARRAY['a'::varchar, ...]::text[])`.
The old code failed to handle the `::text[]` cast at the end, producing
invalid SQL.

This fix:
- Fixes findArrayClose() to handle optional array type casts like ::text[]
- Updates convertAnyArrayToIn() to properly find closing paren after
  optional type casts
- Preserves type information (e.g., ::character varying, ::text) in output

Co-Authored-By: Claude Opus 4.5 <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

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


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

Comment on lines +1099 to +1105
closeParenIdx := arrayEnd + 1
for closeParenIdx < len(expr) && expr[closeParenIdx] != ')' {
closeParenIdx++
}
if closeParenIdx >= len(expr) {
return expr // No closing paren found
}
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The simple loop to find the closing parenthesis for ANY(...) is too naive and will fail if the array type cast contains parentheses. For example, with input like 'col = ANY (ARRAY['a'::varchar(10)]::varchar(10)[])', this code would incorrectly find the ')' inside 'varchar(10)' instead of the one that closes 'ANY(...)'.

A more robust approach would be to properly parse the optional type cast (which follows the pattern '::typename' or '::typename[]' where typename can include parentheses for types like varchar(10)), or to track parenthesis nesting depth similar to how findArrayClose tracks bracket depth.

Copilot uses AI. Check for mistakes.
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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

When user writes CHECK (status IN ('pending', ...)) on a varchar column,
PostgreSQL stores it with single casts: 'pending'::character varying.

But when pgschema generates CHECK (status::text IN ('pending'::character
varying, ...)), PostgreSQL stores it with double casts:
'pending'::character varying::text.

This caused idempotency failures on second apply because the normalized
forms differed.

Add two normalizations:
1. (column)::type → column::type (remove unnecessary parens)
2. ::character varying::text → ::character varying (normalize double casts)

Both forms now normalize to the same output while preserving type casts.

Co-Authored-By: Claude Opus 4.5 <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

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.


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

@tianzhou tianzhou changed the title fix: varchar IN check constraints generating invalid SQL (#269) fix: varchar IN check constraints generating invalid SQL Jan 31, 2026
@tianzhou tianzhou merged commit c4cdae4 into main Jan 31, 2026
7 checks 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.

Varchar In Check Constraints cause invalid sql generation

1 participant