diff --git a/edb/ir/astexpr.py b/edb/ir/astexpr.py index c6feea44a92..2d96d6a1f5d 100644 --- a/edb/ir/astexpr.py +++ b/edb/ir/astexpr.py @@ -63,7 +63,7 @@ def is_possibly_wrapped_distinct_expr( if not isinstance(tree, irast.SelectStmt): return None - return is_pure_distinct_expr(tree.result) + return is_set_expr(tree.result) def is_set_expr(tree: irast.Base) -> Optional[List[irast.Base]]: diff --git a/edb/pgsql/schemamech.py b/edb/pgsql/schemamech.py index 677d69a20b3..2361a4caf5c 100644 --- a/edb/pgsql/schemamech.py +++ b/edb/pgsql/schemamech.py @@ -62,8 +62,7 @@ def _get_exclusive_refs(tree: irast.Statement) -> Sequence[irast.Base] | None: # Check if the expression is # std::_is_exclusive() [and std::_is_exclusive()...] - assert isinstance(tree.expr.expr, irast.SelectStmt) - expr = tree.expr.expr.result + expr = tree.expr.expr return irastexpr.get_constraint_references(expr) @@ -256,7 +255,6 @@ def _compile_constraint_data( options=options, ) assert isinstance(ir, irast.Statement) - assert isinstance(ir.expr.expr, irast.SelectStmt) except_ir: Optional[irast.Statement] = None except_data = None @@ -273,7 +271,7 @@ def _compile_constraint_data( except_data = _edgeql_tree_to_expr_data(except_sql.ast) terminal_refs: set[irast.Set] = ( - ir_utils.get_longest_paths(ir.expr.expr.result) + ir_utils.get_longest_paths(ir.expr.expr) ) if except_ir is not None: terminal_refs.update( diff --git a/edb/schema/functions.py b/edb/schema/functions.py index 5bcfadc5bdd..5e96de151ba 100644 --- a/edb/schema/functions.py +++ b/edb/schema/functions.py @@ -558,6 +558,19 @@ def compile_expr_field( return super().compile_expr_field( schema, context, field, value, track_schema_ref_exprs) + def get_dummy_expr_field_value( + self, + schema: s_schema.Schema, + context: sd.CommandContext, + field: so.Field[Any], + value: Any, + ) -> Optional[s_expr.Expression]: + if field.name == 'default': + type = self.scls.get_type(schema) + return s_types.type_dummy_expr(type, schema) + else: + raise NotImplementedError(f'unhandled field {field.name!r}') + class CreateParameter(ParameterCommand, sd.CreateObject[Parameter]): diff --git a/edb/schema/futures.py b/edb/schema/futures.py index 009d3004200..f955db4539f 100644 --- a/edb/schema/futures.py +++ b/edb/schema/futures.py @@ -26,8 +26,9 @@ from edb.edgeql import qltypes from . import delta as sd -from . import objects as so +from . import modules as s_mod from . import name as sn +from . import objects as so from . import schema as s_schema @@ -152,6 +153,7 @@ class AlterFutureBehavior( # any schema elements. @register_handler('simple_scoping') @register_handler('warn_old_scoping') +@register_handler('_scoping_noop_test') def toggle_scoping_future( cmd: FutureBehaviorCommand, schema: s_schema.Schema, @@ -165,7 +167,14 @@ def toggle_scoping_future( # We need a subcommand to apply the _propagate_if_expr_refs on, so # make an alter. - dummy_object = cmd.scls + dummy_object = ( + cmd.scls if isinstance(cmd, sd.CreateObject) + # HACK: On drops, the future doesn't exist, so grab + # a nonsense object we know will be there. + # The drops *shouldn't* ever fail, so it *shouldn't* + # show up in messages. + else schema.get_global(s_mod.Module, '__derived__') + ) alter_cmd = dummy_object.init_delta_command( schema, cmdtype=sd.AlterObject) @@ -180,6 +189,7 @@ def toggle_scoping_future( all_expr_objects: list[so.Object] = list(schema.get_objects( exclude_stdlib=True, + exclude_extensions=True, extra_filters=[lambda _, x: isinstance(x, types)], )) extra_refs = { diff --git a/edb/schema/types.py b/edb/schema/types.py index 0ac5926e6c4..835961fd1fb 100644 --- a/edb/schema/types.py +++ b/edb/schema/types.py @@ -2927,8 +2927,17 @@ def type_dummy_expr( ) -> Optional[s_expr.Expression]: if isinstance(typ, so.DerivableInheritingObject): typ = typ.get_nearest_non_derived_parent(schema) - text = f'assert_exists(<{typ.get_displayname(schema)}>{{}})' - return s_expr.Expression(text=text) + + q = qlast.FunctionCall( + func=('__std__', 'assert_exists'), + args=[ + qlast.TypeCast( + type=utils.typeref_to_ast(schema, typ), + expr=qlast.Set(elements=[]), + ) + ], + ) + return s_expr.Expression.from_ast(q, schema) class TypeCommand(sd.ObjectCommand[TypeT]): diff --git a/tests/test_dump01.py b/tests/test_dump01.py index c5af99960a6..77d62482070 100644 --- a/tests/test_dump01.py +++ b/tests/test_dump01.py @@ -1902,6 +1902,11 @@ async def test_dump01_branch_data(self): include_data=True, check_method=DumpTestCaseMixin.ensure_schema_data_integrity) + async def test_dump01_future_scope(self): + await self.con.execute(''' + create future _scoping_noop_test; + ''') + class TestDump01Compat( tb.DumpCompatTestCase, diff --git a/tests/test_edgeql_ddl.py b/tests/test_edgeql_ddl.py index c2565c2e357..77ef669d8f9 100644 --- a/tests/test_edgeql_ddl.py +++ b/tests/test_edgeql_ddl.py @@ -9452,30 +9452,6 @@ async def test_edgeql_ddl_extension_02(self): DROP EXTENSION TestAuthExtension; """) - async def test_edgeql_ddl_all_extensions_01(self): - # Install all extensions and then delete them all - exts = await self.con.query(''' - select distinct sys::ExtensionPackage.name - ''') - - ext_commands = ''.join(f'using extension {ext};\n' for ext in exts) - await self.con.execute(f""" - START MIGRATION TO {{ - {ext_commands} - module default {{ }} - }}; - POPULATE MIGRATION; - COMMIT MIGRATION; - """) - - await self.con.execute(f""" - START MIGRATION TO {{ - module default {{ }} - }}; - POPULATE MIGRATION; - COMMIT MIGRATION; - """) - async def test_edgeql_ddl_role_01(self): if not self.has_create_role: self.skipTest("create role is not supported by the backend") @@ -16326,6 +16302,7 @@ async def test_edgeql_ddl_scoping_future_01(self): create type T; insert T; insert T; + create function f(x: int64 = 0) -> int64 using (x); create function get_whatever() -> bool using ( all(T = T) ); @@ -16387,6 +16364,14 @@ async def test_edgeql_ddl_scoping_future_01(self): [dict(func=False, alias=False, query=False)], ) + async def test_edgeql_ddl_scoping_future_02(self): + await self.con.execute(""" + create future simple_scoping; + """) + await self.con.execute(""" + drop future simple_scoping; + """) + async def test_edgeql_ddl_no_volatile_computable_01(self): async with self.assertRaisesRegexTx( edgedb.QueryError, diff --git a/tests/test_edgeql_userddl.py b/tests/test_edgeql_userddl.py index e8f62fcc541..348618a9e45 100644 --- a/tests/test_edgeql_userddl.py +++ b/tests/test_edgeql_userddl.py @@ -417,3 +417,59 @@ async def test_edgeql_userddl_29(self): await self.con.execute(''' drop type ext::_test::X; ''') + + async def test_edgeql_userddl_all_extensions_01(self): + # Install all extensions and then delete them all + exts = await self.con.query(''' + select distinct sys::ExtensionPackage.name + ''') + + # This tests that toggling scoping futures works with + # extensions, and that the extensions work if it is enabled + # first. + await self.con.execute(f""" + START MIGRATION TO {{ + using future warn_old_scoping; + module default {{ }} + }}; + POPULATE MIGRATION; + COMMIT MIGRATION; + """) + + ext_commands = ''.join(f'using extension {ext};\n' for ext in exts) + await self.con.execute(f""" + START MIGRATION TO {{ + using future warn_old_scoping; + {ext_commands} + module default {{ }} + }}; + POPULATE MIGRATION; + COMMIT MIGRATION; + """) + + await self.con.execute(f""" + START MIGRATION TO {{ + {ext_commands} + module default {{ }} + }}; + POPULATE MIGRATION; + COMMIT MIGRATION; + """) + + await self.con.execute(f""" + START MIGRATION TO {{ + using future warn_old_scoping; + {ext_commands} + module default {{ }} + }}; + POPULATE MIGRATION; + COMMIT MIGRATION; + """) + + await self.con.execute(f""" + START MIGRATION TO {{ + module default {{ }} + }}; + POPULATE MIGRATION; + COMMIT MIGRATION; + """)