From 7771d1afec6a6a1fa024dd0726de69b16f2e7603 Mon Sep 17 00:00:00 2001 From: Konstantin Grachev Date: Tue, 23 Sep 2025 18:44:07 +0300 Subject: [PATCH] fix: properly handle nested parentheses in check constraint normalization - Add balanced parentheses validation before removing outer parentheses - Prevent incorrect normalization of complex expressions with nested parentheses - Improve robustness of CHECK constraint parsing logic --- internal/ir/normalize.go | 12 ++++++--- testdata/diff/create_table/add_check/diff.sql | 2 ++ testdata/diff/create_table/add_check/new.sql | 3 +++ testdata/diff/create_table/add_check/old.sql | 3 +++ .../diff/create_table/add_check/plan.json | 26 +++++++++++++++++++ testdata/diff/create_table/add_check/plan.sql | 4 +++ testdata/diff/create_table/add_check/plan.txt | 16 ++++++++++++ 7 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 testdata/diff/create_table/add_check/diff.sql create mode 100644 testdata/diff/create_table/add_check/new.sql create mode 100644 testdata/diff/create_table/add_check/old.sql create mode 100644 testdata/diff/create_table/add_check/plan.json create mode 100644 testdata/diff/create_table/add_check/plan.sql create mode 100644 testdata/diff/create_table/add_check/plan.txt diff --git a/internal/ir/normalize.go b/internal/ir/normalize.go index c2cb6a4d..8a07e5ac 100644 --- a/internal/ir/normalize.go +++ b/internal/ir/normalize.go @@ -849,9 +849,15 @@ func normalizeCheckClause(checkClause string) string { clause = after } - // Remove outer parentheses if present (may be multiple layers) - for strings.HasPrefix(clause, "(") && strings.HasSuffix(clause, ")") { - clause = strings.TrimSpace(clause[1 : len(clause)-1]) + // Remove ONLY outer parentheses if they are balanced and not part of complex expression + clause = strings.TrimSpace(clause) + for len(clause) > 0 && clause[0] == '(' && clause[len(clause)-1] == ')' { + // Check if parentheses are balanced + if isBalancedParentheses(clause[1 : len(clause)-1]) { + clause = strings.TrimSpace(clause[1 : len(clause)-1]) + } else { + break + } } // First apply legacy conversions to handle PostgreSQL-specific patterns diff --git a/testdata/diff/create_table/add_check/diff.sql b/testdata/diff/create_table/add_check/diff.sql new file mode 100644 index 00000000..2b331ac2 --- /dev/null +++ b/testdata/diff/create_table/add_check/diff.sql @@ -0,0 +1,2 @@ +ALTER TABLE code + ADD CONSTRAINT code_check CHECK (code > 0 AND code < 255); diff --git a/testdata/diff/create_table/add_check/new.sql b/testdata/diff/create_table/add_check/new.sql new file mode 100644 index 00000000..305df777 --- /dev/null +++ b/testdata/diff/create_table/add_check/new.sql @@ -0,0 +1,3 @@ +CREATE TABLE public.code ( + code integer PRIMARY KEY CONSTRAINT code_check CHECK (code > 0 AND code < 255) +); diff --git a/testdata/diff/create_table/add_check/old.sql b/testdata/diff/create_table/add_check/old.sql new file mode 100644 index 00000000..83f90eaf --- /dev/null +++ b/testdata/diff/create_table/add_check/old.sql @@ -0,0 +1,3 @@ +CREATE TABLE IF NOT EXISTS code ( + code integer PRIMARY KEY +); diff --git a/testdata/diff/create_table/add_check/plan.json b/testdata/diff/create_table/add_check/plan.json new file mode 100644 index 00000000..c61b665c --- /dev/null +++ b/testdata/diff/create_table/add_check/plan.json @@ -0,0 +1,26 @@ +{ + "version": "1.0.0", + "pgschema_version": "1.1.0", + "created_at": "2025-09-23T18:49:35+03:00", + "source_fingerprint": { + "hash": "a9d472cd406e27485b570b72906ad224b8282469bd50699bddd6da04acdabaef" + }, + "groups": [ + { + "steps": [ + { + "sql": "ALTER TABLE code\nADD CONSTRAINT code_check CHECK (code > 0 AND code < 255) NOT VALID;", + "type": "table.constraint", + "operation": "create", + "path": "public.code.code_check" + }, + { + "sql": "ALTER TABLE code VALIDATE CONSTRAINT code_check;", + "type": "table.constraint", + "operation": "create", + "path": "public.code.code_check" + } + ] + } + ] +} diff --git a/testdata/diff/create_table/add_check/plan.sql b/testdata/diff/create_table/add_check/plan.sql new file mode 100644 index 00000000..6254472d --- /dev/null +++ b/testdata/diff/create_table/add_check/plan.sql @@ -0,0 +1,4 @@ +ALTER TABLE code +ADD CONSTRAINT code_check CHECK (code > 0 AND code < 255) NOT VALID; + +ALTER TABLE code VALIDATE CONSTRAINT code_check; diff --git a/testdata/diff/create_table/add_check/plan.txt b/testdata/diff/create_table/add_check/plan.txt new file mode 100644 index 00000000..49234554 --- /dev/null +++ b/testdata/diff/create_table/add_check/plan.txt @@ -0,0 +1,16 @@ +Plan: 1 to modify. + +Summary by type: + tables: 1 to modify + +Tables: + ~ code + + code_check (constraint) + +DDL to be executed: +-------------------------------------------------- + +ALTER TABLE code +ADD CONSTRAINT code_check CHECK (code > 0 AND code < 255) NOT VALID; + +ALTER TABLE code VALIDATE CONSTRAINT code_check;