Repository: geldata/gel
Pull request: Disallow partial paths in the defaults of link properties
Fix #5200

Problem statement:
Error during compiled migration: QueryError: could not resolve partial path 
Version: newest master branch

Steps to reproduce:
1. Replace your project's schema with the following:
2. run `edb cli migration create`, this step should succeeded. If this step fails, then this bug has been fixed.
3. run `edb cli migrate`, this should now give an error that is hard to understand. 

I believe this has to do with how link properties interact with defaults.


Schema:
```
module default {
type Person {
    required name : str {
        constraint exclusive;
    }

    multi l3 : Person {
        l3note : str {
            default := .name
        }
    }

}
}
```

Hints from issue discussion:

Commit message: add test case

Patch to review:
diff --git a/edb/schema/pointers.py b/edb/schema/pointers.py
index ec982f22e27..2f882a8ed7c 100644
--- a/edb/schema/pointers.py
+++ b/edb/schema/pointers.py
@@ -1439,6 +1439,7 @@ def _compile_expr(
         no_query_rewrites: bool = False,
         make_globals_empty: bool = False,
         source_context: Optional[parsing.ParserContext] = None,
+        should_set_path_prefix_anchor: bool = True,
     ) -> s_expr.CompiledExpression:
         singletons: List[Union[s_types.Type, Pointer]] = []
 
@@ -1489,7 +1490,10 @@ def _compile_expr(
                 modaliases=context.modaliases,
                 schema_object_context=self.get_schema_metaclass(),
                 anchors={qlast.Source().name: source},
-                path_prefix_anchor=qlast.Source().name,
+                path_prefix_anchor=(
+                    qlast.Source().name
+                    if should_set_path_prefix_anchor
+                    else None),
                 singletons=singletons,
                 apply_query_rewrites=(
                     not context.stdmode and not no_query_rewrites
@@ -1539,12 +1543,30 @@ def compile_expr_field(
             else:
                 in_ddl_context_name = None
 
+            # If we are in a link property's default field
+            # do not set path prefix anchor, because link properties
+            # cannot have defaults that reference the object being inserted
+            should_set_path_prefix_anchor = True
+            if field.name == 'default':
+                from .objtypes import ObjectTypeCommandContext
+                from .links import LinkCommandContext
+                from .properties import PropertyCommandContext
+                if (len(context.stack) >= 3 and
+                    isinstance(context.stack[-3],
+                               ObjectTypeCommandContext) and
+                    isinstance(context.stack[-2],
+                               LinkCommandContext) and
+                    isinstance(context.stack[-1],
+                               PropertyCommandContext)):
+                    should_set_path_prefix_anchor = False
+
             return self._compile_expr(
                 schema,
                 context,
                 value,
                 in_ddl_context_name=in_ddl_context_name,
                 track_schema_ref_exprs=track_schema_ref_exprs,
+                should_set_path_prefix_anchor=should_set_path_prefix_anchor,
             )
         else:
             return super().compile_expr_field(
diff --git a/tests/test_schema.py b/tests/test_schema.py
index 6b14878aa6c..a115892cb3b 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -362,6 +362,26 @@ def test_schema_link_prop_on_prop_01(self):
             };
         """
 
+    @tb.must_fail(errors.QueryError,
+                  "could not resolve partial path")
+    def test_schema_partial_path_in_default_of_link_prop_01(self):
+        """
+            module default {
+                type Person {
+                    required name : str {
+                        constraint exclusive;
+                    }
+
+                    multi friends : Person {
+                        note : str {
+                            default := .name
+                        }
+                    }
+
+                }
+            }
+        """
+
     @tb.must_fail(errors.InvalidPropertyTargetError,
                   "invalid property type: expected a scalar type, "
                   "or a scalar collection, got object type 'test::Object'",


Code review comments:
diff_hunk: @@ -1539,12 +1543,30 @@ def compile_expr_field(
             else:
                 in_ddl_context_name = None
 
+            # If we are in a link property's default field
+            # do not set path prefix anchor, because link properties
+            # cannot have defaults that reference the object being inserted
+            should_set_path_prefix_anchor = True
+            if field.name == 'default':
+                from .objtypes import ObjectTypeCommandContext
path: edb/schema/pointers.py
text: @user1:
This is a pretty gnarly way to check if it is a link property.
I would try something like
```
parent_ctx = self.get_referrer_context_or_die(context)
source = parent_ctx.op.get_object(schema, context)
```
and then check if source is a `Pointer`. If it is, this is a link property.
(There are similar checks in `_compile_expr`)