Skip to content

Commit 85133e9

Browse files
committed
sql/rls: refactor rls filter injection
This does a refactor of the logic that adds the filter for row-level security (RLS) policies. The intent is to match what we did for the check constraint. Namely a single function (addRowLevelSecurityFilter) that you can quickly see at a glance what policies are applied to the various policy command scope. There is a slight update to the optbuilder tests as we include redundant clauses that didn't exist before. These will get optimized out. Epic: none Release note: none
1 parent 075c4bc commit 85133e9

File tree

4 files changed

+55
-56
lines changed

4 files changed

+55
-56
lines changed

pkg/sql/opt/exec/explain/testdata/row_level_security

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ UPDATE writer SET key = key * 10;
293293
└── • render
294294
295295
└── • norows
296-
policies: row-level security enabled, no policies applied.
296+
policies: applied (filtered all rows)
297297

298298
exec-ddl
299299
CREATE POLICY p_update1 ON writer FOR UPDATE USING (key = 5) WITH CHECK (value like 'updater: %')
@@ -396,6 +396,11 @@ UPDATE writer SET key = 10 WHERE true;
396396
# Show policy information for delete
397397
# ----------------------------------------------------------------------
398398

399+
# First ensure no policies apply.
400+
exec-ddl
401+
DROP POLICY p_select ON writer;
402+
----
403+
399404
plan
400405
DELETE FROM writer WHERE key = 1;
401406
----
@@ -406,6 +411,22 @@ DELETE FROM writer WHERE key = 1;
406411
└── • norows
407412
policies: row-level security enabled, no policies applied.
408413

414+
# Create a SELECT policy again. But still no delete policy, so everything is
415+
# filtered.
416+
exec-ddl
417+
CREATE POLICY p_select ON writer FOR SELECT USING (true);
418+
----
419+
420+
plan
421+
DELETE FROM writer WHERE key = 1;
422+
----
423+
• delete
424+
│ from: writer
425+
│ auto commit
426+
427+
└── • norows
428+
policies: applied (filtered all rows)
429+
409430
exec-ddl
410431
CREATE POLICY p_delete ON writer FOR DELETE USING (key = 1);
411432
----

pkg/sql/opt/optbuilder/row_level_security.go

