Skip to content

Commit

Permalink
Make linkprops (including @source/@target) work in conflict selects (#…
Browse files Browse the repository at this point in the history
…7284)

Exclusive constraints on the linkprops themselves still don't work,
though it should be fixable.

Fixes #7263
  • Loading branch information
msullivan authored and vpetrovykh committed May 2, 2024
1 parent 10151ad commit 7184615
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 12 deletions.
1 change: 1 addition & 0 deletions edb/edgeql/compiler/conflicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ def _compile_conflict_select(
select_ast = qlast.Set(elements=frags)
with ctx.new() as ectx:
ectx.implicit_limit = 0
ectx.allow_endpoint_linkprops = True
select_ir = dispatch.compile(select_ast, ctx=ectx)
select_ir = setgen.scoped_set(
select_ir, force_reassign=True, ctx=ectx)
Expand Down
5 changes: 5 additions & 0 deletions edb/edgeql/compiler/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,9 @@ class ContextLevel(compiler.ContextLevel):
active_computeds: ordered.OrderedSet[s_pointers.Pointer]
"""A ordered set of currently compiling computeds"""

allow_endpoint_linkprops: bool
"""Whether to allow references to endpoint linkpoints (@source, @target)."""

disallow_dml: Optional[str]
"""Whether we are currently in a place where no dml is allowed,
if not None, then it is of the form `in a FILTER clause` """
Expand Down Expand Up @@ -623,6 +626,7 @@ def __init__(
self.active_rewrites = frozenset()
self.active_defaults = frozenset()

self.allow_endpoint_linkprops = False
self.disallow_dml = None

else:
Expand Down Expand Up @@ -666,6 +670,7 @@ def __init__(
self.active_rewrites = prevlevel.active_rewrites
self.active_defaults = prevlevel.active_defaults

self.allow_endpoint_linkprops = prevlevel.allow_endpoint_linkprops
self.disallow_dml = prevlevel.disallow_dml

if mode == ContextSwitchMode.SUBQUERY:
Expand Down
6 changes: 4 additions & 2 deletions edb/edgeql/compiler/inference/cardinality.py
Original file line number Diff line number Diff line change
Expand Up @@ -945,9 +945,11 @@ def extract_filters(
ptr = left_stype.getptr(schema, sn.UnqualName('id'))
pointers.append(ptr)
else:
left = irutils.unwrap_set(left)

while left.path_id != result_set.path_id:
if irutils.is_implicit_wrapper(left.expr):
left = left.expr.result
continue

assert isinstance(left.expr, irast.Pointer)
ptr = env.schema.get(
left.expr.ptrref.name,
Expand Down
7 changes: 5 additions & 2 deletions edb/edgeql/compiler/setgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,8 +428,11 @@ def compile_path(expr: qlast.Path, *, ctx: context.ContextLevel) -> irast.Set:
# anyway, so disallow them.
if (
ptr_expr.name in ('source', 'target')
and ctx.env.options.schema_object_context
not in (s_constr.Constraint, s_indexes.Index)
and not ctx.allow_endpoint_linkprops
and (
ctx.env.options.schema_object_context
not in (s_constr.Constraint, s_indexes.Index)
)
):
raise errors.QueryError(
f'@{ptr_expr.name} may only be used in index and '
Expand Down
2 changes: 2 additions & 0 deletions edb/edgeql/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ def subject_substitute(
for path in find_paths(ast):
if isinstance(path.steps[0], qlast.Subject):
path.steps[0] = new_subject
elif path.partial:
path.steps[0:0] = [new_subject]
return ast


Expand Down
5 changes: 4 additions & 1 deletion tests/schemas/constraints.esdl
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,11 @@ abstract link translated_label {

abstract link link_with_unique_property {
property unique_property -> str {
constraint exclusive;
# TODO: Move the constraint back here once linkprop constraints
# supported in conflict selects.
# constraint exclusive;
}
constraint exclusive on (@unique_property);
}

abstract link link_with_unique_property_inherited
Expand Down
59 changes: 52 additions & 7 deletions tests/test_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,15 +500,20 @@ async def test_constraints_endpoint_constraint_01(self):
""")

async def test_constraints_endpoint_constraint_02(self):
# testing (@source, @lang) on a single link
# This constraint is pointless and can never fail
# testing (@source, @lang) on a multi link
await self.con.execute("""
insert UniqueName {
translated_labels := ((insert Label { text := "x" }) {
@lang := 'x' })
};
""")

await self.con.execute("""
update UniqueName set {
translated_labels := Label {@lang := 'x' }
};
""")

# Should be fine
await self.con.execute("""
insert UniqueName {
Expand Down Expand Up @@ -562,7 +567,7 @@ async def test_constraints_endpoint_constraint_03(self):

# Same @target different @lang
await self.con.execute("""
insert UniqueName {
insert UniqueNameInherited {
translated_label_tgt := (
select Label { @lang := 'y' } filter .text = 'x' limit 1)
};
Expand Down Expand Up @@ -590,6 +595,26 @@ async def test_constraints_endpoint_constraint_03(self):
};
""")

async with self.assertRaisesRegexTx(
edgedb.ConstraintViolationError,
'violates exclusivity constraint'
):
await self.con.execute("""
update UniqueName
filter .translated_label_tgt.text = 'x'
set { translated_label_tgt :=
.translated_label_tgt { @lang := '!' }
}
""")

await self.con.execute("""
update UniqueName
filter .translated_label_tgt.text = 'x'
set { translated_label_tgt :=
.translated_label_tgt { @lang := @lang }
}
""")

async def test_constraints_endpoint_constraint_04(self):
# testing (@target, @lang) on a multi link
await self.con.execute("""
Expand All @@ -616,10 +641,10 @@ async def test_constraints_endpoint_constraint_04(self):
""")

await self.con.execute("""
insert UniqueNameInherited {
translated_labels_tgt := (
select Label { @lang := 'x!' })
};
insert UniqueNameInherited {
translated_labels_tgt := (
select Label { @lang := 'x!' })
};
""")

async with self.assertRaisesRegexTx(
Expand Down Expand Up @@ -656,6 +681,26 @@ async def test_constraints_endpoint_constraint_04(self):
};
""")

async with self.assertRaisesRegexTx(
edgedb.ConstraintViolationError,
'violates exclusivity constraint'
):
await self.con.execute("""
update UniqueName
filter .translated_labels_tgt.text = 'x'
set { translated_labels_tgt :=
.translated_labels_tgt { @lang := '!' }
}
""")

await self.con.execute("""
update UniqueName
filter .translated_labels_tgt.text = 'x'
set { translated_labels_tgt :=
.translated_labels_tgt { @lang := @lang }
}
""")


class TestConstraintsSchemaMigration(tb.QueryTestCase):

Expand Down

0 comments on commit 7184615

Please sign in to comment.