Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions ir/normalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -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
}
Expand All @@ -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)

Expand Down
24 changes: 24 additions & 0 deletions testdata/dump/tenant/pgschema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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: -
--
Expand Down Expand Up @@ -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());

20 changes: 19 additions & 1 deletion testdata/dump/tenant/tenant.sql
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,22 @@ BEGIN
RAISE NOTICE 'Assigning task % with priority % to %',
task_id, priority, assignment.assignee_name;
END;
$$;
$$;

-- 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());