Repository: edgedb/edgedb

Issue:
ISE when inserting defaults and refering to parent object type
Migration to this schema fails:

```
module default {
    type User {
        link default_project: Project {
            default := (insert Project {
                owner := User
            })
        };
    }

    type Project {
        required owner: User;
    }
}
```

- EdgeDB Version: 3.0-rc1
- EdgeDB CLI Version: EdgeDB CLI 3.1.0+69bd230
- OS Version: Linux Ubuntu something




Discussion:
```
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/__init__.py, line 134, in compile_ir_to_sql_tree
        > qtree = dispatch.compile(ir_expr, ctx=ctx)
    /usr/lib/python3.11/functools.py, line 909, in wrapper
        > return dispatch(args[0].__class__)(*args, **kw)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/expr.py, line 60, in compile_Set
        > _compile_set_impl(ir_set, ctx=ctx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/expr.py, line 114, in _compile_set_impl
        > _compile_set(ir_set, ctx=ctx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/expr.py, line 700, in _compile_set
        > relgen.get_set_rvar(ir_set, ctx=ctx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/relgen.py, line 164, in get_set_rvar
        > return _process_toplevel_query(ir_set, ctx=ctx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/relgen.py, line 286, in _process_toplevel_query
        > rvars = _get_set_rvar(ir_set, ctx=ctx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/relgen.py, line 348, in _get_set_rvar
        > return process_set_as_subquery(ir_set, ctx=ctx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/relgen.py, line 1389, in process_set_as_subquery
        > dispatch.visit(expr, ctx=newctx)
    /usr/lib/python3.11/functools.py, line 909, in wrapper
        > return dispatch(args[0].__class__)(*args, **kw)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/dispatch.py, line 44, in visit
        > compile(ir, ctx=ctx)
    /usr/lib/python3.11/functools.py, line 909, in wrapper
        > return dispatch(args[0].__class__)(*args, **kw)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/stmt.py, line 88, in compile_SelectStmt
        > outvar = clauses.compile_output(stmt.result, ctx=ictx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/clauses.py, line 225, in compile_output
        > dispatch.visit(ir_set, ctx=newctx)
    /usr/lib/python3.11/functools.py, line 909, in wrapper
        > return dispatch(args[0].__class__)(*args, **kw)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/expr.py, line 85, in visit_Set
        > _compile_set_impl(ir_set, ctx=ctx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/expr.py, line 110, in _compile_set_impl
        > _compile_set(ir_set, ctx=scopectx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/expr.py, line 700, in _compile_set
        > relgen.get_set_rvar(ir_set, ctx=ctx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/relgen.py, line 222, in get_set_rvar
        > rvars = _get_set_rvar(ir_set, ctx=subctx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/relgen.py, line 348, in _get_set_rvar
        > return process_set_as_subquery(ir_set, ctx=ctx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/relgen.py, line 1389, in process_set_as_subquery
        > dispatch.visit(expr, ctx=newctx)
    /usr/lib/python3.11/functools.py, line 909, in wrapper
        > return dispatch(args[0].__class__)(*args, **kw)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/dispatch.py, line 44, in visit
        > compile(ir, ctx=ctx)
    /usr/lib/python3.11/functools.py, line 909, in wrapper
        > return dispatch(args[0].__class__)(*args, **kw)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/stmt.py, line 156, in compile_InsertStmt
        > dml.process_insert_body(
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/dml.py, line 760, in process_insert_body
        > _update_link_tables(contents_cte)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/dml.py, line 745, in _update_link_tables
        > link_cte, check_cte = process_link_update(
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/dml.py, line 2279, in process_link_update
        > data_cte, specified_cols = process_link_values(
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/dml.py, line 2884, in process_link_values
        > dispatch.visit(ir_expr, ctx=input_rel_ctx)
    /usr/lib/python3.11/functools.py, line 909, in wrapper
        > return dispatch(args[0].__class__)(*args, **kw)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/expr.py, line 85, in visit_Set
        > _compile_set_impl(ir_set, ctx=ctx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/expr.py, line 110, in _compile_set_impl
        > _compile_set(ir_set, ctx=scopectx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/expr.py, line 700, in _compile_set
        > relgen.get_set_rvar(ir_set, ctx=ctx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/relgen.py, line 222, in get_set_rvar
        > rvars = _get_set_rvar(ir_set, ctx=subctx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/relgen.py, line 348, in _get_set_rvar
        > return process_set_as_subquery(ir_set, ctx=ctx)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/relgen.py, line 1273, in process_set_as_subquery
        > assert outer_fence is not None

AssertionError:

==================================== The above exception was the direct cause of the following exception =====================================

1. edb.errors.InternalServerError: None

----------------------------------------------------------------- Traceback ------------------------------------------------------------------

    /home/aljaz/EdgeDB/edgedb/edb/server/compiler_pool/worker.py, line 186, in compile_in_tx
        > units, cstate = COMPILER.compile_in_tx(cstate, *args, **kwargs)
    /home/aljaz/EdgeDB/edgedb/edb/server/compiler/compiler.py, line 808, in compile_in_tx
        > return compile(ctx=ctx, source=source), ctx.state
    /home/aljaz/EdgeDB/edgedb/edb/server/compiler/compiler.py, line 1935, in compile
        > return _try_compile(ctx=ctx, source=source)
    /home/aljaz/EdgeDB/edgedb/edb/server/compiler/compiler.py, line 2003, in _try_compile
        > comp, capabilities = _compile_dispatch_ql(
    /home/aljaz/EdgeDB/edgedb/edb/server/compiler/compiler.py, line 1842, in _compile_dispatch_ql
        > query = ddl.compile_dispatch_ql_migration(
    /home/aljaz/EdgeDB/edgedb/edb/server/compiler/ddl.py, line 378, in compile_dispatch_ql_migration
        > return compile_and_apply_ddl_stmt(ctx, ql)
    /home/aljaz/EdgeDB/edgedb/edb/server/compiler/ddl.py, line 208, in compile_and_apply_ddl_stmt
        > block, new_types, config_ops = _process_delta(ctx, delta)
    /home/aljaz/EdgeDB/edgedb/edb/server/compiler/ddl.py, line 325, in _process_delta
        > schema = pgdelta.apply(schema, context)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/delta.py, line 7133, in apply
        > schema = super().apply(schema, context)
    /home/aljaz/EdgeDB/edgedb/edb/schema/delta.py, line 1554, in apply
        > schema = self.apply_subcommands(schema, context)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/delta.py, line 189, in apply_subcommands
        > schema = super().apply_subcommands(schema, context)
    /home/aljaz/EdgeDB/edgedb/edb/schema/delta.py, line 1581, in apply_subcommands
        > schema = objop.apply(schema, context)
    /home/aljaz/EdgeDB/edgedb/edb/schema/delta.py, line 3051, in apply
        > schema = self._create_innards(schema, context)
    /home/aljaz/EdgeDB/edgedb/edb/schema/delta.py, line 3019, in _create_innards
        > return self.apply_subcommands(schema, context)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/delta.py, line 189, in apply_subcommands
        > schema = super().apply_subcommands(schema, context)
    /home/aljaz/EdgeDB/edgedb/edb/schema/delta.py, line 952, in apply_subcommands
        > schema = op.apply(schema, context=context)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/delta.py, line 3729, in apply
        > schema = super().apply(schema, context=context)
    /home/aljaz/EdgeDB/edgedb/edb/schema/delta.py, line 3516, in apply
        > schema = self._alter_innards(schema, context)
    /home/aljaz/EdgeDB/edgedb/edb/schema/delta.py, line 3148, in _alter_innards
        > return self.apply_subcommands(schema, context)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/delta.py, line 189, in apply_subcommands
        > schema = super().apply_subcommands(schema, context)
    /home/aljaz/EdgeDB/edgedb/edb/schema/delta.py, line 952, in apply_subcommands
        > schema = op.apply(schema, context=context)
    /home/aljaz/EdgeDB/edgedb/edb/schema/delta.py, line 3047, in apply
        > schema = self._create_begin(schema, context)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/delta.py, line 5071, in _create_begin
        > self._create_link(link, schema, orig_schema, context)
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/delta.py, line 4977, in _create_link
        > self._alter_pointer_optionality(
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/delta.py, line 4158, in _alter_pointer_optionality
        > (conv_expr_ctes, _) = self._compile_conversion_expr(
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/delta.py, line 4685, in _compile_conversion_expr
        > (sql_tree, env) = compiler.compile_ir_to_sql_tree(
    /home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/__init__.py, line 151, in compile_ir_to_sql_tree
        > raise errors.InternalServerError(*args) from e
```
Scopetree:

