diff --git a/edb/buildmeta.py b/edb/buildmeta.py index 996b1b0f421..ff4bbf45d6f 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 = 2025_02_04_00_00 +EDGEDB_CATALOG_VERSION = 2024_02_03_00_00 EDGEDB_MAJOR_VERSION = 7 diff --git a/edb/ir/statypes.py b/edb/ir/statypes.py index 09f122161e9..8e86853f3b4 100644 --- a/edb/ir/statypes.py +++ b/edb/ir/statypes.py @@ -27,7 +27,6 @@ Optional, Self, TypeVar, - TYPE_CHECKING, ) import dataclasses @@ -48,9 +47,6 @@ from edb.schema import name as s_name from edb.schema import objects as s_obj -if TYPE_CHECKING: - from edb.edgeql import qltypes - MISSING: Any = object() @@ -740,74 +736,3 @@ def get_translation_map(cls) -> Mapping[EnabledDisabledEnum, str]: EnabledDisabledEnum.Enabled: "true", EnabledDisabledEnum.Disabled: "false", } - - -class TransactionAccessModeEnum(enum.StrEnum): - ReadOnly = "ReadOnly" - ReadWrite = "ReadWrite" - - -class TransactionAccessMode( - EnumScalarType[TransactionAccessModeEnum], - edgeql_type="sys::TransactionAccessMode", -): - @classmethod - def get_translation_map(cls) -> Mapping[TransactionAccessModeEnum, str]: - return { - TransactionAccessModeEnum.ReadOnly: "true", - TransactionAccessModeEnum.ReadWrite: "false", - } - - def to_qltypes(self) -> qltypes.TransactionAccessMode: - from edb.edgeql import qltypes - match self._val: - case TransactionAccessModeEnum.ReadOnly: - return qltypes.TransactionAccessMode.READ_ONLY - case TransactionAccessModeEnum.ReadWrite: - return qltypes.TransactionAccessMode.READ_WRITE - case _: - raise AssertionError(f"unexpected value: {self._val!r}") - - -class TransactionDeferrabilityEnum(enum.StrEnum): - Deferrable = "Deferrable" - NotDeferrable = "NotDeferrable" - - -class TransactionDeferrability( - EnumScalarType[TransactionDeferrabilityEnum], - edgeql_type="sys::TransactionDeferrability", -): - @classmethod - def get_translation_map(cls) -> Mapping[TransactionDeferrabilityEnum, str]: - return { - TransactionDeferrabilityEnum.Deferrable: "true", - TransactionDeferrabilityEnum.NotDeferrable: "false", - } - - -class TransactionIsolationEnum(enum.StrEnum): - Serializable = "Serializable" - RepeatableRead = "RepeatableRead" - - -class TransactionIsolation( - EnumScalarType[TransactionIsolationEnum], - edgeql_type="sys::TransactionIsolation", -): - @classmethod - def get_translation_map(cls) -> Mapping[TransactionIsolationEnum, str]: - return { - TransactionIsolationEnum.Serializable: "serializable", - TransactionIsolationEnum.RepeatableRead: "repeatable read", - } - - def to_qltypes(self) -> qltypes.TransactionIsolationLevel: - from edb.edgeql import qltypes - match self._val: - case TransactionIsolationEnum.Serializable: - return qltypes.TransactionIsolationLevel.SERIALIZABLE - case TransactionIsolationEnum.RepeatableRead: - return qltypes.TransactionIsolationLevel.REPEATABLE_READ - case _: - raise AssertionError(f"unexpected value: {self._val!r}") diff --git a/edb/lib/cfg.edgeql b/edb/lib/cfg.edgeql index bf8cb298c1f..9b2d672c626 100644 --- a/edb/lib/cfg.edgeql +++ b/edb/lib/cfg.edgeql @@ -188,47 +188,6 @@ ALTER TYPE cfg::AbstractConfig { SET default := '60 seconds'; }; - CREATE REQUIRED PROPERTY default_transaction_isolation - -> sys::TransactionIsolation - { - CREATE ANNOTATION cfg::affects_compilation := 'true'; - CREATE ANNOTATION cfg::backend_setting := - '"default_transaction_isolation"'; - CREATE ANNOTATION std::description := - 'Controls the default isolation level of each new transaction, \ - including implicit transactions. Defaults to `Serializable`. \ - Note that changing this to a lower isolation level implies \ - that the transactions are also read-only by default regardless \ - of the value of the `default_transaction_access_mode` setting.'; - SET default := sys::TransactionIsolation.Serializable; - }; - - CREATE REQUIRED PROPERTY default_transaction_access_mode - -> sys::TransactionAccessMode - { - CREATE ANNOTATION cfg::affects_compilation := 'true'; - CREATE ANNOTATION std::description := - 'Controls the default read-only status of each new transaction, \ - including implicit transactions. Defaults to `ReadWrite`. \ - Note that if `default_transaction_isolation` is set to any value \ - other than Serializable this parameter is implied to be \ - `ReadOnly` regardless of the actual value.'; - SET default := sys::TransactionAccessMode.ReadWrite; - }; - - CREATE REQUIRED PROPERTY default_transaction_deferrable - -> sys::TransactionDeferrability - { - CREATE ANNOTATION cfg::backend_setting := - '"default_transaction_deferrable"'; - CREATE ANNOTATION std::description := - 'Controls the default deferrable status of each new transaction. \ - It currently has no effect on read-write transactions or those \ - operating at isolation levels lower than `Serializable`. \ - The default is `NotDeferrable`.'; - SET default := sys::TransactionDeferrability.NotDeferrable; - }; - CREATE REQUIRED PROPERTY session_idle_transaction_timeout -> std::duration { CREATE ANNOTATION cfg::backend_setting := '"idle_in_transaction_session_timeout"'; diff --git a/edb/lib/sys.edgeql b/edb/lib/sys.edgeql index 57385e822c6..67ebe6c846f 100644 --- a/edb/lib/sys.edgeql +++ b/edb/lib/sys.edgeql @@ -24,14 +24,6 @@ CREATE SCALAR TYPE sys::TransactionIsolation EXTENDING enum; -CREATE SCALAR TYPE sys::TransactionAccessMode - EXTENDING enum; - - -CREATE SCALAR TYPE sys::TransactionDeferrability - EXTENDING enum; - - CREATE SCALAR TYPE sys::VersionStage EXTENDING enum; diff --git a/edb/pgsql/compiler/clauses.py b/edb/pgsql/compiler/clauses.py index 7d1622d34f4..31e531edad4 100644 --- a/edb/pgsql/compiler/clauses.py +++ b/edb/pgsql/compiler/clauses.py @@ -489,7 +489,7 @@ def scan_check_ctes( name='flag', val=pgast.BooleanConstant(val=True) )], relation=pgast.RelRangeVar(relation=pgast.Relation( - name='_dml_dummy')), + schemaname='edgedb', name='_dml_dummy')), where_clause=pgast.Expr( name="=", lexpr=pgast.ColumnRef(name=["id"]), diff --git a/edb/pgsql/metaschema.py b/edb/pgsql/metaschema.py index ee1f0ba647e..c814655a4ab 100644 --- a/edb/pgsql/metaschema.py +++ b/edb/pgsql/metaschema.py @@ -186,6 +186,32 @@ def __init__(self) -> None: ) +class DMLDummyTable(dbops.Table): + """A empty dummy table used when we need to emit no-op DML. + + This is used by scan_check_ctes in the pgsql compiler to + force the evaluation of error checking. + """ + def __init__(self) -> None: + super().__init__(name=('edgedb', '_dml_dummy')) + + self.add_columns([ + dbops.Column(name='id', type='int8'), + dbops.Column(name='flag', type='bool'), + ]) + + self.add_constraint( + dbops.UniqueConstraint( + table_name=('edgedb', '_dml_dummy'), + columns=['id'], + ), + ) + + SETUP_QUERY = ''' + INSERT INTO edgedb._dml_dummy VALUES (0, false) + ''' + + class QueryCacheTable(dbops.Table): def __init__(self) -> None: super().__init__(name=('edgedb', '_query_cache')) @@ -5130,8 +5156,12 @@ def get_fixed_bootstrap_commands() -> dbops.CommandGroup: DBConfigTable(), ), # TODO: SHOULD THIS BE VERSIONED? + dbops.CreateTable(DMLDummyTable()), + # TODO: SHOULD THIS BE VERSIONED? dbops.CreateTable(QueryCacheTable()), + dbops.Query(DMLDummyTable.SETUP_QUERY), + dbops.CreateDomain(BigintDomain()), dbops.CreateDomain(ConfigMemoryDomain()), dbops.CreateDomain(TimestampTzDomain()), diff --git a/edb/server/bootstrap.py b/edb/server/bootstrap.py index d580ac36239..6d25f557b45 100644 --- a/edb/server/bootstrap.py +++ b/edb/server/bootstrap.py @@ -2591,11 +2591,9 @@ async def _bootstrap( await tpl_ctx.conn.sql_execute(b"SELECT pg_advisory_lock(3987734529)") try: - # Some of the views need access to the _edgecon_state table and the - # _dml_dummy table, so set it up. - tmp_table_query = ( - pgcon.SETUP_TEMP_TABLE_SCRIPT + pgcon.SETUP_DML_DUMMY_TABLE_SCRIPT - ) + # Some of the views need access to the _edgecon_state table, + # so set it up. + tmp_table_query = pgcon.SETUP_TEMP_TABLE_SCRIPT await _execute(tpl_ctx.conn, tmp_table_query) stdlib, config_spec, compiler = await _init_stdlib( diff --git a/edb/server/compiler/compiler.py b/edb/server/compiler/compiler.py index a2fba818511..a816bd7a17d 100644 --- a/edb/server/compiler/compiler.py +++ b/edb/server/compiler/compiler.py @@ -66,7 +66,6 @@ from edb.edgeql import qltypes from edb.ir import staeval as ireval -from edb.ir import statypes from edb.ir import ast as irast from edb.schema import ddl as s_ddl @@ -2142,39 +2141,22 @@ def _compile_ql_transaction( ctx.state.start_tx() - # Compute the effective isolation level - iso_config: statypes.TransactionIsolation = _get_config_val( - ctx, "default_transaction_isolation" - ) - default_iso = iso_config.to_qltypes() + sqls = 'START TRANSACTION' iso = ql.isolation - if iso is None: - iso = default_iso - - # Compute the effective access mode - access = ql.access - if access is None: - if default_iso is qltypes.TransactionIsolationLevel.SERIALIZABLE: - access_mode: statypes.TransactionAccessMode = _get_config_val( - ctx, "default_transaction_access_mode" + if iso is not None: + if ( + iso is not qltypes.TransactionIsolationLevel.SERIALIZABLE + and ql.access is not qltypes.TransactionAccessMode.READ_ONLY + ): + raise errors.TransactionError( + f"{iso.value} transaction isolation level is only " + "supported in read-only transactions", + span=ql.span, + hint=f"specify READ ONLY access mode", ) - access = access_mode.to_qltypes() - else: - access = qltypes.TransactionAccessMode.READ_ONLY - - # Guard against unsupported isolation + access combinations - if ( - iso is not qltypes.TransactionIsolationLevel.SERIALIZABLE - and access is not qltypes.TransactionAccessMode.READ_ONLY - ): - raise errors.TransactionError( - f"{iso.value} transaction isolation level is only " - "supported in read-only transactions", - span=ql.span, - hint=f"specify READ ONLY access mode", - ) - - sqls = f'START TRANSACTION ISOLATION LEVEL {iso.value} {access.value}' + sqls += f' ISOLATION LEVEL {iso.value}' + if ql.access is not None: + sqls += f' {ql.access.value}' if ql.deferrable is not None: sqls += f' {ql.deferrable.value}' sqls += ';' @@ -2351,7 +2333,7 @@ def _inject_config_cache_clear(sql_ast: pgast.Base) -> pgast.Base: name='flag', val=pgast.BooleanConstant(val=True) )], relation=pgast.RelRangeVar(relation=pgast.Relation( - name='_dml_dummy')), + schemaname='edgedb', name='_dml_dummy')), where_clause=pgast.Expr( name="=", lexpr=pgast.ColumnRef(name=["id"]), diff --git a/edb/server/compiler/explain/to_json.py b/edb/server/compiler/explain/to_json.py index 16bd7ad7ddb..fe4a8da5b3b 100644 --- a/edb/server/compiler/explain/to_json.py +++ b/edb/server/compiler/explain/to_json.py @@ -21,8 +21,6 @@ import enum import uuid -from edb.ir import statypes - class ToJson: def to_json(self) -> Any: @@ -38,6 +36,4 @@ def json_hook(value: Any) -> Any: return value.value elif isinstance(value, (frozenset, set)): return list(value) - elif isinstance(value, statypes.ScalarType): - return value.to_json() raise TypeError(f"Cannot serialize {value!r}") diff --git a/edb/server/dbview/dbview.pxd b/edb/server/dbview/dbview.pxd index 10326bf7453..95bfa8453bd 100644 --- a/edb/server/dbview/dbview.pxd +++ b/edb/server/dbview/dbview.pxd @@ -238,7 +238,6 @@ cdef class DatabaseConnectionView: cdef get_system_config(self) cpdef get_compilation_system_config(self) - cdef config_lookup(self, name) cdef set_modaliases(self, new_aliases) cpdef get_modaliases(self) diff --git a/edb/server/dbview/dbview.pyx b/edb/server/dbview/dbview.pyx index 49574d6dced..da3fb2529aa 100644 --- a/edb/server/dbview/dbview.pyx +++ b/edb/server/dbview/dbview.pyx @@ -1198,22 +1198,17 @@ cdef class DatabaseConnectionView: self._reset_tx_state() return side_effects - cdef config_lookup(self, name): - return self.server.config_lookup( - name, - self.get_session_config(), - self.get_database_config(), - self.get_system_config(), - ) - async def recompile_cached_queries(self, user_schema, schema_version): compiler_pool = self.server.get_compiler_pool() compile_concurrency = max(1, compiler_pool.get_size_hint() // 2) concurrency_control = asyncio.Semaphore(compile_concurrency) rv = [] - recompile_timeout = self.config_lookup( + recompile_timeout = self.server.config_lookup( "auto_rebuild_query_cache_timeout", + self.get_session_config(), + self.get_database_config(), + self.get_system_config(), ) loop = asyncio.get_running_loop() @@ -1442,6 +1437,9 @@ cdef class DatabaseConnectionView: user_schema_version = unit.user_schema_version if user_schema and not self.config_lookup( "auto_rebuild_query_cache", + self.get_session_config(), + self.get_database_config(), + self.get_system_config(), ): user_schema = None if user_schema: @@ -1717,23 +1715,6 @@ cdef class DatabaseConnectionView: msg, ) - if not self.in_tx() and query_capabilities & enums.Capability.WRITE: - isolation = self.config_lookup("default_transaction_isolation") - if isolation and isolation.to_str() != "Serializable": - raise query_capabilities.make_error( - ~enums.Capability.WRITE, - errors.TransactionError, - f"default_transaction_isolation is set to " - f"{isolation.to_str()}", - ) - access_mode = self.config_lookup("default_transaction_access_mode") - if access_mode and access_mode.to_str() == "ReadOnly": - raise query_capabilities.make_error( - ~enums.Capability.WRITE, - errors.TransactionError, - "default_transaction_access_mode is set to ReadOnly", - ) - async def reload_state_serializer(self): # This should only happen once when a different protocol version is # used after schema change, or non-current version of protocol is used diff --git a/edb/server/pgcon/__init__.py b/edb/server/pgcon/__init__.py index 7fde1af6075..8babc785151 100644 --- a/edb/server/pgcon/__init__.py +++ b/edb/server/pgcon/__init__.py @@ -33,7 +33,6 @@ pg_connect, SETUP_TEMP_TABLE_SCRIPT, SETUP_CONFIG_CACHE_SCRIPT, - SETUP_DML_DUMMY_TABLE_SCRIPT, RESET_STATIC_CFG_SCRIPT, ) @@ -46,6 +45,5 @@ 'BackendCatalogNameError', 'SETUP_TEMP_TABLE_SCRIPT', 'SETUP_CONFIG_CACHE_SCRIPT', - 'SETUP_DML_DUMMY_TABLE_SCRIPT', 'RESET_STATIC_CFG_SCRIPT' ) diff --git a/edb/server/pgcon/connect.py b/edb/server/pgcon/connect.py index 24d890ca8a1..aafe1532f3f 100644 --- a/edb/server/pgcon/connect.py +++ b/edb/server/pgcon/connect.py @@ -61,20 +61,6 @@ value edgedb._sys_config_val_t NOT NULL ); '''.strip() - -# A empty dummy table used when we need to emit no-op DML. -# -# This is used by scan_check_ctes in the pgsql compiler to -# force the evaluation of error checking. -SETUP_DML_DUMMY_TABLE_SCRIPT = ''' - CREATE TEMPORARY TABLE _dml_dummy ( - id int8, - flag bool, - unique(id) - ); - INSERT INTO _dml_dummy VALUES (0, false); -'''.strip() - RESET_STATIC_CFG_SCRIPT: bytes = b''' WITH x1 AS ( DELETE FROM _config_cache @@ -102,7 +88,6 @@ def _build_init_con_script(*, check_pg_is_in_recovery: bool) -> bytes: {SETUP_TEMP_TABLE_SCRIPT} {SETUP_CONFIG_CACHE_SCRIPT} - {SETUP_DML_DUMMY_TABLE_SCRIPT} PREPARE _clear_state AS WITH x1 AS ( diff --git a/edb/server/pgcon/pgcon.pyi b/edb/server/pgcon/pgcon.pyi index 9c0c9901574..29f38d5c6cb 100644 --- a/edb/server/pgcon/pgcon.pyi +++ b/edb/server/pgcon/pgcon.pyi @@ -105,4 +105,3 @@ class PGConnection(asyncio.Protocol): SETUP_TEMP_TABLE_SCRIPT: str SETUP_CONFIG_CACHE_SCRIPT: str -SETUP_DML_DUMMY_TABLE_SCRIPT: str diff --git a/tests/test_server_proto.py b/tests/test_server_proto.py index 0017339abef..9d5f0f4a53a 100644 --- a/tests/test_server_proto.py +++ b/tests/test_server_proto.py @@ -2069,366 +2069,6 @@ async def test_server_proto_tx_22(self): self.assertEqual(await self.con.query_single('SELECT 42'), 42) - async def assert_tx_isolation_and_default( - self, expected: str, *, default: str | None = None, conn=None - ): - if conn is None: - conn = self.con - if default is None: - default = expected - self.assertEqual( - await conn.query_single(''' - select ( - sys::get_transaction_isolation(), - assert_single(cfg::Config) - .default_transaction_isolation, - ); - '''), - (expected, default), - ) - - async def assert_read_only_and_default( - self, reason: str, *, default: str = 'ReadOnly', conn=None - ): - if conn is None: - conn = self.con - self.assertEqual( - await conn.query_single( - 'select assert_single(cfg::Config)' - '.default_transaction_access_mode;', - ), - default, - ) - with self.assertRaisesRegex( - edgedb.TransactionError, - reason, - ): - await self.con.query(''' - INSERT Tmp { - tmp := 'aaa' - }; - ''') - - async def test_server_proto_tx_23(self): - # Test that default_transaction_isolation is respected - - await self.con.query(''' - CONFIGURE SESSION - SET default_transaction_isolation := 'RepeatableRead'; - ''') - - try: - await self.assert_tx_isolation_and_default('RepeatableRead') - finally: - await self.con.query(''' - CONFIGURE SESSION - RESET default_transaction_isolation; - ''') - - async def test_server_proto_tx_24(self): - # default_transaction_isolation < Serializable enforces read-only - - await self.con.query(''' - CONFIGURE SESSION - SET default_transaction_isolation := 'RepeatableRead'; - ''') - - try: - await self.assert_read_only_and_default( - "cannot execute.*RepeatableRead", - default='ReadWrite', - ) - finally: - await self.con.query(''' - CONFIGURE SESSION - RESET default_transaction_isolation; - ''') - - self.assertEqual( - await self.con.query('SELECT 42'), - [42]) - - async def test_server_proto_tx_25(self): - # default_transaction_isolation < Serializable overrides read-write - - try: - await self.con.query(''' - CONFIGURE SESSION - SET default_transaction_isolation := 'RepeatableRead'; - ''') - await self.con.query(''' - CONFIGURE SESSION - SET default_transaction_access_mode := 'ReadWrite'; - ''') - - await self.assert_read_only_and_default( - "cannot execute.*RepeatableRead", - default='ReadWrite', - ) - finally: - await self.con.query(''' - CONFIGURE SESSION - RESET default_transaction_access_mode; - ''') - await self.con.query(''' - CONFIGURE SESSION - RESET default_transaction_isolation; - ''') - - self.assertEqual( - await self.con.query('SELECT 42'), - [42]) - - async def test_server_proto_tx_26(self): - # Test that default_transaction_access_mode is respected - - await self.con.query(''' - CONFIGURE SESSION - SET default_transaction_access_mode := 'ReadOnly'; - ''') - - try: - await self.assert_tx_isolation_and_default('Serializable') - await self.assert_read_only_and_default('cannot execute.*ReadOnly') - finally: - await self.con.query(''' - CONFIGURE SESSION - RESET default_transaction_access_mode; - ''') - - self.assertEqual( - await self.con.query('SELECT 42'), - [42]) - - async def test_server_proto_tx_27(self): - # Test that START TRANSACTION respects the default isolation - - try: - await self.con.query(''' - CONFIGURE SESSION - SET default_transaction_isolation := 'RepeatableRead'; - ''') - await self.con.query(''' - START TRANSACTION; - ''') - - await self.assert_tx_isolation_and_default('RepeatableRead') - - finally: - await self.con.query(f''' - ROLLBACK; - ''') - await self.con.query(''' - CONFIGURE SESSION - RESET default_transaction_isolation; - ''') - - self.assertEqual( - await self.con.query('SELECT 42'), - [42]) - - async def test_server_proto_tx_28(self): - # Test that non-serializable START TRANSACTION enforces read-only - - try: - await self.con.query(''' - CONFIGURE SESSION - SET default_transaction_isolation := 'RepeatableRead'; - ''') - await self.con.query(''' - START TRANSACTION; - ''') - - await self.assert_read_only_and_default( - 'read-only transaction', default='ReadWrite' - ) - finally: - await self.con.query(f''' - ROLLBACK; - ''') - await self.con.query(''' - CONFIGURE SESSION - RESET default_transaction_isolation; - ''') - - self.assertEqual( - await self.con.query('SELECT 42'), - [42]) - - async def test_server_proto_tx_29(self): - # Test that START TRANSACTION respects default read-only - - try: - await self.con.query(''' - CONFIGURE SESSION - SET default_transaction_access_mode:= 'ReadOnly'; - ''') - await self.con.query(''' - START TRANSACTION; - ''') - - await self.assert_tx_isolation_and_default('Serializable') - await self.assert_read_only_and_default('read-only transaction') - finally: - await self.con.query(f''' - ROLLBACK; - ''') - await self.con.query(''' - CONFIGURE SESSION - RESET default_transaction_access_mode; - ''') - - self.assertEqual( - await self.con.query('SELECT 42'), - [42]) - - async def test_server_proto_tx_30(self): - # Test that non-serializable START TRANSACTION conflicts read-write - - try: - await self.con.query(''' - CONFIGURE SESSION - SET default_transaction_isolation := 'RepeatableRead'; - ''') - with self.assertRaisesRegex( - edgedb.TransactionError, - 'only supported in read-only transactions', - ): - await self.con.query(''' - START TRANSACTION READ WRITE; - ''') - - finally: - await self.con.query(f''' - ROLLBACK; - ''') - await self.con.query(''' - CONFIGURE SESSION - RESET default_transaction_isolation; - ''') - - self.assertEqual( - await self.con.query('SELECT 42'), - [42]) - - async def test_server_proto_tx_31(self): - # Test that non-serializable START TRANSACTION works fine with the - # default read-only - - try: - await self.con.query(''' - CONFIGURE SESSION - SET default_transaction_access_mode:= 'ReadOnly'; - ''') - await self.con.query(''' - START TRANSACTION ISOLATION REPEATABLE READ; - ''') - - await self.assert_tx_isolation_and_default( - 'RepeatableRead', default='Serializable' - ) - await self.assert_read_only_and_default('read-only transaction') - finally: - await self.con.query(f''' - ROLLBACK; - ''') - await self.con.query(''' - CONFIGURE SESSION - RESET default_transaction_access_mode; - ''') - - self.assertEqual( - await self.con.query('SELECT 42'), - [42]) - - async def test_server_proto_tx_32(self): - # Test state sync across 2 frontend connections works fine - con2 = await self.connect(database=self.con.dbname) - try: - await con2.query(''' - CONFIGURE SESSION - SET default_transaction_isolation := 'RepeatableRead'; - ''') - await self.assert_tx_isolation_and_default( - 'RepeatableRead', conn=con2 - ) - - # Try a few times back and forth - this should be enough to hit - # the same backend connection. - for _ in range(5): - # Test state reset - await self.assert_tx_isolation_and_default('Serializable') - - # Test state sync - await self.assert_tx_isolation_and_default( - 'RepeatableRead', conn=con2 - ) - finally: - await con2.aclose() - - async def _test_with_sql_connection(self, test_func): - # Test state sync across EdgeQL / SQL interfaces - try: - import asyncpg - except ImportError: - self.skipTest("asyncpg is not installed") - - conn_args = self.get_connect_args(database=self.con.dbname) - scon = await asyncpg.connect( - host=conn_args['host'], - port=conn_args['port'], - user=conn_args['user'], - database=conn_args['database'], - password=conn_args['password'], - ssl='require' - ) - - try: - await test_func(scon) - finally: - await self.con.query(''' - CONFIGURE SESSION - RESET default_transaction_isolation; - ''') - await scon.close() - - async def test_server_proto_tx_33(self): - async def test(scon): - await scon.execute(''' - set default_transaction_isolation to 'repeatable read'; - ''') - for _ in range(5): - await self.assert_tx_isolation_and_default('Serializable') - self.assertEqual( - await scon.fetchval('show transaction_isolation'), - 'repeatable read', - ) - self.assertEqual( - await scon.fetchval('show default_transaction_isolation'), - 'repeatable read', - ) - - await self._test_with_sql_connection(test) - - async def test_server_proto_tx_34(self): - async def test(scon): - await self.con.query(''' - CONFIGURE SESSION - SET default_transaction_isolation := 'RepeatableRead'; - ''') - for _ in range(5): - await self.assert_tx_isolation_and_default('RepeatableRead') - self.assertEqual( - await scon.fetchval('show transaction_isolation'), - 'serializable', - ) - self.assertEqual( - await scon.fetchval('show default_transaction_isolation'), - 'serializable', - ) - - await self._test_with_sql_connection(test) - class TestServerProtoMigration(tb.QueryTestCase):