diff --git a/ir/normalize.go b/ir/normalize.go index 52dec79d..30ee171a 100644 --- a/ir/normalize.go +++ b/ir/normalize.go @@ -70,9 +70,9 @@ func normalizeTable(table *Table) { normalizeColumn(column, table.Schema) } - // Normalize policies + // Normalize policies (pass table schema for context - Issue #220) for _, policy := range table.Policies { - normalizePolicy(policy) + normalizePolicy(policy, table.Schema) } // Normalize triggers @@ -191,7 +191,8 @@ func normalizeDefaultValue(value string, tableSchema string) string { } // normalizePolicy normalizes RLS policy representation -func normalizePolicy(policy *RLSPolicy) { +// tableSchema is used to strip same-schema qualifiers from function calls (Issue #220) +func normalizePolicy(policy *RLSPolicy, tableSchema string) { if policy == nil { return } @@ -201,8 +202,8 @@ func normalizePolicy(policy *RLSPolicy) { // Normalize expressions by removing extra whitespace // For policy expressions, we want to preserve parentheses as they are part of the expected format - policy.Using = normalizePolicyExpression(policy.Using) - policy.WithCheck = normalizePolicyExpression(policy.WithCheck) + policy.Using = normalizePolicyExpression(policy.Using, tableSchema) + policy.WithCheck = normalizePolicyExpression(policy.WithCheck, tableSchema) } // normalizePolicyRoles normalizes policy roles for consistent comparison @@ -229,7 +230,8 @@ 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 -func normalizePolicyExpression(expr string) string { +// tableSchema is used to strip same-schema qualifiers from function calls (Issue #220) +func normalizePolicyExpression(expr string, tableSchema string) string { if expr == "" { return expr } @@ -238,6 +240,16 @@ func normalizePolicyExpression(expr string) string { expr = strings.TrimSpace(expr) expr = regexp.MustCompile(`\s+`).ReplaceAllString(expr, " ") + // Strip same-schema qualifiers from function calls (Issue #220) + // This matches PostgreSQL's behavior where same-schema qualifiers are stripped + // Example: tenant1.auth_uid() -> auth_uid() (when tableSchema is "tenant1") + // util.get_status() -> util.get_status() (preserved, different schema) + if tableSchema != "" && strings.Contains(expr, tableSchema+".") { + prefix := tableSchema + "." + pattern := regexp.MustCompile(regexp.QuoteMeta(prefix) + `([a-zA-Z_][a-zA-Z0-9_]*)\(`) + expr = pattern.ReplaceAllString(expr, `${1}(`) + } + // Handle all parentheses normalization (adding required ones, removing unnecessary ones) expr = normalizeExpressionParentheses(expr) diff --git a/testdata/dump/tenant/pgschema.sql b/testdata/dump/tenant/pgschema.sql index 6a41eebe..3f9283fd 100644 --- a/testdata/dump/tenant/pgschema.sql +++ b/testdata/dump/tenant/pgschema.sql @@ -74,6 +74,18 @@ CREATE TABLE IF NOT EXISTS posts ( ALTER TABLE posts ADD CONSTRAINT posts_author_id_fkey FOREIGN KEY (author_id) REFERENCES users (id); +-- +-- Name: auth_uid(); Type: FUNCTION; Schema: -; Owner: - +-- + +CREATE OR REPLACE FUNCTION auth_uid() +RETURNS integer +LANGUAGE sql +STABLE +AS $$ + SELECT 1 +$$; + -- -- Name: create_task_assignment(text, priority_level, integer); Type: FUNCTION; Schema: -; Owner: - -- @@ -180,3 +192,15 @@ CREATE TABLE IF NOT EXISTS users ( CREATE INDEX IF NOT EXISTS idx_users_email ON users (email); +-- +-- Name: users; Type: RLS; Schema: -; Owner: - +-- + +ALTER TABLE users ENABLE ROW LEVEL SECURITY; + +-- +-- Name: users_isolation; Type: POLICY; Schema: -; Owner: - +-- + +CREATE POLICY users_isolation ON users TO PUBLIC USING (id = auth_uid()); + diff --git a/testdata/dump/tenant/tenant.sql b/testdata/dump/tenant/tenant.sql index 319c835b..7073d56a 100644 --- a/testdata/dump/tenant/tenant.sql +++ b/testdata/dump/tenant/tenant.sql @@ -117,4 +117,22 @@ BEGIN RAISE NOTICE 'Assigning task % with priority % to %', task_id, priority, assignment.assignee_name; END; -$$; \ No newline at end of file +$$; + +-- Function to simulate auth.uid() pattern (Issue #220) +CREATE FUNCTION auth_uid() +RETURNS integer +LANGUAGE sql +STABLE +AS $$ + SELECT 1 +$$; + +-- Enable RLS and add policy using same-schema function (Issue #220) +-- Bug: perpetual diff between `schema.auth_uid()` and `auth_uid()` +ALTER TABLE users ENABLE ROW LEVEL SECURITY; + +CREATE POLICY users_isolation ON users + FOR ALL + TO PUBLIC + USING (id = auth_uid()); \ No newline at end of file