From 1bd1ce9f4b657d65e4b957c120adc8a20e997256 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Tue, 4 Feb 2025 15:09:01 -0800 Subject: [PATCH] Add testing for the patch system We test actually applying all of our real patches on release branches, but this adds testing on the master branch of the patch system itself. It works in basically the same way as our inplace upgrade tests: by creating a database, and then applying a diff that makes some synthetic testing changes to the server/compiler. I (re)discovered some various frustrating limitations of the patch system while doing this, but haven't fixed those yet. Fixes #5804. --- .github/workflows.src/tests-inplace.tpl.yml | 17 +- .github/workflows/tests-inplace.yml | 168 ++++++- edb/server/compiler/ddl.py | 3 +- tests/patch-testing/test.sh | 48 ++ tests/patch-testing/upgrade.patch | 480 ++++++++++++++++++++ 5 files changed, 709 insertions(+), 7 deletions(-) create mode 100755 tests/patch-testing/test.sh create mode 100644 tests/patch-testing/upgrade.patch diff --git a/.github/workflows.src/tests-inplace.tpl.yml b/.github/workflows.src/tests-inplace.tpl.yml index 66760848950b..5bc8f17785b4 100644 --- a/.github/workflows.src/tests-inplace.tpl.yml +++ b/.github/workflows.src/tests-inplace.tpl.yml @@ -1,6 +1,6 @@ <% from "tests.inc.yml" import build, calc_cache_key, restore_cache -%> -name: Tests of in-place upgrades +name: Tests of in-place upgrades and patching on: schedule: @@ -22,7 +22,7 @@ jobs: << calc_cache_key()|indent >> <%- endcall %> - test: + test-inplace: runs-on: ubuntu-latest needs: build strategy: @@ -47,12 +47,23 @@ jobs: run: | ./tests/inplace-testing/test.sh ${{ matrix.flags }} vt ${{ matrix.tests }} + test-patches: + runs-on: ubuntu-latest + needs: build + steps: + <<- restore_cache() >> + + - name: Test performing in-place upgrades + run: | + ./tests/patch-testing/test.sh -k test_link_on_target_delete -k test_edgeql_select -k test_edgeql_scope -k test_dump + workflow-notifications: if: failure() && github.event_name != 'pull_request' name: Notify in Slack on failures needs: - build - - test + - test-inplace + - test-patches runs-on: ubuntu-latest permissions: actions: 'read' diff --git a/.github/workflows/tests-inplace.yml b/.github/workflows/tests-inplace.yml index 380ba0edb131..2e457c0ed96e 100644 --- a/.github/workflows/tests-inplace.yml +++ b/.github/workflows/tests-inplace.yml @@ -1,4 +1,4 @@ -name: Tests of in-place upgrades +name: Tests of in-place upgrades and patching on: schedule: @@ -332,7 +332,7 @@ jobs: run: | edb server --bootstrap-only - test: + test-inplace: runs-on: ubuntu-latest needs: build strategy: @@ -508,12 +508,174 @@ jobs: run: | ./tests/inplace-testing/test.sh ${{ matrix.flags }} vt ${{ matrix.tests }} + test-patches: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: false + + - uses: actions/checkout@v4 + with: + fetch-depth: 50 + submodules: true + + - name: Set up Python + uses: actions/setup-python@v5 + id: setup-python + with: + python-version: '3.12.2' + cache: 'pip' + cache-dependency-path: | + pyproject.toml + + # The below is technically a lie as we are technically not + # inside a virtual env, but there is really no reason to bother + # actually creating and activating one as below works just fine. + - name: Export $VIRTUAL_ENV + run: | + venv="$(python -c 'import sys; sys.stdout.write(sys.prefix)')" + echo "VIRTUAL_ENV=${venv}" >> $GITHUB_ENV + + - name: Set up uv cache + uses: actions/cache@v4 + with: + path: ~/.cache/uv + key: uv-cache-${{ runner.os }}-py-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('pyproject.toml') }} + + - name: Download requirements.txt + uses: actions/cache@v4 + with: + path: requirements.txt + key: edb-requirements-${{ hashFiles('pyproject.toml') }} + + - name: Install Python dependencies + run: python -m pip install uv~=0.1.0 && uv pip install -U -r requirements.txt + + # Restore the artifacts and environment variables + + - name: Download shared artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: shared-artifacts + path: shared-artifacts + + - name: Set environment variables + run: | + echo EDGEDBCLI_GIT_REV=$(cat shared-artifacts/edgedbcli_git_rev.txt) >> $GITHUB_ENV + echo POSTGRES_GIT_REV=$(cat shared-artifacts/postgres_git_rev.txt) >> $GITHUB_ENV + echo STOLON_GIT_REV=$(cat shared-artifacts/stolon_git_rev.txt) >> $GITHUB_ENV + echo BUILD_LIB=$(python setup.py -q ci_helper --type build_lib) >> $GITHUB_ENV + echo BUILD_TEMP=$(python setup.py -q ci_helper --type build_temp) >> $GITHUB_ENV + + # Restore build cache + + - name: Restore cached EdgeDB CLI binaries + uses: actions/cache@v4 + id: cli-cache + with: + path: build/cli + key: edb-cli-v4-${{ env.EDGEDBCLI_GIT_REV }} + + - name: Restore cached Rust extensions + uses: actions/cache@v4 + id: rust-cache + with: + path: build/rust_extensions + key: edb-rust-v4-${{ hashFiles('shared-artifacts/rust_cache_key.txt') }} + + - name: Restore cached Cython extensions + uses: actions/cache@v4 + id: ext-cache + with: + path: build/extensions + key: edb-ext-v6-${{ hashFiles('shared-artifacts/ext_cache_key.txt') }} + + - name: Restore compiled parsers cache + uses: actions/cache@v4 + id: parsers-cache + with: + path: build/lib + key: edb-parsers-v3-${{ hashFiles('shared-artifacts/parsers_cache_key.txt') }} + + - name: Restore cached PostgreSQL build + uses: actions/cache@v4 + id: postgres-cache + with: + path: build/postgres/install + key: edb-postgres-v3-${{ env.POSTGRES_GIT_REV }}-${{ hashFiles('shared-artifacts/lib_cache_key.txt') }} + + - name: Restore cached Stolon build + uses: actions/cache@v4 + id: stolon-cache + with: + path: build/stolon/bin + key: edb-stolon-v2-${{ env.STOLON_GIT_REV }} + + - name: Restore bootstrap cache + uses: actions/cache@v4 + id: bootstrap-cache + with: + path: build/cache + key: edb-bootstrap-v2-${{ hashFiles('shared-artifacts/bootstrap_cache_key.txt') }} + + - name: Stop if we cannot retrieve the cache + if: | + steps.cli-cache.outputs.cache-hit != 'true' || + steps.rust-cache.outputs.cache-hit != 'true' || + steps.ext-cache.outputs.cache-hit != 'true' || + steps.parsers-cache.outputs.cache-hit != 'true' || + steps.postgres-cache.outputs.cache-hit != 'true' || + steps.stolon-cache.outputs.cache-hit != 'true' || + steps.bootstrap-cache.outputs.cache-hit != 'true' + run: | + echo ::error::Cannot retrieve build cache. + exit 1 + + - name: Validate cached binaries + run: | + # Validate EdgeDB CLI + ./build/cli/bin/edgedb --version || exit 1 + + # Validate Stolon + ./build/stolon/bin/stolon-sentinel --version || exit 1 + ./build/stolon/bin/stolon-keeper --version || exit 1 + ./build/stolon/bin/stolon-proxy --version || exit 1 + + # Validate PostgreSQL + ./build/postgres/install/bin/postgres --version || exit 1 + ./build/postgres/install/bin/pg_config --version || exit 1 + + - name: Restore cache into the source tree + run: | + cp -v build/cli/bin/edgedb edb/cli/edgedb + cp -v build/cli/bin/edgedb edb/cli/gel + rsync -av ./build/rust_extensions/edb/ ./edb/ + rsync -av ./build/extensions/edb/ ./edb/ + rsync -av ./build/lib/edb/ ./edb/ + cp build/postgres/install/stamp build/postgres/ + + - name: Install edgedb-server + env: + BUILD_EXT_MODE: skip + run: | + # --no-build-isolation because we have explicitly installed all deps + # and don't want them to be reinstalled in an "isolated env". + pip install --no-build-isolation --no-deps -e .[test,docs] + + - name: Test performing in-place upgrades + run: | + ./tests/patch-testing/test.sh -k test_link_on_target_delete -k test_edgeql_select -k test_edgeql_scope -k test_dump + workflow-notifications: if: failure() && github.event_name != 'pull_request' name: Notify in Slack on failures needs: - build - - test + - test-inplace + - test-patches runs-on: ubuntu-latest permissions: actions: 'read' diff --git a/edb/server/compiler/ddl.py b/edb/server/compiler/ddl.py index 1991466e92c4..0b0026bf08a9 100644 --- a/edb/server/compiler/ddl.py +++ b/edb/server/compiler/ddl.py @@ -1405,9 +1405,10 @@ def administer_repair_schema( return dbstate.DDLQuery( sql=sql, - user_schema=current_tx.get_user_schema_if_updated(), # type: ignore + user_schema=current_tx.get_user_schema_if_updated(), global_schema=current_tx.get_global_schema_if_updated(), config_ops=config_ops, + feature_used_metrics={}, ) diff --git a/tests/patch-testing/test.sh b/tests/patch-testing/test.sh new file mode 100755 index 000000000000..08188ccacc4a --- /dev/null +++ b/tests/patch-testing/test.sh @@ -0,0 +1,48 @@ +#!/bin/bash -ex + +while [[ $# -gt 0 ]]; do + case $1 in + --save-tarballs) + SAVE_TARBALLS=1 + shift + ;; + *) + break + ;; + esac +done + + +DIR="$1" +shift + +if ! git diff-index --quiet HEAD --; then + set +x + echo Refusing to run patching upgrade test with dirty git state. + echo "(The test makes local modifications.)" + exit 1 +fi + +make parsers + +# Setup the test database +edb inittestdb -D "$DIR" "$@" + + +if [ "$SAVE_TARBALLS" = 1 ]; then + tar cf "$DIR".tar "$DIR" +fi + + +# Upgrade to the new version +patch -f -p1 < tests/patch-testing/upgrade.patch +make parsers + +edb server --bootstrap-only --data-dir "$DIR" + +if [ "$SAVE_TARBALLS" = 1 ]; then + tar cf "$DIR"-upgraded.tar "$DIR" +fi + +# Test! +edb test --data-dir "$DIR" --use-data-dir-dbs -v "$@" diff --git a/tests/patch-testing/upgrade.patch b/tests/patch-testing/upgrade.patch new file mode 100644 index 000000000000..4daf6b9a3ca9 --- /dev/null +++ b/tests/patch-testing/upgrade.patch @@ -0,0 +1,480 @@ +diff --git a/edb/edgeql/ast.py b/edb/edgeql/ast.py +index 4c7bc4d3c..70a30b9c1 100644 +--- a/edb/edgeql/ast.py ++++ b/edb/edgeql/ast.py +@@ -1149,6 +1149,23 @@ class SetGlobalType(SetField): + reset_value: bool = False + + ++class BlobalCommand(ObjectDDL): ++ ++ __abstract_node__ = True ++ ++ ++class CreateBlobal(CreateObject, BlobalCommand): ++ pass ++ ++ ++class AlterBlobal(AlterObject, BlobalCommand): ++ pass ++ ++ ++class DropBlobal(DropObject, BlobalCommand): ++ pass ++ ++ + class LinkCommand(ObjectDDL): + + __abstract_node__ = True +diff --git a/edb/edgeql/codegen.py b/edb/edgeql/codegen.py +index c131325c7..00fbaec63 100644 +--- a/edb/edgeql/codegen.py ++++ b/edb/edgeql/codegen.py +@@ -2461,6 +2461,12 @@ class EdgeQLSourceGenerator(codegen.SourceGenerator): + def visit_DropGlobal(self, node: qlast.DropGlobal) -> None: + self._visit_DropObject(node, 'GLOBAL') + ++ def visit_CreateBlobal(self, node: qlast.CreateGlobal) -> None: ++ self._visit_CreateObject(node, 'BLOBAL') ++ ++ def visit_DropBlobal(self, node: qlast.DropGlobal) -> None: ++ self._visit_DropObject(node, 'BLOBAL') ++ + def visit_ConfigSet(self, node: qlast.ConfigSet) -> None: + if node.scope == qltypes.ConfigScope.GLOBAL: + self._write_keywords('SET GLOBAL ') +diff --git a/edb/edgeql/parser/grammar/ddl.py b/edb/edgeql/parser/grammar/ddl.py +index d6e6b73ec..94f4df8e6 100644 +--- a/edb/edgeql/parser/grammar/ddl.py ++++ b/edb/edgeql/parser/grammar/ddl.py +@@ -247,6 +247,14 @@ class InnerDDLStmt(Nonterm): + def reduce_DropGlobalStmt(self, *_): + pass + ++ @parsing.inline(0) ++ def reduce_CreateBlobalStmt(self, *_): ++ pass ++ ++ @parsing.inline(0) ++ def reduce_DropBlobalStmt(self, *_): ++ pass ++ + @parsing.inline(0) + def reduce_DropCastStmt(self, *_): + pass +@@ -3566,6 +3574,38 @@ class DropGlobalStmt(Nonterm): + name=kids[2].val + ) + ++# ++# CREATE BLOBAL ++# ++ ++ ++commands_block( ++ 'CreateBlobal', ++ SetFieldStmt, ++ CreateAnnotationValueStmt, ++) ++ ++ ++class CreateBlobalStmt(Nonterm): ++ def reduce_CreateBlobal(self, *kids): ++ """%reduce ++ CREATE BLOBAL NodeName ++ OptCreateBlobalCommandsBlock ++ """ ++ self.val = qlast.CreateBlobal( ++ name=kids[2].val, ++ commands=kids[3].val, ++ ) ++ ++ ++class DropBlobalStmt(Nonterm): ++ def reduce_DropBlobal(self, *kids): ++ r"""%reduce DROP BLOBAL NodeName""" ++ self.val = qlast.DropBlobal( ++ name=kids[2].val ++ ) ++ ++ + # + # MIGRATIONS + # +diff --git a/edb/lib/_testmode.edgeql b/edb/lib/_testmode.edgeql +index 761a5dc53..d9a70f541 100644 +--- a/edb/lib/_testmode.edgeql ++++ b/edb/lib/_testmode.edgeql +@@ -232,6 +232,15 @@ create extension package _conf VERSION '1.0' { + + # std::_gen_series + ++CREATE FUNCTION ++std::_upgrade_test( ++) -> std::str ++{ ++ SET volatility := 'Immutable'; ++ USING ('asdf'); ++}; ++ ++ + CREATE FUNCTION + std::_gen_series( + `start`: std::int64, +diff --git a/edb/lib/schema.edgeql b/edb/lib/schema.edgeql +index ae5c70d5b..b7692b16c 100644 +--- a/edb/lib/schema.edgeql ++++ b/edb/lib/schema.edgeql +@@ -526,9 +526,17 @@ CREATE TYPE schema::Global EXTENDING schema::AnnotationSubject { + }; + + ++CREATE TYPE schema::Blobal EXTENDING schema::AnnotationSubject { ++ CREATE PROPERTY required -> std::bool; ++}; ++ ++ + CREATE TYPE schema::Function + EXTENDING schema::CallableObject, schema::VolatilitySubject + { ++ CREATE PROPERTY test_field_a -> std::str; ++ CREATE PROPERTY test_nativecode_size -> std::int64; ++ + CREATE PROPERTY preserves_optionality -> std::bool { + SET default := false; + }; +diff --git a/edb/pgsql/delta.py b/edb/pgsql/delta.py +index b447e6a4c..fc7ee4a74 100644 +--- a/edb/pgsql/delta.py ++++ b/edb/pgsql/delta.py +@@ -748,6 +748,38 @@ class DeleteGlobal( + pass + + ++class BlobalCommand(MetaCommand): ++ pass ++ ++ ++class CreateBlobal( ++ BlobalCommand, ++ adapts=s_globals.CreateBlobal, ++): ++ pass ++ ++ ++class RenameBlobal( ++ BlobalCommand, ++ adapts=s_globals.RenameBlobal, ++): ++ pass ++ ++ ++class AlterBlobal( ++ BlobalCommand, ++ adapts=s_globals.AlterBlobal, ++): ++ pass ++ ++ ++class DeleteBlobal( ++ BlobalCommand, ++ adapts=s_globals.DeleteBlobal, ++): ++ pass ++ ++ + class AccessPolicyCommand(MetaCommand): + pass + +diff --git a/edb/pgsql/patches.py b/edb/pgsql/patches.py +index 9d63263a9..0a6f9eca6 100644 +--- a/edb/pgsql/patches.py ++++ b/edb/pgsql/patches.py +@@ -60,4 +60,38 @@ The current kinds are: + * ...+testmode - only run the patch in testmode. Works with any patch kind. + """ + PATCHES: list[tuple[str, str]] = [ ++ ('edgeql', ''' ++CREATE FUNCTION ++std::_upgrade_test( ++) -> std::str ++{ ++ SET volatility := 'Immutable'; ++ USING ('asdf'); ++}; ++'''), ++ ('edgeql+schema', ''' ++CREATE TYPE schema::Blobal EXTENDING schema::AnnotationSubject { ++ CREATE PROPERTY required -> std::bool; ++}; ++# ASDF! We can't apply these separately because it gets picked up from ++# the present schema nonsense! ++# The patch system is so janky. ++# Worse, adding these publically only works in the *first* +schema patch. ++ALTER TYPE schema::Function ++{ ++ CREATE PROPERTY test_field_a -> std::str; ++ CREATE PROPERTY test_nativecode_size -> std::int64; ++}; ++'''), ++ ('repair', ''), ++ ('edgeql+schema+config+testmode', ''' ++ALTER TYPE cfg::AbstractConfig { ++ CREATE PROPERTY __internal_sess_testvalue2 -> std::str { ++ CREATE ANNOTATION cfg::internal := 'true'; ++ SET default := '!'; ++ }; ++}; ++'''), ++ ('sql-introspection', ''), ++ ('metaschema-sql', 'SysConfigFullFunction'), + ] +diff --git a/edb/schema/functions.py b/edb/schema/functions.py +index 5e96de151..96f87b6b6 100644 +--- a/edb/schema/functions.py ++++ b/edb/schema/functions.py +@@ -1257,6 +1257,31 @@ class Function( + data_safe=True, + ): + ++ ## ++ test_field_a = so.SchemaField( ++ str, ++ default=None, ++ compcoef=0.4, ++ allow_ddl_set=True, ++ patch_level=1, ++ ) ++ ++ test_field_b = so.SchemaField( ++ str, ++ default=None, ++ compcoef=0.4, ++ allow_ddl_set=True, ++ patch_level=1, ++ ) ++ ++ test_nativecode_size = so.SchemaField( ++ int, ++ default=None, ++ compcoef=0.99, ++ patch_level=1, ++ ) ++ ## ++ + used_globals = so.SchemaField( + so.ObjectSet[s_globals.Global], + coerce=True, default=so.DEFAULT_CONSTRUCTOR, +@@ -1628,6 +1653,10 @@ class FunctionCommand( + nativecode.not_compiled() + ) + ++ if self.has_attribute_value('nativecode'): ++ code = self.get_attribute_value('nativecode') ++ self.set_attribute_value('test_nativecode_size', len(code.text)) ++ + # Resolving 'nativecode' has side effects on has_dml and + # volatility, so force it to happen as part of + # canonicalization of attributes. +diff --git a/edb/schema/globals.py b/edb/schema/globals.py +index 76ec833f4..460ea0a67 100644 +--- a/edb/schema/globals.py ++++ b/edb/schema/globals.py +@@ -619,3 +619,60 @@ class DeleteGlobal( + GlobalCommand, + ): + astnode = qlast.DropGlobal ++ ++ ++class Blobal( ++ so.QualifiedObject, ++ s_anno.AnnotationSubject, ++ qlkind=qltypes.SchemaObjectClass.GLOBAL, ++ data_safe=True, ++): ++ ++ required = so.SchemaField( ++ bool, ++ default=False, ++ compcoef=0.909, ++ allow_ddl_set=True, ++ ) ++ ++ ++class BlobalCommandContext( ++ sd.ObjectCommandContext[so.Object], ++ s_anno.AnnotationSubjectCommandContext ++): ++ pass ++ ++ ++class BlobalCommand( ++ sd.QualifiedObjectCommand[Blobal], ++ context_class=BlobalCommandContext, ++): ++ pass ++ ++ ++class CreateBlobal( ++ sd.CreateObject[Blobal], ++ BlobalCommand, ++): ++ astnode = qlast.CreateBlobal ++ ++ ++class RenameBlobal( ++ sd.RenameObject[Blobal], ++ BlobalCommand, ++): ++ pass ++ ++ ++class AlterBlobal( ++ sd.AlterObject[Blobal], ++ BlobalCommand, ++): ++ astnode = qlast.AlterBlobal ++ ++ ++class DeleteBlobal( ++ sd.DeleteObject[Blobal], ++ BlobalCommand, ++): ++ astnode = qlast.DropBlobal +diff --git a/edb/server/compiler/status.py b/edb/server/compiler/status.py +index d6ed5cf80..9fe61ecb0 100644 +--- a/edb/server/compiler/status.py ++++ b/edb/server/compiler/status.py +@@ -68,6 +68,8 @@ def get_schema_class(ql: qlast.ObjectDDL) -> qltypes.SchemaObjectClass: + return osc.ALIAS + case qlast.GlobalCommand(): + return osc.GLOBAL ++ case qlast.BlobalCommand(): ++ return osc.GLOBAL + case qlast.LinkCommand(): + return osc.LINK + case qlast.IndexCommand(): +diff --git a/tests/test_edgeql_select.py b/tests/test_edgeql_select.py +index 94fd98cf1..12c02055b 100644 +--- a/tests/test_edgeql_select.py ++++ b/tests/test_edgeql_select.py +@@ -1964,6 +1964,56 @@ class TestEdgeQLSelect(tb.QueryTestCase): + ]), + ) + ++ @test.xfail('Not fixed for patches; see #5844') ++ async def test_edgeql_select_baseobject_function_01(self): ++ # HACK: special inplace-upgrade test ++ await self.con.execute(''' ++ CREATE BLOBAL asdf { set required := true; }; ++ ''') ++ await self.assert_query_result( ++ r''' ++ select all_objects()[is schema::Blobal] { name }; ++ ''', ++ [{"name": "default::asdf"}], ++ ) ++ ++ async def test_edgeql_select_nativecode_size_01(self): ++ # HACK: special inplace-upgrade test ++ await self.assert_query_result( ++ r''' ++ select schema::Function { test_nativecode_size } ++ filter .name = 'default::ident' ++ ''', ++ [{"test_nativecode_size": 8}], ++ ) ++ ++ async def test_edgeql_select_config_hack_01(self): ++ # HACK: special inplace-upgrade test ++ await self.assert_query_result( ++ r''' ++ select cfg::Config.__internal_sess_testvalue2 ++ ''', ++ ['!'] ++ ) ++ await self.con.execute( ++ r''' ++ configure session set __internal_sess_testvalue2 := 'asdf'; ++ ''' ++ ) ++ await self.assert_query_result( ++ r''' ++ select cfg::Config.__internal_sess_testvalue2 ++ ''', ++ ['asdf'] ++ ) ++ ++ await self.assert_query_result( ++ r''' ++ select _upgrade_test() ++ ''', ++ ['asdf'] ++ ) ++ + async def test_edgeql_select_id_01(self): + # allow assigning id to a computed (#4781) + await self.con.query('SELECT schema::Type { XYZ := .id};') +diff --git a/tests/test_link_target_delete.py b/tests/test_link_target_delete.py +index 8982b3113..de1632b0a 100644 +--- a/tests/test_link_target_delete.py ++++ b/tests/test_link_target_delete.py +@@ -29,6 +29,7 @@ from edb.schema import links as s_links + from edb.schema import name as s_name + + from edb.testbase import server as stb ++from edb.tools import test + + + class TestLinkTargetDeleteSchema(tb.BaseSchemaLoadTest): +@@ -307,6 +308,50 @@ class TestLinkTargetDeleteDeclarative(stb.QueryTestCase): + DELETE (SELECT Target1 FILTER .name = 'Target1.1'); + """) + ++ @test.xfail('Not fixed for patches; see #5844') ++ async def test_link_on_target_delete_restrict_schema_01(self): ++ # HACK: special inplace-upgrade test ++ async with self._run_and_rollback(): ++ await self.con.execute(""" ++ CREATE BLOBAL asdf2 { set required := true; }; ++ ++ INSERT SchemaSource { ++ name := 'Source1.1', ++ schema_restrict := ( ++ SELECT schema::Blobal LIMIT 1 ++ ) ++ }; ++ """) ++ ++ with self.assertRaisesRegex( ++ edgedb.ConstraintViolationError, ++ 'prohibited by link'): ++ await self.con.execute(""" ++ DROP BLOBAL asdf2; ++ """) ++ ++ @test.xfail('Not fixed for patches; see #5844') ++ async def test_link_on_target_delete_restrict_schema_02(self): ++ # HACK: special inplace-upgrade test ++ async with self._run_and_rollback(): ++ await self.con.execute(""" ++ CREATE BLOBAL asdf2 { set required := true; }; ++ ++ INSERT SchemaSource { ++ name := 'Source1.1', ++ schema_m_restrict := ( ++ SELECT schema::Blobal LIMIT 1 ++ ) ++ }; ++ """) ++ ++ with self.assertRaisesRegex( ++ edgedb.ConstraintViolationError, ++ 'prohibited by link'): ++ await self.con.execute(""" ++ DROP BLOBAL asdf2; ++ """) ++ + async def test_link_on_target_delete_deferred_restrict_01(self): + exception_is_deferred = False +