```
"FENCE 0x7f21b2b54f10": {
    "(default::User) 0x7f21b2af3a50",
    "(default::User).>default_project[IS default::Project] 0x7f21b2855bd0",
    "FENCE 0x7f21b2d0e6d0",
    "FENCE uid:5 no-factor 0x7f21b20349d0": {
        "(default::Project) 0x7f21b2927990",
        "FENCE no-unnest 0x7f21b2198150": {
            "FENCE uid:1 0x7f21b2148c50": {
                "(default::Project).>owner[IS default::User] 0x7f21b20d6390",
                "FENCE uid:3 0x7f21b21b9010"
            }
        },
        "FENCE no-unnest 0x7f21b202af90": {
            "FENCE uid:2 0x7f21b20d21d0"
        },
        "FENCE uid:4 0x7f21b20ba0d0"
    }
}
```
Migration:
```
CREATE MIGRATION m1gd5kirjv47ocwfksejkyloi52nkzib57gceawikrwphivihburta
    ONTO m1ld4imcn5hbrfnxdj22d5bk57xqotdgkvh3u4hixhnvymhkh74u4q
{
  ALTER TYPE default::User {
      CREATE LINK default_project: default::Project {
          SET default := (INSERT
              default::Project
              {
                  owner := default::User
              });
      };
  };
};
```

