Repository: edgedb/edgedb

Issue:
Don't allow setting a global if it was declared with a computable expression
Schema:

```
module default {
    global test := 'hello';
}
```

```
> set global test := 'world'; <--- this should error out, but it does not
OK

> select global test;
{'hello'}    <--- because this is confusing
```



Discussion:
We'll also want to test that it fails when trying to set it directly from a client, also. That might be either more or less gnarly.

See `test_edgeql_globals_composite` for a test where we set globals using the protocol

Resolving patch:
diff --git a/edb/edgeql/compiler/config.py b/edb/edgeql/compiler/config.py
--- a/edb/edgeql/compiler/config.py
+++ b/edb/edgeql/compiler/config.py
@@ -292,11 +292,20 @@ def _validate_config_object(
 def _validate_global_op(
     expr: qlast.ConfigOp, *, ctx: context.ContextLevel
 ) -> SettingInfo:
+    glob_name = s_utils.ast_ref_to_name(expr.name)
     glob = ctx.env.get_schema_object_and_track(
-        s_utils.ast_ref_to_name(expr.name), expr.name,
+        glob_name, expr.name,
         modaliases=ctx.modaliases, type=s_globals.Global)
     assert isinstance(glob, s_globals.Global)
 
+    if isinstance(expr, (qlast.ConfigSet, qlast.ConfigReset)):
+        if glob.get_expr(ctx.env.schema):
+            raise errors.ConfigurationError(
+                f"global '{glob_name}' is computed from an expression and "
+                f"cannot be modified",
+                span=expr.name.span
+            )
+
     param_type = glob.get_target(ctx.env.schema)
 
     return SettingInfo(param_name=str(glob.get_name(ctx.env.schema)),


Test patch:
diff --git a/tests/test_edgeql_globals.py b/tests/test_edgeql_globals.py
--- a/tests/test_edgeql_globals.py
+++ b/tests/test_edgeql_globals.py
@@ -434,6 +434,113 @@ async def test_edgeql_globals_13(self):
                 my_param='1 sec',
             )
 
+    async def test_edgeql_globals_14(self):
+        with self.assertRaisesRegex(
+            edgedb.ConfigurationError,
+            "global 'def_cur_user_excited' is computed from an expression "
+            "and cannot be modified",
+        ):
+            await self.con.execute(
+                '''
+                set global def_cur_user_excited := 'yay!'
+                ''',
+            )
+
+    async def test_edgeql_globals_15(self):
+        await self.con.execute('''
+            create global foo := 1;
+        ''')
+
+        with self.assertRaisesRegex(
+            edgedb.ConfigurationError,
+            "global 'def_cur_user_excited' is computed from an expression "
+            "and cannot be modified",
+        ):
+            await self.con.execute(
+                '''
+                reset global def_cur_user_excited
+                ''',
+            )
+
+    async def test_edgeql_globals_client_01(self):
+        con = edgedb.create_async_client(
+            **self.get_connect_args(database=self.con.dbname)
+        )
+        try:
+            globs = dict(
+                cur_user='Alice',
+            )
+            scon = con.with_globals(**globs)
+            res = await scon.query_single(
+                f'select {{ cur_user := global cur_user }}'
+            )
+            dres = dataclasses.asdict(res)
+            self.assertEqual(dres, {'cur_user': 'Alice'})
+        finally:
+            await con.aclose()
+
+    async def test_edgeql_globals_client_02(self):
+        con = edgedb.create_async_client(
+            **self.get_connect_args(database=self.con.dbname)
+        )
+        try:
+            globs = dict(
+                cur_user=1,  # wrong type
+            )
+            scon = con.with_globals(**globs)
+            with self.assertRaisesRegex(
+                edgedb.InvalidArgumentError,
+                r"invalid input for state argument  default::cur_user := 1 "
+                r"\(expected str, got int\)",
+            ):
+                await scon.query_single(
+                    f'select {{ cur_user := global cur_user }}'
+                )
+        finally:
+            await con.aclose()
+
+    async def test_edgeql_globals_client_03(self):
+        con = edgedb.create_async_client(
+            **self.get_connect_args(database=self.con.dbname)
+        )
+        try:
+            globs = dict(
+                def_cur_user_excited='yay!',  # computed
+            )
+            scon = con.with_globals(**globs)
+            with self.assertRaisesRegex(
+                edgedb.QueryArgumentError,
+                r"got {'default::def_cur_user_excited'}, "
+                r"extra {'default::def_cur_user_excited'}",
+            ):
+                await scon.query_single(
+                    f'select {{'
+                    f'    def_cur_user_excited := global def_cur_user_excited'
+                    f'}}'
+                )
+        finally:
+            await con.aclose()
+
+    async def test_edgeql_globals_client_04(self):
+        con = edgedb.create_async_client(
+            **self.get_connect_args(database=self.con.dbname)
+        )
+        try:
+            globs = dict(
+                imaginary='!',  # doesn't exist
+            )
+            scon = con.with_globals(**globs)
+            with self.assertRaisesRegex(
+                edgedb.QueryArgumentError,
+                r"got {'default::imaginary'}, "
+                r"extra {'default::imaginary'}",
+            ):
+                await scon.query_single(
+                    f'select {{ imaginary := global imaginary }}'
+                )
+        finally:
+            await con.aclose()
+
     async def test_edgeql_globals_state_cardinality(self):
         await self.con.execute('''
             set global cur_user := {};