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
4 changes: 2 additions & 2 deletions internal/diff/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func generatePolicySQL(policy *ir.RLSPolicy, targetSchema string) string {
// Only include table name without schema if it's in the target schema
tableName := getTableNameWithSchema(policy.Schema, policy.Table, targetSchema)

policyStmt := fmt.Sprintf("CREATE POLICY %s ON %s", policy.Name, tableName)
policyStmt := fmt.Sprintf("CREATE POLICY %s ON %s", ir.QuoteIdentifier(policy.Name), tableName)

// Add command type if specified
if policy.Command != ir.PolicyCommandAll {
Expand Down Expand Up @@ -104,7 +104,7 @@ func generateAlterPolicySQL(old, new *ir.RLSPolicy, targetSchema string) string
withCheckChange := old.WithCheck != new.WithCheck

// Build ALTER POLICY statement with all changes
alterStmt := fmt.Sprintf("ALTER POLICY %s ON %s", new.Name, tableName)
alterStmt := fmt.Sprintf("ALTER POLICY %s ON %s", ir.QuoteIdentifier(new.Name), tableName)

// Add TO clause if roles changed
if roleChange {
Expand Down
4 changes: 2 additions & 2 deletions internal/diff/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -915,7 +915,7 @@ func (td *tableDiff) generateAlterTableStatements(targetSchema string, collector
// Drop policies - already sorted by the Diff operation
for _, policy := range td.DroppedPolicies {
tableName := getTableNameWithSchema(td.Table.Schema, td.Table.Name, targetSchema)
sql := fmt.Sprintf("DROP POLICY IF EXISTS %s ON %s;", policy.Name, tableName)
sql := fmt.Sprintf("DROP POLICY IF EXISTS %s ON %s;", ir.QuoteIdentifier(policy.Name), tableName)

context := &diffContext{
Type: DiffTypeTablePolicy,
Expand Down Expand Up @@ -1006,7 +1006,7 @@ func (td *tableDiff) generateAlterTableStatements(targetSchema string, collector
if needsRecreate(policyDiff.Old, policyDiff.New) {
tableName := getTableNameWithSchema(td.Table.Schema, td.Table.Name, targetSchema)
// Drop and recreate policy for modification
sql := fmt.Sprintf("DROP POLICY IF EXISTS %s ON %s;", policyDiff.Old.Name, tableName)
sql := fmt.Sprintf("DROP POLICY IF EXISTS %s ON %s;", ir.QuoteIdentifier(policyDiff.Old.Name), tableName)

context := &diffContext{
Type: DiffTypeTablePolicy,
Expand Down
5 changes: 4 additions & 1 deletion testdata/diff/create_policy/add_policy/diff.sql
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
CREATE POLICY user_tenant_isolation ON users TO PUBLIC USING (tenant_id = current_setting('app.current_tenant')::integer);
CREATE POLICY "UserPolicy" ON users TO PUBLIC USING (tenant_id = current_setting('app.current_tenant')::integer);
CREATE POLICY "my-policy" ON users FOR INSERT TO PUBLIC WITH CHECK ((role)::text = 'user');
CREATE POLICY "select" ON users FOR SELECT TO PUBLIC USING (true);
CREATE POLICY user_tenant_isolation ON users FOR UPDATE TO PUBLIC USING (tenant_id = current_setting('app.current_tenant')::integer);
28 changes: 24 additions & 4 deletions testdata/diff/create_policy/add_policy/new.sql
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
tenant_id INTEGER NOT NULL
tenant_id INTEGER NOT NULL,
role VARCHAR(50) NOT NULL
);

-- RLS is enabled with new policy
-- RLS is enabled with multiple policies demonstrating quoting scenarios
ALTER TABLE users ENABLE ROW LEVEL SECURITY;

CREATE POLICY user_tenant_isolation ON users
-- Policy with reserved word name (requires quoting)
CREATE POLICY "select" ON users
FOR SELECT
TO PUBLIC
USING (true);

-- Policy with mixed case name (requires quoting to preserve case)
CREATE POLICY "UserPolicy" ON users
FOR ALL
TO PUBLIC
USING (tenant_id = current_setting('app.current_tenant')::INTEGER);
USING (tenant_id = current_setting('app.current_tenant')::INTEGER);

-- Policy with special character in name (requires quoting)
CREATE POLICY "my-policy" ON users
FOR INSERT
TO PUBLIC
WITH CHECK (role = 'user');

-- Policy with regular snake_case name (no quoting needed)
CREATE POLICY user_tenant_isolation ON users
FOR UPDATE
TO PUBLIC
USING (tenant_id = current_setting('app.current_tenant')::INTEGER);
5 changes: 3 additions & 2 deletions testdata/diff/create_policy/add_policy/old.sql
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
tenant_id INTEGER NOT NULL
tenant_id INTEGER NOT NULL,
role VARCHAR(50) NOT NULL
);

-- RLS is enabled but no policies exist yet
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
22 changes: 20 additions & 2 deletions testdata/diff/create_policy/add_policy/plan.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,31 @@
"pgschema_version": "1.5.0",
"created_at": "1970-01-01T00:00:00Z",
"source_fingerprint": {
"hash": "fb10f588a3287058ca5ba2bf9491dabe163e388b42043ebc878fadca3ef22c5c"
"hash": "dbb8387a973c070eb3a87e82500e6c4342a795d3271927271e1831dd101e2920"
},
"groups": [
{
"steps": [
{
"sql": "CREATE POLICY user_tenant_isolation ON users TO PUBLIC USING (tenant_id = current_setting('app.current_tenant')::integer);",
"sql": "CREATE POLICY \"UserPolicy\" ON users TO PUBLIC USING (tenant_id = current_setting('app.current_tenant')::integer);",
"type": "table.policy",
"operation": "create",
"path": "public.users.UserPolicy"
},
{
"sql": "CREATE POLICY \"my-policy\" ON users FOR INSERT TO PUBLIC WITH CHECK ((role)::text = 'user');",
"type": "table.policy",
"operation": "create",
"path": "public.users.my-policy"
},
{
"sql": "CREATE POLICY \"select\" ON users FOR SELECT TO PUBLIC USING (true);",
"type": "table.policy",
"operation": "create",
"path": "public.users.select"
},
{
"sql": "CREATE POLICY user_tenant_isolation ON users FOR UPDATE TO PUBLIC USING (tenant_id = current_setting('app.current_tenant')::integer);",
"type": "table.policy",
"operation": "create",
"path": "public.users.user_tenant_isolation"
Expand Down
8 changes: 7 additions & 1 deletion testdata/diff/create_policy/add_policy/plan.sql
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
CREATE POLICY user_tenant_isolation ON users TO PUBLIC USING (tenant_id = current_setting('app.current_tenant')::integer);
CREATE POLICY "UserPolicy" ON users TO PUBLIC USING (tenant_id = current_setting('app.current_tenant')::integer);

CREATE POLICY "my-policy" ON users FOR INSERT TO PUBLIC WITH CHECK ((role)::text = 'user');

CREATE POLICY "select" ON users FOR SELECT TO PUBLIC USING (true);

CREATE POLICY user_tenant_isolation ON users FOR UPDATE TO PUBLIC USING (tenant_id = current_setting('app.current_tenant')::integer);
11 changes: 10 additions & 1 deletion testdata/diff/create_policy/add_policy/plan.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,18 @@ Summary by type:

Tables:
~ users
+ UserPolicy (policy)
+ my-policy (policy)
+ select (policy)
+ user_tenant_isolation (policy)

DDL to be executed:
--------------------------------------------------

CREATE POLICY user_tenant_isolation ON users TO PUBLIC USING (tenant_id = current_setting('app.current_tenant')::integer);
CREATE POLICY "UserPolicy" ON users TO PUBLIC USING (tenant_id = current_setting('app.current_tenant')::integer);

CREATE POLICY "my-policy" ON users FOR INSERT TO PUBLIC WITH CHECK ((role)::text = 'user');

CREATE POLICY "select" ON users FOR SELECT TO PUBLIC USING (true);

CREATE POLICY user_tenant_isolation ON users FOR UPDATE TO PUBLIC USING (tenant_id = current_setting('app.current_tenant')::integer);