Skip to content

Commit 7db665a

Browse files
authored
Fix exponential time (in inheritance depth) migrations when altering things (#8671)
The issue is that we are propagating changes to *all descendants* (not all children), but each descendant does the same. There is a mechanism involving setting 'implicit_propagation' as an annotation that solves this in other cases; use it in some more cases. Fixes #7195.
1 parent 5b720cf commit 7db665a

File tree

6 files changed

+117
-9
lines changed

6 files changed

+117
-9
lines changed

edb/edgeql/declarative.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,15 @@ def trace_layout_CreateProperty(
596596
_trace_item_layout(node, ctx=ctx)
597597

598598

599+
@trace_layout.register
600+
def trace_layout_CreateConstraint(
601+
node: qlast.CreateConstraint,
602+
*,
603+
ctx: LayoutTraceContext,
604+
) -> None:
605+
_trace_item_layout(node, ctx=ctx)
606+
607+
599608
def _trace_item_layout(
600609
node: qlast.CreateObject,
601610
*,

edb/schema/constraints.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,39 @@ def localnames_from_ast(
730730

731731
return localnames
732732

733+
def inherit_fields(
734+
self,
735+
schema: s_schema.Schema,
736+
context: sd.CommandContext,
737+
bases: tuple[so.Object, ...],
738+
*,
739+
fields: Optional[Iterable[str]] = None,
740+
ignore_local: bool = False,
741+
apply: bool = True,
742+
) -> s_schema.Schema:
743+
# Concrete constraints populate a bunch of other fields that
744+
# can be based on their abstract parents but don't come from
745+
# them. So if we are inheriting a new expr from a potentially
746+
# abstract parent, we need to actually inherit all of these
747+
# other properties that can be populated by
748+
# _populate_concrete_constraint_attrs.
749+
#
750+
# This is pretty fragile though, and I don't love it.
751+
752+
if fields is not None:
753+
fields = set(fields) | {
754+
'subjectexpr', 'finalexpr', 'abstract', 'args'
755+
}
756+
757+
return super().inherit_fields(
758+
schema,
759+
context,
760+
bases,
761+
fields=fields,
762+
ignore_local=ignore_local,
763+
apply=apply,
764+
)
765+
733766
def _populate_concrete_constraint_attrs(
734767
self,
735768
schema: s_schema.Schema,

edb/schema/inheriting.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,9 @@ def _propagate_field_alter(
959959
scls: so.InheritingObject,
960960
props: tuple[str, ...],
961961
) -> None:
962+
if _has_implicit_propagation(context):
963+
return
964+
962965
descendant_names = [
963966
d.get_name(schema) for d in scls.ordered_descendants(schema)
964967
]
@@ -979,6 +982,7 @@ def _propagate_field_alter(
979982
d_alter_cmd.add(self.clone(d_alter_cmd.classname))
980983

981984
with ctx_stack():
985+
d_alter_cmd.set_annotation('implicit_propagation', True)
982986
assert isinstance(d_alter_cmd, InheritingObjectCommand)
983987
schema = d_alter_cmd.inherit_fields(
984988
schema, context, d_bases, fields=props, apply=False
@@ -1145,12 +1149,16 @@ def _alter_finalize(
11451149

11461150
if not context.canonical:
11471151
schema = self._recompute_inheritance(schema, context)
1148-
if context.enable_recursion:
1152+
if (
1153+
context.enable_recursion
1154+
and not _has_implicit_propagation(context)
1155+
):
11491156
for descendant in self.scls.ordered_descendants(schema):
11501157
d_root_cmd, d_alter_cmd, ctx_stack = (
11511158
descendant.init_delta_branch(
11521159
schema, context, sd.AlterObject))
11531160
assert isinstance(d_alter_cmd, InheritingObjectCommand)
1161+
d_alter_cmd.set_annotation('implicit_propagation', True)
11541162
with ctx_stack():
11551163
d_alter_cmd._fixup_inheritance_refdicts(
11561164
schema, context)
@@ -1331,3 +1339,13 @@ def _needs_refdict(refdict: so.RefDict, context: sd.CommandContext) -> bool:
13311339
inheritance_refdicts is None
13321340
or refdict.attr in inheritance_refdicts
13331341
) and (context.inheritance_merge is None or context.inheritance_merge)
1342+
1343+
1344+
def _has_implicit_propagation(context: sd.CommandContext) -> bool:
1345+
for ctx in reversed(context.stack):
1346+
if (
1347+
isinstance(ctx.op, sd.ObjectCommand)
1348+
and ctx.op.get_annotation('implicit_propagation')
1349+
):
1350+
return True
1351+
return False

edb/schema/referencing.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -853,12 +853,8 @@ def _propagate_ref_op(
853853
scls: ReferencedInheritingObject,
854854
cb: Callable[[sd.ObjectCommand[so.Object], sn.Name], None]
855855
) -> None:
856-
for ctx in reversed(context.stack):
857-
if (
858-
isinstance(ctx.op, sd.ObjectCommand)
859-
and ctx.op.get_annotation('implicit_propagation')
860-
):
861-
return
856+
if inheriting._has_implicit_propagation(context):
857+
return
862858

863859
referrer_ctx = self.get_referrer_context(context)
864860
if referrer_ctx:

tests/test_edgeql_functions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from edb.tools import test
3232

3333

34-
class TestEdgeQLFunctions(tb.QueryTestCase):
34+
class TestEdgeQLFunctions(tb.DDLTestCase):
3535

3636
SCHEMA = os.path.join(os.path.dirname(__file__), 'schemas',
3737
'issues.esdl')

tests/test_schema.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7654,6 +7654,31 @@ def test_schema_migrations_equivalence_linkprops_14(self):
76547654
};
76557655
"""])
76567656

7657+
def test_schema_migrations_equivalence_linkprops_15(self):
7658+
self._assert_migration_equivalence([r"""
7659+
abstract link link_with_value {
7660+
single property value := 'lol';
7661+
index on (__subject__@value);
7662+
}
7663+
type Tgt;
7664+
type Foo {
7665+
link l1 extending link_with_value -> Tgt;
7666+
};
7667+
type Bar extending Foo;
7668+
type Baz extending Bar;
7669+
""", r"""
7670+
abstract link link_with_value {
7671+
single property value := 12;
7672+
index on (__subject__@value);
7673+
}
7674+
type Tgt;
7675+
type Foo {
7676+
link l1 extending link_with_value -> Tgt;
7677+
};
7678+
type Bar extending Foo;
7679+
type Baz extending Bar;
7680+
"""])
7681+
76577682
def test_schema_migrations_equivalence_annotation_01(self):
76587683
self._assert_migration_equivalence([r"""
76597684
type Base;
@@ -8061,7 +8086,7 @@ def test_schema_migrations_equivalence_constraint_04(self):
80618086
""", r"""
80628087
"""])
80638088

8064-
def test_schema_migrations_equivalence_constraint_05(self):
8089+
def test_schema_migrations_equivalence_constraint_05a(self):
80658090
self._assert_migration_equivalence([r"""
80668091
abstract constraint not_bad {
80678092
using (__subject__ != "bad" and __subject__ != "terrible")
@@ -8086,6 +8111,33 @@ def test_schema_migrations_equivalence_constraint_05(self):
80868111
type Bar extending Foo;
80878112
"""])
80888113

8114+
def test_schema_migrations_equivalence_constraint_05b(self):
8115+
self._assert_migration_equivalence([r"""
8116+
abstract constraint not_bad_1 {
8117+
using (__subject__ != "bad" and __subject__ != "terrible")
8118+
}
8119+
abstract constraint not_bad extending not_bad_1;
8120+
8121+
type Foo {
8122+
property x -> str {
8123+
constraint not_bad;
8124+
}
8125+
}
8126+
type Bar extending Foo;
8127+
""", r"""
8128+
abstract constraint not_bad_1 {
8129+
using (__subject__ != "bad" and __subject__ != "terrible")
8130+
}
8131+
abstract constraint not_bad extending not_bad_1;
8132+
8133+
type Foo {
8134+
property x -> str {
8135+
constraint not_bad;
8136+
}
8137+
}
8138+
type Bar extending Foo;
8139+
"""])
8140+
80898141
def test_schema_migrations_equivalence_constraint_06(self):
80908142
self._assert_migration_equivalence([r"""
80918143
type Cell {

0 commit comments

Comments
 (0)