Skip to content

Commit 96c41d0

Browse files
committed
Fix updating of two single links with linkprops at once (#8932)
Mutating a single link with link properties works by compiling the actual expression that defines the value into the link table update, and then copying that value into the table itself by (ab/re)using the rewrite machinery. The rewrite machinery uses a "policy_ctx", which uses one of the input tables as part of the overlay instead of an output table. The "policy_ctx" has its overlay tables copied before being updated, but it does the copy "just in time", which means that you can sometimes get away with depending on things added to the context *after* the policy_ctx was created. Of course, this only worked *once*, after which the copy had been made. As a result there was a bug where an 'except' overlay wasn't being applied when populating a single link. Be more explicit about all this by populating *both* overlay tables. Also, it turns out that we do depend on incorporating unrelated changes made to the overlay tables during compilation (like if an INSERT is happening nested inside other DML), so we need to explicitly reapply the policy overlays to the main one. (There was an existing bug caused by this that this change made appear more easily.) Possibly also fix some other policy related issues, since the code is shared, but I haven't tracked it down too much.
1 parent d94f5e8 commit 96c41d0

File tree

4 files changed

+85
-10
lines changed

4 files changed

+85
-10
lines changed

edb/pgsql/compiler/dml.py

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,7 @@ def process_insert_body(
810810
# use later. We do this in advance so that the link update code
811811
# can populate overlay fields in it.
812812
with ctx.new() as pol_ctx:
813-
pass
813+
pol_ctx.rel_overlays = context.RelOverlays()
814814

815815
needs_insert_on_conflict = bool(
816816
ir_stmt.on_conflict and not on_conflict_fake_iterator)
@@ -861,6 +861,11 @@ def _update_link_tables(inp_cte: pgast.CommonTableExpr) -> None:
861861
assert not needs_insert_on_conflict
862862

863863
assert pol_ctx
864+
# Now that all the compilation for the INSERT has been done,
865+
# apply the tweaked policy overlays.
866+
pol_ctx.rel_overlays = update_overlay(
867+
ctx.rel_overlays, pol_ctx.rel_overlays
868+
)
864869
with pol_ctx.reenter(), pol_ctx.newrel() as rctx:
865870
# Pull in ptr rel overlays, so we can see the pointers
866871
merge_overlays_globally((ir_stmt,), ctx=rctx)
@@ -1192,6 +1197,25 @@ def merge_overlays_globally(
11921197
ctx.rel_overlays.ptr = ctx.rel_overlays.ptr.set(None, ptr_overlay)
11931198

11941199

1200+
def update_overlay(
1201+
left: context.RelOverlays,
1202+
right: context.RelOverlays,
1203+
) -> context.RelOverlays:
1204+
'''Make an updated copy of the left overlay using right.'''
1205+
left = left.copy()
1206+
1207+
for ir_stmt, tm in right.type.items():
1208+
ltm = left.type.get(ir_stmt, immu.Map())
1209+
ltm = ltm.update(tm)
1210+
left.type = left.type.set(ir_stmt, ltm)
1211+
for ir_stmt, pm in right.ptr.items():
1212+
lpm = left.ptr.get(ir_stmt, immu.Map())
1213+
lpm = lpm.update(pm)
1214+
left.ptr = left.ptr.set(ir_stmt, lpm)
1215+
1216+
return left
1217+
1218+
11951219
def compile_policy_check(
11961220
dml_cte: pgast.CommonTableExpr,
11971221
ir_stmt: irast.MutatingStmt,
@@ -1752,7 +1776,7 @@ def process_update_body(
17521776
# use later. We do this in advance so that the link update code
17531777
# can populate overlay fields in it.
17541778
with ctx.new() as pol_ctx:
1755-
pass
1779+
pol_ctx.rel_overlays = context.RelOverlays()
17561780

17571781
no_update = not values and not rewrites and not single_external
17581782
if no_update:
@@ -1806,6 +1830,11 @@ def process_update_body(
18061830
if rewrites or single_external:
18071831
rewrites = rewrites or {}
18081832
assert pol_ctx
1833+
# Now that all the compilation for the UPDATE has been done,
1834+
# apply the tweaked policy overlays.
1835+
pol_ctx.rel_overlays = update_overlay(
1836+
ctx.rel_overlays, pol_ctx.rel_overlays
1837+
)
18091838
with pol_ctx.reenter(), pol_ctx.new() as rctx:
18101839
merge_overlays_globally((ir_stmt,), ctx=rctx)
18111840
contents_cte, contents_rvar, values = process_update_rewrites(
@@ -2704,14 +2733,20 @@ def process_link_update(
27042733
# Record the effect of this removal in the relation overlay
27052734
# context to ensure that references to the link in the result
27062735
# of this DML statement yield the expected results.
2707-
relctx.add_ptr_rel_overlay(
2708-
mptrref,
2709-
context.OverlayOp.EXCEPT,
2710-
delcte,
2711-
path_id=path_id.ptr_path(),
2712-
dml_stmts=ctx.dml_stmt_stack,
2713-
ctx=ctx
2736+
except_overlay = (lambda octx:
2737+
relctx.add_ptr_rel_overlay(
2738+
mptrref,
2739+
context.OverlayOp.EXCEPT,
2740+
delcte,
2741+
path_id=path_id.ptr_path(),
2742+
dml_stmts=ctx.dml_stmt_stack,
2743+
ctx=octx,
2744+
)
27142745
)
2746+
except_overlay(ctx)
2747+
if policy_ctx:
2748+
except_overlay(policy_ctx)
2749+
27152750
toplevel.append_cte(delcte)
27162751
else:
27172752
delqry = None
@@ -2918,7 +2953,6 @@ def register_overlays(
29182953
)
29192954

29202955
if policy_ctx:
2921-
policy_ctx.rel_overlays = policy_ctx.rel_overlays.copy()
29222956
register_overlays(data_cte, policy_ctx)
29232957

29242958
register_overlays(update, ctx)

tests/schemas/updates.esdl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ type UpdateTest {
4444
link annotated_status -> Status {
4545
property note -> str;
4646
}
47+
link annotated_status2 -> Status {
48+
property note -> str;
49+
}
4750

4851
# for testing links to sets
4952
multi link tags -> Tag;

tests/test_edgeql_policies.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,18 @@ async def test_edgeql_policies_11(self):
794794
};
795795
''')
796796

797+
async with self.assertRaisesRegexTx(
798+
edgedb.InvalidValueError,
799+
r"access policy violation on insert of default::Issue"):
800+
await self.con.query('''
801+
insert Issue {
802+
name := '', body := '',
803+
watchers := {},
804+
status := (select Status filter .name = 'Open'), number := '',
805+
owner := (insert User { name := "???" }),
806+
};
807+
''')
808+
797809
async def test_edgeql_policies_12(self):
798810
await self.con.query('''
799811
create global cur_user_obj := (

tests/test_edgeql_update.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1794,6 +1794,32 @@ async def test_edgeql_update_props_10(self):
17941794
]
17951795
)
17961796

1797+
async def test_edgeql_update_props_11(self):
1798+
# Check that we can update a link property on a specific link.
1799+
1800+
# Setup some multi links
1801+
await self.con.execute(
1802+
r"""
1803+
UPDATE UpdateTest
1804+
FILTER .name = 'update-test1'
1805+
SET {
1806+
annotated_status := (select Status limit 1),
1807+
annotated_status2 := (select Status limit 1),
1808+
};
1809+
""",
1810+
)
1811+
1812+
await self.con.execute(
1813+
r"""
1814+
UPDATE UpdateTest
1815+
FILTER .name = 'update-test1'
1816+
SET {
1817+
annotated_status := (select Status limit 1),
1818+
annotated_status2 := (select Status limit 1),
1819+
};
1820+
""",
1821+
)
1822+
17971823
async def test_edgeql_update_for_01(self):
17981824
await self.assert_query_result(
17991825
r"""

0 commit comments

Comments
 (0)