Repository: edgedb/edgedb

Issue:
Default value derived from function with optional output parameter broke first time migration
- EdgeDB Version: 4.0-rc.1+fd5ed53
- OS Version: Win11

Steps to Reproduce:

1. Lets define function with optional output:
```
    function getDefaultCurrency() -> optional Currency {
        volatility:= 'Stable';
        using (
            assert_single((SELECT Currency FILTER .code IN global currentOrganization.currencies.code LIMIT 1)))
    }

```

2. Lets  define type with default property value:
```
    abstract type Currencied {
        required currency: Currency {
            readonly:= true;
            # Comment it on the first migration
            default:= getDefaultCurrency();
        };
    }
```

3. Lets create first time migration:
```
  CREATE FUNCTION default::getDefaultCurrency() -> OPTIONAL default::Currency {
      SET volatility := 'Stable';
      USING (std::assert_single((SELECT
          default::Currency FILTER
              (.code IN (GLOBAL default::currentOrganization).currencies.code)
      LIMIT
          1
      )))
  ;};
  ALTER TYPE default::Currencied {
      CREATE REQUIRED LINK currency: default::Currency {
          SET default := (default::getDefaultCurrency());
          SET readonly := true;
      };
  };
```

4. When you apply it, the bug is coming:
```
UnsupportedFeatureError: functions that reference globals may not be used when converting/populating data in migrations
    ┌─ C:\...\dbschema\migrations\00001.edgeql:896:7
    │
896 │ ╭       CREATE REQUIRED LINK currency: default::Currency {
897 │ │           SET default := (func::getDefaultCurrency());
898 │ │           SET readonly := true;
899 │ │       };
    │ ╰───────^ error

edgedb error: error in one of the migrations
```

The workaround are: comment the line in the first migration `SET default := (default::getDefaultCurrency());`, then create second migration and the bug disappear


Discussion:
Perhaps this is not a bug but expected behavior since default values can be used to seed properties. Then you need to describe a method for solving similar problems - for example, a flag that disables seeding during the first migration, etc.

Resolving patch:
diff --git a/edb/edgeql/compiler/setgen.py b/edb/edgeql/compiler/setgen.py
--- a/edb/edgeql/compiler/setgen.py
+++ b/edb/edgeql/compiler/setgen.py
@@ -2018,6 +2018,11 @@ def get_globals_as_json(
     """
     # TODO: arrange to compute this once per query, in a CTE or some such?
 
+    # If globals are empty, arrange to still pass in the argument but
+    # don't put anything in it.
+    if ctx.env.options.make_globals_empty:
+        globs = ()
+
     objctx = ctx.env.options.schema_object_context
     if globs and objctx in (s_constr.Constraint, s_indexes.Index):
         typname = objctx.get_schema_class_displayname()


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
@@ -6853,25 +6853,6 @@ async def test_edgeql_ddl_global_04(self):
                 }
             """)
 
-        await self.con.execute("""
-            create type X;
-        """)
-
-        # We can't set a default that uses a global when creating a new
-        # pointer, since it would need to run *now* and populate the data
-        async with self.assertRaisesRegexTx(
-            edgedb.UnsupportedFeatureError,
-            r"functions that reference globals may not be used when "
-            r"converting/populating data in migrations"
-        ):
-            await self.con.execute("""
-                alter type X {
-                    create property foo -> str {
-                        set default := (gfoo());
-                    }
-                };
-            """)
-
     async def test_edgeql_ddl_global_05(self):
         await self.con.execute("""
             create global foo -> str;
@@ -7129,20 +7110,24 @@ async def test_edgeql_ddl_global_default(self):
 
         await self.con.execute('''
             alter global foo set default := "!";
+            create function get_foo() -> optional str using (global foo);
         ''')
 
         # Try it again now that there is a default
         await self.con.execute('''
             alter type Foo { create required property name2 -> str {
                 set default := (global foo);
-            } }
+            } };
+            alter type Foo { create required property name3 -> str {
+                set default := (get_foo());
+            } };
         ''')
 
         await self.assert_query_result(
             '''
-                select Foo { name, name2 }
+                select Foo { name, name2, name3 }
             ''',
-            [{'name': "test", 'name2': "!"}],
+            [{'name': "test", 'name2': "!", 'name3': "!"}],
         )
 
     async def test_edgeql_ddl_property_computable_01(self):