Lines changed: 27 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,28 @@ func (b *Builder) addRowLevelSecurityFilter(
2828
if b.isExemptFromRLSPolicies(tabMeta, cmdScope) {
2929
return
3030
}
31-
scalar := b.buildRowLevelSecurityUsingExpression(tabMeta, tableScope, cmdScope)
32-
if scalar != nil {
33-
tableScope.expr = b.factory.ConstructSelect(tableScope.expr,
34-
memo.FiltersExpr{b.factory.ConstructFiltersItem(scalar)})
31+
32+
var scalar opt.ScalarExpr
33+
switch cmdScope {
34+
case cat.PolicyScopeSelect:
35+
scalar = b.genPolicyUsingExpr(tabMeta, tableScope, cat.PolicyScopeSelect)
36+
case cat.PolicyScopeUpdate:
37+
scalar = b.factory.ConstructAnd(
38+
b.genPolicyUsingExpr(tabMeta, tableScope, cat.PolicyScopeSelect),
39+
b.genPolicyUsingExpr(tabMeta, tableScope, cat.PolicyScopeUpdate),
40+
)
41+
case cat.PolicyScopeDelete:
42+
scalar = b.factory.ConstructAnd(
43+
b.genPolicyUsingExpr(tabMeta, tableScope, cat.PolicyScopeSelect),
44+
b.genPolicyUsingExpr(tabMeta, tableScope, cat.PolicyScopeDelete),
45+
)
46+
default:
47+
panic(errors.AssertionFailedf("unsupported policy command scope for filter: %v", cmdScope))
3548
}
49+
50+
tableScope.expr = b.factory.ConstructSelect(tableScope.expr,
51+
memo.FiltersExpr{b.factory.ConstructFiltersItem(scalar)})
52+
b.factory.Metadata().GetRLSMeta().RefreshNoPoliciesAppliedForTable(tabMeta.MetaID)
3653
}
3754

3855
// isExemptFromRLSPolicies will check if the given user is exempt from RLS policies.
@@ -65,52 +82,11 @@ func (b *Builder) isExemptFromRLSPolicies(
6582
return false
6683
}
6784

68-
// buildRowLevelSecurityUsingExpression generates a scalar expression for read
69-
// operations by combining all applicable RLS policies. An expression is always
70-
// returned; if no policies apply, a 'false' expression is returned.
71-
func (b *Builder) buildRowLevelSecurityUsingExpression(
72-
tabMeta *opt.TableMeta, tableScope *scope, cmdScope cat.PolicyCommandScope,
73-
) opt.ScalarExpr {
74-
combinedExpr := b.combinePolicyUsingExpressionForCommand(tabMeta, tableScope, cmdScope)
75-
if combinedExpr == nil {
76-
// No policies, filter out all rows by adding a "false" expression.
77-
b.factory.Metadata().GetRLSMeta().NoPoliciesApplied = true
78-
return memo.FalseSingleton
79-
}
80-
return b.buildScalar(combinedExpr, tableScope, nil, nil, nil)
81-
}
82-
83-
// combinePolicyUsingExpressionForCommand generates a `tree.TypedExpr` command
84-
// for use in the scan. Depending on the policy command scope, it may simply
85-
// pass through to `buildRowLevelSecurityUsingExpressionForCommand`.
86-
// For other commands, the process is more complex and may involve multiple
87-
// calls to that function to generate the expression.
88-
func (b *Builder) combinePolicyUsingExpressionForCommand(
89-
tabMeta *opt.TableMeta, tableScope *scope, cmdScope cat.PolicyCommandScope,
90-
) tree.TypedExpr {
91-
// For DELETE and UPDATE, we always apply SELECT/ALL policies because
92-
// reading the existing row is necessary before performing the write.
93-
switch cmdScope {
94-
case cat.PolicyScopeUpdate, cat.PolicyScopeDelete:
95-
selectExpr := b.buildRowLevelSecurityUsingExpressionForCommand(tabMeta, tableScope, cat.PolicyScopeSelect)
96-
if selectExpr == nil {
97-
return selectExpr
98-
}
99-
updateExpr := b.buildRowLevelSecurityUsingExpressionForCommand(tabMeta, tableScope, cmdScope)
100-
if updateExpr == nil {
101-
return nil
102-
}
103-
return tree.NewTypedAndExpr(selectExpr, updateExpr)
104-
default:
105-
return b.buildRowLevelSecurityUsingExpressionForCommand(tabMeta, tableScope, cmdScope)
106-
}
107-
}
108-
109-
// buildRowLevelSecurityUsingExpressionForCommand will generate a tree.TypedExpr
85+
// genPolicyUsingExpr will generate a opt.ScalarExpr
11086
// for all policies that apply to the given policy command scope.
111-
func (b *Builder) buildRowLevelSecurityUsingExpressionForCommand(
87+
func (b *Builder) genPolicyUsingExpr(
11288
tabMeta *opt.TableMeta, tableScope *scope, cmdScope cat.PolicyCommandScope,
113-
) tree.TypedExpr {
89+
) opt.ScalarExpr {
11490
var policiesUsed opt.PolicyIDSet
11591
var combinedExpr tree.TypedExpr
11692
policies := tabMeta.Table.Policies()
@@ -147,8 +123,8 @@ func (b *Builder) buildRowLevelSecurityUsingExpressionForCommand(
147123
buildForPolicy(policy, false /* restrictive */)
148124
}
149125
if combinedExpr == nil {
150-
// No permissive policies. Return an empty expr to force the caller to generate a deny-all expression.
151-
return nil
126+
// No permissive policies. Return the false expr as a deny-all expression.
127+
return memo.FalseSingleton
152128
}
153129
for _, policy := range policies.Restrictive {
154130
buildForPolicy(policy, true /* restrictive */)
@@ -159,7 +135,7 @@ func (b *Builder) buildRowLevelSecurityUsingExpressionForCommand(
159135
panic(errors.AssertionFailedf("at least one applicable policy should have been found"))
160136
}
161137
b.factory.Metadata().GetRLSMeta().AddPoliciesUsed(tabMeta.MetaID, policiesUsed, true /* applyFilterExpr */)
162-
return combinedExpr
138+
return b.buildScalar(combinedExpr, tableScope, nil, nil, nil)
163139
}
164140

165141
// policyAppliesToCommandScope checks whether a given PolicyCommandScope applies

pkg/sql/opt/optbuilder/testdata/row_level_security

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ update t1
115115
│ │ │ │ ├── columns: c1:7 c2:8 c3:9 rowid:10!null crdb_internal_mvcc_timestamp:11 tableoid:12
116116
│ │ │ │ └── flags: avoid-full-scan
117117
│ │ │ └── filters
118-
│ │ │ └── false
118+
│ │ │ └── ((c1:7 % 2) = 0) AND false
119119
│ │ └── filters
120120
│ │ └── c1:7 > 0
121121
│ └── projections
@@ -516,7 +516,7 @@ delete t1
516516
│ │ ├── columns: c1:7 c2:8 c3:9 rowid:10!null crdb_internal_mvcc_timestamp:11 tableoid:12
517517
│ │ └── flags: avoid-full-scan
518518
│ └── filters
519-
│ └── false
519+
│ └── ((c1:7 % 2) = 0) AND false
520520
└── filters
521521
└── (c1:7 >= 0) AND (c1:7 <= 20)
522522

@@ -781,7 +781,7 @@ update t1
781781
│ │ │ ├── columns: c1:7 c2:8 c3:9 rowid:10!null crdb_internal_mvcc_timestamp:11 tableoid:12
782782
│ │ │ └── flags: avoid-full-scan
783783
│ │ └── filters
784-
│ │ └── false
784+
│ │ └── true AND false
785785
│ └── projections
786786
│ └── 'new val' [as=c2_new:13]
787787
└── projections

pkg/sql/opt/row_level_security.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,10 @@ func (r *RowLevelSecurityMeta) GetPoliciesUsed(
9999
// is complete for a given table. If no policies were recorded for the table,
100100
// it updates the 'NoPoliciesApplied' flag to indicate this.
101101
func (r *RowLevelSecurityMeta) RefreshNoPoliciesAppliedForTable(tableID TableID) {
102-
if _, ok := r.PoliciesApplied[tableID]; !ok {
102+
if a, ok := r.PoliciesApplied[tableID]; !ok {
103103
r.NoPoliciesApplied = true
104+
} else {
105+
r.NoPoliciesApplied = a.Filter.Len() == 0 && a.Check.Len() == 0
104106
}
105107
}
106108

0 commit comments

Comments
 (0)