diff --git a/edb/buildmeta.py b/edb/buildmeta.py index 68ecefe356d..07c602cd2d6 100644 --- a/edb/buildmeta.py +++ b/edb/buildmeta.py @@ -60,7 +60,7 @@ # The merge conflict there is a nice reminder that you probably need # to write a patch in edb/pgsql/patches.py, and then you should preserve # the old value. -EDGEDB_CATALOG_VERSION = 2024_11_08_00_00 +EDGEDB_CATALOG_VERSION = 2024_11_08_01_00 EDGEDB_MAJOR_VERSION = 6 diff --git a/edb/pgsql/metaschema.py b/edb/pgsql/metaschema.py index 3d0d4f9bfe9..0146429b28a 100644 --- a/edb/pgsql/metaschema.py +++ b/edb/pgsql/metaschema.py @@ -4456,8 +4456,13 @@ class GetPgTypeForEdgeDBTypeFunction2(trampoline.VersionedFunction): 'invalid_parameter_value', msg => ( format( - 'cannot determine OID of Gel type %L', - "typeid"::text + 'cannot determine Postgres OID of Gel %s(%L)%s', + "kind", + "typeid"::text, + (case when "elemid" is not null + then ' with element type ' || "elemid"::text + else '' + end) ) ) ) diff --git a/edb/schema/delta.py b/edb/schema/delta.py index af0f87d2eeb..220515671f0 100644 --- a/edb/schema/delta.py +++ b/edb/schema/delta.py @@ -3758,7 +3758,7 @@ def _delete_finalize( orig_schema = ctx.original_schema if refs: for ref in refs: - if (not context.is_deleting(ref) + if (not self._is_deleting_ref(schema, context, ref) and ref.is_blocking_ref(orig_schema, self.scls)): ref_strs.append( ref.get_verbosename(orig_schema, with_parent=True)) @@ -3781,6 +3781,21 @@ def _delete_finalize( return schema + def _is_deleting_ref( + self, + schema: s_schema.Schema, + context: CommandContext, + ref: so.Object, + ) -> bool: + if context.is_deleting(ref): + return True + + for op in self.get_prerequisites(): + if isinstance(op, DeleteObject) and op.scls == ref: + return True + + return False + def _has_outside_references( self, schema: s_schema.Schema, diff --git a/edb/schema/objects.py b/edb/schema/objects.py index 8b5585ad32c..cde90cf7ff9 100644 --- a/edb/schema/objects.py +++ b/edb/schema/objects.py @@ -3222,7 +3222,7 @@ def get_explicit_local_field_value( def allow_ref_propagation( self, schema: s_schema.Schema, - constext: sd.CommandContext, + context: sd.CommandContext, refdict: RefDict, ) -> bool: return True diff --git a/edb/schema/objtypes.py b/edb/schema/objtypes.py index 61bb8f9cdc4..439f6c3ca18 100644 --- a/edb/schema/objtypes.py +++ b/edb/schema/objtypes.py @@ -324,7 +324,7 @@ def _issubclass( def allow_ref_propagation( self, schema: s_schema.Schema, - constext: sd.CommandContext, + context: sd.CommandContext, refdict: so.RefDict, ) -> bool: return not self.is_view(schema) or refdict.attr == 'pointers' @@ -333,9 +333,7 @@ def as_type_delete_if_unused( self, schema: s_schema.Schema, ) -> Optional[sd.DeleteObject[ObjectType]]: - if not schema.get_by_id(self.id, default=None): - # this type was already deleted by some other op - # (probably alias types cleanup) + if not self._is_deletable(schema): return None # References to aliases can only occur inside other aliases, diff --git a/edb/schema/pointers.py b/edb/schema/pointers.py index 5fb90f7111e..82467e5dfb6 100644 --- a/edb/schema/pointers.py +++ b/edb/schema/pointers.py @@ -857,7 +857,7 @@ def has_user_defined_properties(self, schema: s_schema.Schema) -> bool: def allow_ref_propagation( self, schema: s_schema.Schema, - constext: sd.CommandContext, + context: sd.CommandContext, refdict: so.RefDict, ) -> bool: object_type = self.get_source(schema) diff --git a/edb/schema/properties.py b/edb/schema/properties.py index 5b1dd2a105c..f3cf6b90b0f 100644 --- a/edb/schema/properties.py +++ b/edb/schema/properties.py @@ -146,7 +146,7 @@ def is_link_property(self, schema: s_schema.Schema) -> bool: def allow_ref_propagation( self, schema: s_schema.Schema, - constext: sd.CommandContext, + context: sd.CommandContext, refdict: so.RefDict, ) -> bool: source = self.get_source(schema) diff --git a/edb/schema/scalars.py b/edb/schema/scalars.py index 81d422c9923..ec6a622c8b5 100644 --- a/edb/schema/scalars.py +++ b/edb/schema/scalars.py @@ -595,6 +595,26 @@ def _cmd_tree_from_ast( return cmd + def _create_begin( + self, + schema: s_schema.Schema, + context: sd.CommandContext, + ) -> s_schema.Schema: + schema = super()._create_begin(schema, context) + if ( + not context.canonical + and not self.scls.get_abstract(schema) + and not self.scls.get_transient(schema) + ): + # Create an array type for this scalar eagerly. + # We mostly do this so that we know the `backend_id` + # of the array type when running translation of SQL + # involving arrays of scalars. + schema2, arr_t = s_types.Array.from_subtypes(schema, [self.scls]) + self.add_caused(arr_t.as_shell(schema2).as_create_delta(schema2)) + + return schema + def validate_create( self, schema: s_schema.Schema, @@ -819,3 +839,19 @@ def _get_ast( return None else: return super()._get_ast(schema, context, parent_node=parent_node) + + def _delete_begin( + self, + schema: s_schema.Schema, + context: sd.CommandContext, + ) -> s_schema.Schema: + if not context.canonical: + schema2, arr_typ = s_types.Array.from_subtypes(schema, [self.scls]) + arr_op = arr_typ.init_delta_command( + schema2, + sd.DeleteObject, + if_exists=True, + ) + self.add_prerequisite(arr_op) + + return super()._delete_begin(schema, context) diff --git a/edb/schema/types.py b/edb/schema/types.py index 1c1abfd9a31..0ac5926e6c4 100644 --- a/edb/schema/types.py +++ b/edb/schema/types.py @@ -533,6 +533,14 @@ def as_type_delete_if_unused( return None + def _is_deletable( + self, + schema: s_schema.Schema, + ) -> bool: + # this type was already deleted by some other op + # (probably alias types cleanup) + return schema.get_by_id(self.id, default=None) is not None + class QualifiedType(so.QualifiedObject, Type): pass @@ -988,7 +996,14 @@ def as_delete_delta( assert isinstance(delta, sd.DeleteObject) if not isinstance(self, CollectionExprAlias): delta.if_exists = True - delta.if_unused = True + if not ( + isinstance(self, Array) + and self.get_element_type(schema).is_scalar() + ): + # Arrays of scalars are special, because we create them + # implicitly and overload reference checks to never + # delete them unless the scalar is also deleted. + delta.if_unused = True return delta @classmethod @@ -1140,9 +1155,7 @@ def as_type_delete_if_unused( self: CollectionTypeT, schema: s_schema.Schema, ) -> Optional[sd.DeleteObject[CollectionTypeT]]: - if not schema.get_by_id(self.id, default=None): - # this type was already deleted by some other op - # (probably alias types cleanup) + if not self._is_deletable(schema): return None return self.init_delta_command( @@ -1202,9 +1215,7 @@ def as_type_delete_if_unused( self: CollectionExprAliasT, schema: s_schema.Schema, ) -> Optional[sd.DeleteObject[CollectionExprAliasT]]: - if not schema.get_by_id(self.id, default=None): - # this type was already deleted by some other op - # (probably alias types cleanup) + if not self._is_deletable(schema): return None cmd = self.init_delta_command(schema, sd.DeleteObject, if_exists=True) @@ -3365,7 +3376,21 @@ class DeleteTupleExprAlias(DeleteCollectionExprAlias[TupleExprAlias]): class DeleteArray(DeleteCollectionType[Array]): - pass + # Prevent array types from getting deleted unless the element + # type is being deleted too. + def _has_outside_references( + self, + schema: s_schema.Schema, + context: sd.CommandContext, + ) -> bool: + if super()._has_outside_references(schema, context): + return True + + el_type = self.scls.get_element_type(schema) + if el_type.is_scalar() and not context.is_deleting(el_type): + return True + + return False class DeleteArrayExprAlias(DeleteCollectionExprAlias[ArrayExprAlias]): diff --git a/edb/server/bootstrap.py b/edb/server/bootstrap.py index a2e94db3a33..c829549bd79 100644 --- a/edb/server/bootstrap.py +++ b/edb/server/bootstrap.py @@ -2026,45 +2026,65 @@ def compile_sys_queries( # The code below re-syncs backend_id properties of Gel builtin # types with the actual OIDs in the DB. backend_id_fixup_edgeql = ''' - WITH - _ := ( - UPDATE {schema::ScalarType, schema::Tuple} - FILTER - NOT (.abstract ?? False) - AND NOT (.transient ?? False) - SET { - backend_id := sys::_get_pg_type_for_edgedb_type( - .id, - .__type__.name, - {}, - [is schema::ScalarType].sql_type ?? ( - select [is schema::ScalarType] - .bases[is schema::ScalarType] limit 1 - ).sql_type, - ) - } - ), - _ := ( - UPDATE {schema::Array, schema::Range, schema::MultiRange} - FILTER - NOT (.abstract ?? False) - AND NOT (.transient ?? False) - SET { - backend_id := sys::_get_pg_type_for_edgedb_type( - .id, - .__type__.name, - .element_type.id, - {}, - ) - } - ), - SELECT 1; + UPDATE schema::ScalarType + FILTER + NOT (.abstract ?? False) + AND NOT (.transient ?? False) + SET { + backend_id := sys::_get_pg_type_for_edgedb_type( + .id, + .__type__.name, + {}, + [is schema::ScalarType].sql_type ?? ( + select [is schema::ScalarType] + .bases[is schema::ScalarType] limit 1 + ).sql_type, + ) + }; + UPDATE schema::Tuple + FILTER + NOT (.abstract ?? False) + AND NOT (.transient ?? False) + SET { + backend_id := sys::_get_pg_type_for_edgedb_type( + .id, + .__type__.name, + {}, + [is schema::ScalarType].sql_type ?? ( + select [is schema::ScalarType] + .bases[is schema::ScalarType] limit 1 + ).sql_type, + ) + }; + UPDATE {schema::Range, schema::MultiRange} + FILTER + NOT (.abstract ?? False) + AND NOT (.transient ?? False) + SET { + backend_id := sys::_get_pg_type_for_edgedb_type( + .id, + .__type__.name, + .element_type.id, + {}, + ) + }; + UPDATE schema::Array + FILTER + NOT (.abstract ?? False) + AND NOT (.transient ?? False) + SET { + backend_id := sys::_get_pg_type_for_edgedb_type( + .id, + .__type__.name, + .element_type.id, + {}, + ) + }; ''' _, sql = compile_bootstrap_script( compiler, schema, backend_id_fixup_edgeql, - expected_cardinality_one=True, ) queries['backend_id_fixup'] = sql diff --git a/tests/schemas/dump02_setup.edgeql b/tests/schemas/dump02_setup.edgeql index f7700acfc3e..cce4641f57d 100644 --- a/tests/schemas/dump02_setup.edgeql +++ b/tests/schemas/dump02_setup.edgeql @@ -19,8 +19,8 @@ SET MODULE default; -CREATE MIGRATION m12hdldnmvzj5weaevxsmizppnl2poo6nconx2hcfkklbwcghqsmaq -ONTO m1vyvlra26tef6oe6yu37m7lfw7i3ef3n62m6om353dvnbm3mynqqa { +CREATE MIGRATION m1t2phsw6j2rgl4ieihm6mnvoln3ssayxncjzl2kwkxmunn2f6aqha +ONTO m1iej6dr3hk33wykqwqgg4xxo3tivpiznpb2mto7qsw2zgipsbfihq { CREATE TYPE default::Migrated; create type default::Migrated2 {}; }; diff --git a/tests/test_edgeql_ddl.py b/tests/test_edgeql_ddl.py index 5b0061fd029..e41d890c965 100644 --- a/tests/test_edgeql_ddl.py +++ b/tests/test_edgeql_ddl.py @@ -9412,6 +9412,13 @@ async def test_edgeql_ddl_extension_02(self): algo = ext::auth::JWTAlgo.RS256 ); }; + + create type ext::auth::Config extending std::BaseObject { + create property supported_algos: + array; + create multi property algo_config: + tuple; + }; } """) @@ -14823,7 +14830,7 @@ async def test_edgeql_ddl_drop_multi_prop_01(self): """) async def test_edgeql_ddl_collection_cleanup_01(self): - count_query = "SELECT count(schema::Array);" + count_query = "SELECT count(schema::Tuple);" orig_count = await self.con.query_single(count_query) await self.con.execute(r""" @@ -14832,9 +14839,9 @@ async def test_edgeql_ddl_collection_cleanup_01(self): CREATE SCALAR TYPE b extending str; CREATE SCALAR TYPE c extending str; - CREATE TYPE TestArrays { - CREATE PROPERTY x -> array; - CREATE PROPERTY y -> array; + CREATE TYPE TestTuples { + CREATE PROPERTY x -> tuple; + CREATE PROPERTY y -> tuple; }; """) @@ -14844,7 +14851,7 @@ async def test_edgeql_ddl_collection_cleanup_01(self): ) await self.con.execute(r""" - ALTER TYPE TestArrays { + ALTER TYPE TestTuples { DROP PROPERTY x; }; """) @@ -14855,10 +14862,10 @@ async def test_edgeql_ddl_collection_cleanup_01(self): ) await self.con.execute(r""" - ALTER TYPE TestArrays { + ALTER TYPE TestTuples { ALTER PROPERTY y { - SET TYPE array USING ( - >>.y); + SET TYPE tuple USING ( + >>.y); } }; """) @@ -14869,13 +14876,13 @@ async def test_edgeql_ddl_collection_cleanup_01(self): ) await self.con.execute(r""" - DROP TYPE TestArrays; + DROP TYPE TestTuples; """) self.assertEqual(await self.con.query_single(count_query), orig_count) async def test_edgeql_ddl_collection_cleanup_01b(self): - count_query = "SELECT count(schema::Array);" + count_query = "SELECT count(schema::Tuple);" orig_count = await self.con.query_single(count_query) await self.con.execute(r""" @@ -14884,10 +14891,10 @@ async def test_edgeql_ddl_collection_cleanup_01b(self): CREATE SCALAR TYPE b extending str; CREATE SCALAR TYPE c extending str; - CREATE TYPE TestArrays { - CREATE PROPERTY x -> array; - CREATE PROPERTY y -> array; - CREATE PROPERTY z -> array; + CREATE TYPE TestTuples { + CREATE PROPERTY x -> tuple; + CREATE PROPERTY y -> tuple; + CREATE PROPERTY z -> tuple; }; """) @@ -14897,7 +14904,7 @@ async def test_edgeql_ddl_collection_cleanup_01b(self): ) await self.con.execute(r""" - ALTER TYPE TestArrays { + ALTER TYPE TestTuples { DROP PROPERTY x; }; """) @@ -14908,10 +14915,10 @@ async def test_edgeql_ddl_collection_cleanup_01b(self): ) await self.con.execute(r""" - ALTER TYPE TestArrays { + ALTER TYPE TestTuples { ALTER PROPERTY y { - SET TYPE array USING ( - >>.y); + SET TYPE tuple USING ( + >>.y); } }; """) @@ -14922,7 +14929,7 @@ async def test_edgeql_ddl_collection_cleanup_01b(self): ) await self.con.execute(r""" - DROP TYPE TestArrays; + DROP TYPE TestTuples; """) self.assertEqual(await self.con.query_single(count_query), orig_count) @@ -14944,14 +14951,17 @@ async def test_edgeql_ddl_collection_cleanup_02(self): self.assertEqual( await self.con.query_single(count_query), - orig_count + 2, + orig_count + 3 + 2, ) await self.con.execute(r""" DROP TYPE TestArrays; """) - self.assertEqual(await self.con.query_single(count_query), orig_count) + self.assertEqual( + await self.con.query_single(count_query), + orig_count + 3, + ) async def test_edgeql_ddl_collection_cleanup_03(self): count_query = "SELECT count(schema::CollectionType);" @@ -14972,7 +14982,7 @@ async def test_edgeql_ddl_collection_cleanup_03(self): self.assertEqual( await self.con.query_single(count_query), - orig_count + 4, + orig_count + 3 + 2, ) await self.con.execute(r""" @@ -14980,9 +14990,14 @@ async def test_edgeql_ddl_collection_cleanup_03(self): x: array, z: tuple, y: array>); """) - self.assertEqual(await self.con.query_single(count_query), orig_count) self.assertEqual( - await self.con.query_single(elem_count_query), orig_elem_count) + await self.con.query_single(count_query), + orig_count + 3, + ) + self.assertEqual( + await self.con.query_single(elem_count_query), + orig_elem_count, + ) async def test_edgeql_ddl_collection_cleanup_04(self): count_query = "SELECT count(schema::CollectionType);" @@ -15005,7 +15020,7 @@ async def test_edgeql_ddl_collection_cleanup_04(self): self.assertEqual( await self.con.query_single(count_query), - orig_count + 1, + orig_count + 3 + 1, ) await self.con.execute(r""" @@ -15014,7 +15029,7 @@ async def test_edgeql_ddl_collection_cleanup_04(self): self.assertEqual( await self.con.query_single(count_query), - orig_count + 1, + orig_count + 3 + 1, ) await self.con.execute(r""" @@ -15023,7 +15038,7 @@ async def test_edgeql_ddl_collection_cleanup_04(self): self.assertEqual( await self.con.query_single(count_query), - orig_count + 2, + orig_count + 3 + 2, ) await self.con.execute(r""" @@ -15032,7 +15047,7 @@ async def test_edgeql_ddl_collection_cleanup_04(self): self.assertEqual( await self.con.query_single(count_query), - orig_count + 2, + orig_count + 3 + 2, ) await self.con.execute(r""" @@ -15041,7 +15056,7 @@ async def test_edgeql_ddl_collection_cleanup_04(self): self.assertEqual( await self.con.query_single(count_query), - orig_count + 2, + orig_count + 3 + 2, ) # Make a change that doesn't change the types @@ -15051,14 +15066,17 @@ async def test_edgeql_ddl_collection_cleanup_04(self): self.assertEqual( await self.con.query_single(count_query), - orig_count + 2, + orig_count + 3 + 2, ) await self.con.execute(r""" DROP ALIAS Bar; """) - self.assertEqual(await self.con.query_single(count_query), orig_count) + self.assertEqual( + await self.con.query_single(count_query), + orig_count + 3, + ) async def test_edgeql_ddl_collection_cleanup_05(self): count_query = "SELECT count(schema::CollectionType);" @@ -15074,7 +15092,9 @@ async def test_edgeql_ddl_collection_cleanup_05(self): self.assertEqual( await self.con.query_single(count_query), - orig_count + 2, # one for tuple, one for TupleExprAlias + orig_count + 2 + 2, # one for tuple + # one for TupleExprAlias, + # two for implicit array and array ) await self.con.execute(r""" @@ -15083,14 +15103,17 @@ async def test_edgeql_ddl_collection_cleanup_05(self): self.assertEqual( await self.con.query_single(count_query), - orig_count + 2, + orig_count + 2 + 2, ) await self.con.execute(r""" DROP ALIAS Bar; """) - self.assertEqual(await self.con.query_single(count_query), orig_count) + self.assertEqual( + await self.con.query_single(count_query), + orig_count + 2, + ) async def test_edgeql_ddl_drop_field_01(self): await self.con.execute(r"""