From f0b40c71c461d55163112e2444a0b6569d147ed0 Mon Sep 17 00:00:00 2001 From: Azim Sonawalla Date: Sun, 4 Jan 2026 17:35:17 -0500 Subject: [PATCH] fix: normalize table references in policy expressions (#224) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following the pattern established in PR #222 for function qualifiers, this extends policy expression normalization to also strip same-schema qualifiers from table references. When a policy expression contains a subquery like: FROM public.users u PostgreSQL's pg_get_expr() returns it without the schema qualifier: FROM users u This caused perpetual diffs because pgschema added the qualifier back. The fix applies the same normalization approach: strip schema qualifiers from table references when they match the target schema. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- ir/normalize.go | 8 ++++++- .../same_schema_table_reference/diff.sql | 1 + .../same_schema_table_reference/new.sql | 21 +++++++++++++++++++ .../same_schema_table_reference/old.sql | 14 +++++++++++++ .../same_schema_table_reference/plan.json | 20 ++++++++++++++++++ .../same_schema_table_reference/plan.sql | 1 + .../same_schema_table_reference/plan.txt | 13 ++++++++++++ 7 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 testdata/diff/create_policy/same_schema_table_reference/diff.sql create mode 100644 testdata/diff/create_policy/same_schema_table_reference/new.sql create mode 100644 testdata/diff/create_policy/same_schema_table_reference/old.sql create mode 100644 testdata/diff/create_policy/same_schema_table_reference/plan.json create mode 100644 testdata/diff/create_policy/same_schema_table_reference/plan.sql create mode 100644 testdata/diff/create_policy/same_schema_table_reference/plan.txt diff --git a/ir/normalize.go b/ir/normalize.go index 30ee171a..cccd8af9 100644 --- a/ir/normalize.go +++ b/ir/normalize.go @@ -230,7 +230,7 @@ func normalizePolicyRoles(roles []string) []string { // normalizePolicyExpression normalizes policy expressions (USING/WITH CHECK clauses) // It preserves parentheses as they are part of the expected format for policies -// tableSchema is used to strip same-schema qualifiers from function calls (Issue #220) +// tableSchema is used to strip same-schema qualifiers from function calls and table references (Issue #220, #224) func normalizePolicyExpression(expr string, tableSchema string) string { if expr == "" { return expr @@ -248,6 +248,12 @@ func normalizePolicyExpression(expr string, tableSchema string) string { prefix := tableSchema + "." pattern := regexp.MustCompile(regexp.QuoteMeta(prefix) + `([a-zA-Z_][a-zA-Z0-9_]*)\(`) expr = pattern.ReplaceAllString(expr, `${1}(`) + + // Strip same-schema qualifiers from table references (Issue #224) + // Matches schema.identifier followed by whitespace, comma, closing paren, or end of string + // Example: public.users -> users (when tableSchema is "public") + tablePattern := regexp.MustCompile(regexp.QuoteMeta(prefix) + `([a-zA-Z_][a-zA-Z0-9_]*)(\s|,|\)|$)`) + expr = tablePattern.ReplaceAllString(expr, `${1}${2}`) } // Handle all parentheses normalization (adding required ones, removing unnecessary ones) diff --git a/testdata/diff/create_policy/same_schema_table_reference/diff.sql b/testdata/diff/create_policy/same_schema_table_reference/diff.sql new file mode 100644 index 00000000..f2f34c5d --- /dev/null +++ b/testdata/diff/create_policy/same_schema_table_reference/diff.sql @@ -0,0 +1 @@ +CREATE POLICY select_own_orders ON orders FOR SELECT TO PUBLIC USING (user_id IN ( SELECT u.id FROM users u WHERE (u.tenant_id = 1))); diff --git a/testdata/diff/create_policy/same_schema_table_reference/new.sql b/testdata/diff/create_policy/same_schema_table_reference/new.sql new file mode 100644 index 00000000..d7f2f2b4 --- /dev/null +++ b/testdata/diff/create_policy/same_schema_table_reference/new.sql @@ -0,0 +1,21 @@ +-- Test case for Issue #224: Table references in policy expressions +-- This tests that same-schema table references are properly normalized + +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + tenant_id INTEGER NOT NULL +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) +); + +ALTER TABLE orders ENABLE ROW LEVEL SECURITY; + +-- Policy with subquery referencing another table in the same schema +-- The table reference "users" should be normalized regardless of schema prefix +CREATE POLICY select_own_orders ON orders + FOR SELECT + TO PUBLIC + USING (user_id IN (SELECT u.id FROM users u WHERE u.tenant_id = 1)); diff --git a/testdata/diff/create_policy/same_schema_table_reference/old.sql b/testdata/diff/create_policy/same_schema_table_reference/old.sql new file mode 100644 index 00000000..2898014b --- /dev/null +++ b/testdata/diff/create_policy/same_schema_table_reference/old.sql @@ -0,0 +1,14 @@ +-- Test case for Issue #224: Table references in policy expressions +-- This tests that same-schema table references are properly normalized + +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + tenant_id INTEGER NOT NULL +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) +); + +ALTER TABLE orders ENABLE ROW LEVEL SECURITY; diff --git a/testdata/diff/create_policy/same_schema_table_reference/plan.json b/testdata/diff/create_policy/same_schema_table_reference/plan.json new file mode 100644 index 00000000..16900614 --- /dev/null +++ b/testdata/diff/create_policy/same_schema_table_reference/plan.json @@ -0,0 +1,20 @@ +{ + "version": "1.0.0", + "pgschema_version": "1.5.1", + "created_at": "1970-01-01T00:00:00Z", + "source_fingerprint": { + "hash": "48bc23dfa4645111f3629340b47451db977912c1c85e523f09f66d9548435fe8" + }, + "groups": [ + { + "steps": [ + { + "sql": "CREATE POLICY select_own_orders ON orders FOR SELECT TO PUBLIC USING (user_id IN ( SELECT u.id FROM users u WHERE (u.tenant_id = 1)));", + "type": "table.policy", + "operation": "create", + "path": "public.orders.select_own_orders" + } + ] + } + ] +} diff --git a/testdata/diff/create_policy/same_schema_table_reference/plan.sql b/testdata/diff/create_policy/same_schema_table_reference/plan.sql new file mode 100644 index 00000000..f2f34c5d --- /dev/null +++ b/testdata/diff/create_policy/same_schema_table_reference/plan.sql @@ -0,0 +1 @@ +CREATE POLICY select_own_orders ON orders FOR SELECT TO PUBLIC USING (user_id IN ( SELECT u.id FROM users u WHERE (u.tenant_id = 1))); diff --git a/testdata/diff/create_policy/same_schema_table_reference/plan.txt b/testdata/diff/create_policy/same_schema_table_reference/plan.txt new file mode 100644 index 00000000..dd11a9ac --- /dev/null +++ b/testdata/diff/create_policy/same_schema_table_reference/plan.txt @@ -0,0 +1,13 @@ +Plan: 1 to modify. + +Summary by type: + tables: 1 to modify + +Tables: + ~ orders + + select_own_orders (policy) + +DDL to be executed: +-------------------------------------------------- + +CREATE POLICY select_own_orders ON orders FOR SELECT TO PUBLIC USING (user_id IN ( SELECT u.id FROM users u WHERE (u.tenant_id = 1)));