Skip to content

Commit 9e03a73

Browse files
craig[bot]spilchen
andcommitted
Merge #148203
148203: sql/rls: push leakproof projections through permeable Barriers r=spilchen a=spilchen Adds a normalization rule to push a Project operator below a Barrier when all projection expressions are leakproof and the Barrier is marked as permeable. This is needed to optimizer RLS queries since those have Barriers added to support the RLS filter. This rule will help bubble the Barrier up the plan tree. The rule is applied if all projections are leakproof. I wasn't able to split the projection into leakproof and non-leakproof like the PushLeakproofFiltersIntoPermeableBarrier rule does for filter. I didn't know how to split the passthrough columns that go along with the projection. Informs #146952 Epic: CRDB-48807 Release note: none Co-authored-by: Matt Spilchen <[email protected]>
2 parents 20c631c + 245a8bd commit 9e03a73

File tree

5 files changed

+174
-25
lines changed

5 files changed

+174
-25
lines changed

pkg/sql/opt/norm/project_funcs.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,3 +962,14 @@ func (c *CustomFuncs) UnbindFiltersFromProjections(
962962
newFilters := c.f.RemapCols(&filters, colMap).(*memo.FiltersExpr)
963963
return *newFilters
964964
}
965+
966+
// HasAllLeakProofProjections returns true if every projection given uses
967+
// leakproof expressions.
968+
func (c *CustomFuncs) HasAllLeakProofProjections(projections memo.ProjectionsExpr) bool {
969+
for i := range projections {
970+
if !projections[i].ScalarProps().VolatilitySet.IsLeakproof() {
971+
return false
972+
}
973+
}
974+
return true
975+
}

pkg/sql/opt/norm/rules/project.opt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,3 +351,23 @@ $input
351351
(FoldIsNullProjectionsItems $projections $input)
352352
$passthrough
353353
)
354+
355+
# PushLeakproofProjectionsIntoPermeableBarrier moves a Project below a Barrier
356+
# when all projection expressions are leakproof and the Barrier is marked as
357+
# permeable. This is safe because leakproof expressions cannot reveal
358+
# information through their evaluation, and a permeable Barrier allows such
359+
# projections to pass through it.
360+
[PushLeakproofProjectionsIntoPermeableBarrier, Normalize]
361+
(Project
362+
(Barrier
363+
$input:*
364+
$leakproofPermeable:* & (If $leakproofPermeable)
365+
)
366+
$projections:* & (HasAllLeakProofProjections $projections)
367+
$passthrough:*
368+
)
369+
=>
370+
(Barrier
371+
(Project $input $projections $passthrough)
372+
$leakproofPermeable
373+
)

pkg/sql/opt/norm/testdata/rules/project

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,30 @@ CREATE TABLE assn_cast (
3131
)
3232
----
3333