Resolving patch:
diff --git a/edb/schema/expr.py b/edb/schema/expr.py
--- a/edb/schema/expr.py
+++ b/edb/schema/expr.py
@@ -176,9 +176,11 @@ def compiled(
         *,
         options: Optional[qlcompiler.CompilerOptions] = None,
         as_fragment: bool = False,
+        detached: bool = False,
     ) -> CompiledExpression:
 
         from edb.ir import ast as irast_
+        from edb.edgeql import ast as qlast
 
         if as_fragment:
             ir: irast_.Command = qlcompiler.compile_ast_fragment_to_ir(
@@ -187,8 +189,15 @@ def compiled(
                 options=options,
             )
         else:
+            ql_expr = self.qlast
+            if detached:
+                ql_expr = qlast.DetachedExpr(
+                    expr=ql_expr,
+                    preserve_path_prefix=True,
+                )
+
             ir = qlcompiler.compile_ast_to_ir(
-                self.qlast,
+                ql_expr,
                 schema=schema,
                 options=options,
             )
diff --git a/edb/schema/pointers.py b/edb/schema/pointers.py
--- 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,
+        detached: bool = False,
     ) -> s_expr.CompiledExpression:
         singletons: List[Union[s_types.Type, Pointer]] = []
 
@@ -1499,7 +1500,9 @@ def _compile_expr(
                 in_ddl_context_name=in_ddl_context_name,
             )
 
-            compiled = expr.compiled(schema=schema, options=options)
+            compiled = expr.compiled(
+                schema=schema, options=options, detached=detached
+            )
 
             if singleton_result_expected and compiled.cardinality.is_multi():
                 if expr_description is None:
@@ -1536,8 +1539,10 @@ def compile_expr_field(
                 parent_vname = source.get_verbosename(schema)
                 ptr_name = self.get_verbosename(parent=parent_vname)
                 in_ddl_context_name = f'computed {ptr_name}'
+                detached = False
             else:
                 in_ddl_context_name = None
+                detached = True
 
             return self._compile_expr(
                 schema,
@@ -1545,6 +1550,7 @@ def compile_expr_field(
                 value,
                 in_ddl_context_name=in_ddl_context_name,
                 track_schema_ref_exprs=track_schema_ref_exprs,
+                detached=detached,
             )
         else:
             return super().compile_expr_field(
@@ -1727,7 +1733,7 @@ def validate_object(
 
             if not default_expr.irast:
                 default_expr = self._compile_expr(
-                    schema, context, default_expr
+                    schema, context, default_expr, detached=True,
                 )
                 assert default_expr.irast
 
@@ -1838,7 +1844,7 @@ def _check_id_default(
             'std::uuid_generate_v4',
         )
 
-        if (
+        while (
             isinstance(expr, irast.Set)
             and expr.expr
             and irutils.is_trivial_select(expr.expr)


Test patch:
diff --git a/tests/test_edgeql_ddl.py b/tests/test_edgeql_ddl.py
--- a/tests/test_edgeql_ddl.py
+++ b/tests/test_edgeql_ddl.py
@@ -608,7 +608,7 @@ async def test_edgeql_ddl_16(self):
             CREATE TYPE TestSelfLink3 {
                 CREATE PROPERTY foo3 -> std::str;
                 CREATE PROPERTY bar3 -> std::str {
-                    SET default := TestSelfLink3.foo3;
+                    SET default := .foo3;
                 };
             };
         """)
@@ -1957,13 +1957,42 @@ async def test_edgeql_ddl_default_12(self):
             edgedb.QueryError,
             'is part of a default cycle'
         ):
-            await self.con.execute(r"""
+            await self.con.execute(
+                r"""
                 CREATE TYPE Test3 {
                     CREATE LINK d7 : Test3 {
                         SET default := (INSERT Test3 {})
                     }
                 }
-            """)
+                """
+            )
+
+    async def test_edgeql_ddl_default_13(self):
+        # defaults are detached: when referencing the source object (User),
+        # the cardinality is many.
+        # (you can still use partial path prefix)
+
+        with self.assertRaisesRegex(
+            edgedb.QueryError,
+            'possibly more than one element returned by an expression',
+        ):
+            await self.con.execute(
+                r"""
+                create type User;
+
+                create type Project {
+                    create required link owner -> User;
+                };
+
+                alter type User {
+                    create link default_project -> Project {
+                        set default := (insert Project {
+                            owner := User
+                        })
+                    };
+                };
+                """
+            )
 
     async def test_edgeql_ddl_default_circular(self):
         await self.con.execute(r"""