34+
exec-ddl
35+
CREATE TABLE rls (x INT PRIMARY KEY, y INT, alice_has_access BOOL);
36+
----
37+
38+
exec-ddl
39+
CREATE INDEX ON rls(y);
40+
----
41+
42+
exec-ddl
43+
ALTER TABLE rls ENABLE ROW LEVEL SECURITY;
44+
----
45+
46+
exec-ddl
47+
CREATE POLICY select_policy_alice
48+
ON rls
49+
FOR SELECT
50+
TO alice
51+
USING (alice_has_access);
52+
----
53+
54+
exec-ddl
55+
CREATE ROLE alice;
56+
----
57+
3458
# --------------------------------------------------
3559
# EliminateJoinUnderProjectLeft
3660
# --------------------------------------------------
@@ -2018,3 +2042,103 @@ sort
20182042
│ └── fd: (1)-->(2)
20192043
└── projections
20202044
└── y:2 IS NULL [as=nulls_ordering_y:7, outer=(2)]
2045+
2046+
# --------------------------------------------------
2047+
# PushLeakproofProjectionsIntoPermeableBarrier
2048+
# --------------------------------------------------
2049+
2050+
exec-ddl
2051+
SET ROLE alice;
2052+
----
2053+
2054+
# All expressions in projection are leakproof
2055+
norm expect=PushLeakproofProjectionsIntoPermeableBarrier
2056+
SELECT CASE WHEN y = 20 THEN 10 ELSE 0 END FROM rls
2057+
----
2058+
barrier
2059+
├── columns: case:6
2060+
└── project
2061+
├── columns: case:6
2062+
├── select
2063+
│ ├── columns: y:2 alice_has_access:3!null
2064+
│ ├── fd: ()-->(3)
2065+
│ ├── scan rls
2066+
│ │ └── columns: y:2 alice_has_access:3
2067+
│ └── filters
2068+
│ └── alice_has_access:3 [outer=(3), constraints=(/3: [/true - /true]; tight), fd=()-->(3)]
2069+
└── projections
2070+
└── CASE WHEN y:2 = 20 THEN 10 ELSE 0 END [as=case:6, outer=(2)]
2071+
2072+
# Only some expressions in projection are leakproof
2073+
norm expect-not=PushLeakproofProjectionsIntoPermeableBarrier
2074+
SELECT CASE WHEN y = 20 THEN 10 ELSE 0 END, y/0 FROM rls
2075+
----
2076+
project
2077+
├── columns: case:6 "?column?":7
2078+
├── immutable
2079+
├── barrier
2080+
│ ├── columns: x:1!null y:2 alice_has_access:3!null crdb_internal_mvcc_timestamp:4 tableoid:5
2081+
│ ├── key: (1)
2082+
│ ├── fd: ()-->(3), (1)-->(2,4,5)
2083+
│ └── select
2084+
│ ├── columns: x:1!null y:2 alice_has_access:3!null crdb_internal_mvcc_timestamp:4 tableoid:5
2085+
│ ├── key: (1)
2086+
│ ├── fd: ()-->(3), (1)-->(2,4,5)
2087+
│ ├── scan rls
2088+
│ │ ├── columns: x:1!null y:2 alice_has_access:3 crdb_internal_mvcc_timestamp:4 tableoid:5
2089+
│ │ ├── key: (1)
2090+
│ │ └── fd: (1)-->(2-5)
2091+
│ └── filters
2092+
│ └── alice_has_access:3 [outer=(3), constraints=(/3: [/true - /true]; tight), fd=()-->(3)]
2093+
└── projections
2094+
├── CASE WHEN y:2 = 20 THEN 10 ELSE 0 END [as=case:6, outer=(2)]
2095+
└── y:2 / 0 [as="?column?":7, outer=(2), immutable]
2096+
2097+
# All expressions in projection are not leakproof
2098+
norm expect-not=PushLeakproofProjectionsIntoPermeableBarrier
2099+
SELECT y/0 FROM rls
2100+
----
2101+
project
2102+
├── columns: "?column?":6
2103+
├── immutable
2104+
├── barrier
2105+
│ ├── columns: x:1!null y:2 alice_has_access:3!null crdb_internal_mvcc_timestamp:4 tableoid:5
2106+
│ ├── key: (1)
2107+
│ ├── fd: ()-->(3), (1)-->(2,4,5)
2108+
│ └── select
2109+
│ ├── columns: x:1!null y:2 alice_has_access:3!null crdb_internal_mvcc_timestamp:4 tableoid:5
2110+
│ ├── key: (1)
2111+
│ ├── fd: ()-->(3), (1)-->(2,4,5)
2112+
│ ├── scan rls
2113+
│ │ ├── columns: x:1!null y:2 alice_has_access:3 crdb_internal_mvcc_timestamp:4 tableoid:5
2114+
│ │ ├── key: (1)
2115+
│ │ └── fd: (1)-->(2-5)
2116+
│ └── filters
2117+
│ └── alice_has_access:3 [outer=(3), constraints=(/3: [/true - /true]; tight), fd=()-->(3)]
2118+
└── projections
2119+
└── y:2 / 0 [as="?column?":6, outer=(2), immutable]
2120+
2121+
# All expressions in projection are leakproof and we have passthrough columns
2122+
norm expect=PushLeakproofProjectionsIntoPermeableBarrier
2123+
SELECT x, y, CASE WHEN y = 20 THEN 10 ELSE 0 END FROM rls
2124+
----
2125+
barrier
2126+
├── columns: x:1!null y:2 case:6
2127+
├── key: (1)
2128+
├── fd: (1)-->(2), (2)-->(6)
2129+
└── project
2130+
├── columns: case:6 x:1!null y:2
2131+
├── key: (1)
2132+
├── fd: (1)-->(2), (2)-->(6)
2133+
├── select
2134+
│ ├── columns: x:1!null y:2 alice_has_access:3!null
2135+
│ ├── key: (1)
2136+
│ ├── fd: ()-->(3), (1)-->(2)
2137+
│ ├── scan rls
2138+
│ │ ├── columns: x:1!null y:2 alice_has_access:3
2139+
│ │ ├── key: (1)
2140+
│ │ └── fd: (1)-->(2,3)
2141+
│ └── filters
2142+
│ └── alice_has_access:3 [outer=(3), constraints=(/3: [/true - /true]; tight), fd=()-->(3)]
2143+
└── projections
2144+
└── CASE WHEN y:2 = 20 THEN 10 ELSE 0 END [as=case:6, outer=(2)]

pkg/sql/opt/norm/testdata/rules/select

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2547,22 +2547,18 @@ project
25472547
norm expect=PushLeakproofFiltersIntoPermeableBarrier
25482548
SELECT * FROM rls WHERE y = 20;
25492549
----
2550-
project
2550+
barrier
25512551
├── columns: x:1!null y:2!null alice_has_access:3!null
25522552
├── key: (1)
25532553
├── fd: ()-->(2,3)
2554-
└── barrier
2555-
├── columns: x:1!null y:2!null alice_has_access:3!null crdb_internal_mvcc_timestamp:4 tableoid:5
2554+
└── select
2555+
├── columns: x:1!null y:2!null alice_has_access:3!null
25562556
├── key: (1)
2557-
├── fd: ()-->(2,3), (1)-->(4,5)
2558-
└── select
2559-
├── columns: x:1!null y:2!null alice_has_access:3!null crdb_internal_mvcc_timestamp:4 tableoid:5
2560-
├── key: (1)
2561-
├── fd: ()-->(2,3), (1)-->(4,5)
2562-
├── scan rls
2563-
│ ├── columns: x:1!null y:2 alice_has_access:3 crdb_internal_mvcc_timestamp:4 tableoid:5
2564-
│ ├── key: (1)
2565-
│ └── fd: (1)-->(2-5)
2566-
└── filters
2567-
├── alice_has_access:3 [outer=(3), constraints=(/3: [/true - /true]; tight), fd=()-->(3)]
2568-
└── y:2 = 20 [outer=(2), constraints=(/2: [/20 - /20]; tight), fd=()-->(2)]
2557+
├── fd: ()-->(2,3)
2558+
├── scan rls
2559+
│ ├── columns: x:1!null y:2 alice_has_access:3
2560+
│ ├── key: (1)
2561+
│ └── fd: (1)-->(2,3)
2562+
└── filters
2563+
├── alice_has_access:3 [outer=(3), constraints=(/3: [/true - /true]; tight), fd=()-->(3)]
2564+
└── y:2 = 20 [outer=(2), constraints=(/2: [/20 - /20]; tight), fd=()-->(2)]

pkg/sql/opt/optbuilder/testdata/row_level_security

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2110,17 +2110,15 @@ CREATE POLICY p1 ON ob USING (user_has_access);
21102110
norm
21112111
SELECT * FROM ob WHERE y = 20;
21122112
----
2113-
project
2113+
barrier
21142114
├── columns: x:1!null y:2!null user_has_access:3!null
2115-
└── barrier
2116-
├── columns: x:1!null y:2!null user_has_access:3!null crdb_internal_mvcc_timestamp:4 tableoid:5
2117-
└── select
2118-
├── columns: x:1!null y:2!null user_has_access:3!null crdb_internal_mvcc_timestamp:4 tableoid:5
2119-
├── scan ob
2120-
│ └── columns: x:1!null y:2 user_has_access:3 crdb_internal_mvcc_timestamp:4 tableoid:5
2121-
└── filters
2122-
├── user_has_access:3
2123-
└── y:2 = 20
2115+
└── select
2116+
├── columns: x:1!null y:2!null user_has_access:3!null
2117+
├── scan ob
2118+
│ └── columns: x:1!null y:2 user_has_access:3
2119+
└── filters
2120+
├── user_has_access:3
2121+
└── y:2 = 20
21242122

21252123
# Barrier needed. Try to expose with a division by zero error.
21262124
norm

0 commit comments

Comments
 (0)