Skip to content

Commit

Permalink
Add test
Browse files Browse the repository at this point in the history
  • Loading branch information
fantix committed Feb 6, 2025
1 parent c48580c commit acff63d
Show file tree
Hide file tree
Showing 3 changed files with 260 additions and 0 deletions.
7 changes: 7 additions & 0 deletions edb/server/protocol/system_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ async def handle_request(
handle_liveness_query(request, response, tenant),
interruptable=False,
)
elif (
path_parts[:2] == ['testmode', 'connpool-gc'] and
request.method == b'GET' and
server.in_test_mode()
):
await tenant.testmode_connpool_gc(path_parts[2])
_response_ok(response, b'"OK"')
else:
_response(
response,
Expand Down
6 changes: 6 additions & 0 deletions edb/server/tenant.py
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,12 @@ async def ensure_database_not_connected(

await self._pg_ensure_database_not_connected(dbname)

async def testmode_connpool_gc(self, dbname: str) -> None:
if self.server.in_test_mode():
await self._pg_pool.prune_inactive_connections(
self.resolve_branch_name(dbname, None)
)

async def _pg_ensure_database_not_connected(self, dbname: str) -> None:
async with self.use_sys_pgcon() as pgcon:
conns = await pgcon.sql_fetch_col(
Expand Down
247 changes: 247 additions & 0 deletions tests/test_server_proto.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from edb.common import devmode
from edb.common import asyncutil
from edb.testbase import server as tb
from edb.server import args
from edb.server.compiler import enums
from edb.tools import test

Expand Down Expand Up @@ -2367,6 +2368,252 @@ async def test_server_proto_tx_31(self):
await self.con.query('SELECT 42'),
[42])

async def test_server_proto_tx_32(self):
# Test default_transaction_isolation is respected on system/database
# levels, and read-only is playing as planned accordingly. We use a
# separate Gel server here to avoid interference with other tests.

async with tb.start_edgedb_server(
http_endpoint_security=args.ServerEndpointSecurityMode.Optional,
net_worker_mode="disabled",
) as sd:
conn = await sd.connect()
try:
await self._test_server_proto_tx_32(sd, conn)
finally:
await conn.aclose()

async def _test_server_proto_tx_32(self, sd, conn):
await conn.query('''
CREATE TYPE X;
''')

# First, set default_transaction_isolation on the system level
await conn.query('''
CONFIGURE SYSTEM
SET default_transaction_isolation := 'RepeatableRead';
''')

# Verify isolation and access-mode are correct
async for tr in self.try_until_succeeds(ignore=AssertionError):
async with tr:
self.assertEqual(
await conn.query(
'select <str>sys::get_transaction_isolation();',
),
["RepeatableRead"],
)
with self.assertRaisesRegex(
edgedb.TransactionError,
'cannot execute.*RepeatableRead',
):
await conn.query('''
Insert X;
''')

# Verify again in an explicit transaction
tx = conn.transaction()
await tx.start()
self.assertEqual(
await conn.query(
'select <str>sys::get_transaction_isolation();',
),
["RepeatableRead"],
)
with self.assertRaisesRegex(
edgedb.TransactionError,
'read-only transaction',
):
await conn.query('''
Insert X;
''')
await tx.rollback()

# Changes to default_transaction_access_mode should not take effect
await conn.query('''
CONFIGURE SESSION
SET default_transaction_isolation := 'Serializable';
''')
await conn.query('''
CONFIGURE SYSTEM
SET default_transaction_access_mode := 'ReadWrite';
''')
await conn.query('''
CONFIGURE SESSION
RESET default_transaction_isolation;
''')
with self.assertRaisesRegex(
edgedb.TransactionError,
'cannot execute.*RepeatableRead',
):
await conn.query('''
Insert X;
''')
# Though, they are remembered - we'll verify this in the very end.
await conn.query('''
CONFIGURE SESSION
SET default_transaction_isolation := 'Serializable';
''')
await conn.query('''
CONFIGURE SYSTEM
SET default_transaction_access_mode := 'ReadOnly';
''')

# Now, set default_transaction_isolation on the database level
await conn.query('''
CONFIGURE SESSION
SET default_transaction_access_mode := 'ReadWrite';
''')
await conn.query('''
CONFIGURE CURRENT DATABASE
SET default_transaction_isolation := 'Serializable';
CONFIGURE SESSION
RESET default_transaction_isolation;
CONFIGURE SESSION
RESET default_transaction_access_mode;
''')
sd.call_system_api(f"/server/testmode/connpool-gc/{conn.dbname}")

# Verify isolation and access-mode (inherited from system) are correct
self.assertEqual(
await conn.query(
'select <str>sys::get_transaction_isolation();',
),
["Serializable"],
)
async for tr in self.try_until_fails(
wait_for=edgedb.TransactionError,
wait_for_regexp='cannot execute.*ReadOnly',
):
async with tr:
await conn.query('''
Insert X;
''')

# Similarly in an explicit transaction
tx = conn.transaction()
await tx.start()
self.assertEqual(
await conn.query(
'select <str>sys::get_transaction_isolation();',
),
["Serializable"],
)
with self.assertRaisesRegex(
edgedb.TransactionError,
'read-only transaction',
):
await conn.query('''
Insert X;
''')
await tx.rollback()

# Also override access mode on the database level
await conn.query('''
CONFIGURE SESSION
SET default_transaction_access_mode := 'ReadWrite';
''')
await conn.query('''
CONFIGURE CURRENT DATABASE
SET default_transaction_access_mode := 'ReadWrite';
CONFIGURE SESSION
RESET default_transaction_access_mode;
''')
self.assertEqual(
await conn.query(
'select <str>sys::get_transaction_isolation();',
),
["Serializable"],
)
await conn.query('''
Insert X;
''')
async with conn.transaction():
self.assertEqual(
await conn.query(
'select <str>sys::get_transaction_isolation();',
),
["Serializable"],
)
await conn.query('''
Insert X;
''')

# Now reset the database level settings, we should get back to the
# system level RepeatableRead + ReadOnly
await conn.query('''
CONFIGURE CURRENT DATABASE
RESET default_transaction_isolation;
CONFIGURE CURRENT DATABASE
RESET default_transaction_access_mode;
''')
sd.call_system_api(f"/server/testmode/connpool-gc/{conn.dbname}")
self.assertEqual(
await conn.query(
'select <str>sys::get_transaction_isolation();',
),
["RepeatableRead"],
)
with self.assertRaisesRegex(
edgedb.TransactionError,
'cannot execute.*RepeatableRead',
):
await conn.query('''
Insert X;
''')

# Reset the isolation on the system level first, the Gel default
# should be effective instead of backend PG default.
await conn.query('''
CONFIGURE SESSION
SET default_transaction_isolation := 'Serializable';
CONFIGURE SESSION
SET default_transaction_access_mode := 'ReadWrite';
''')
await conn.query('''
CONFIGURE SYSTEM
RESET default_transaction_isolation;
''')
await conn.query('''
CONFIGURE SESSION
RESET default_transaction_isolation;
CONFIGURE SESSION
RESET default_transaction_access_mode;
''')
async for tr in self.try_until_succeeds(ignore=AssertionError):
async with tr:
self.assertEqual(
await conn.query(
'select <str>sys::get_transaction_isolation();',
),
["Serializable"],
)
# The previously-remembered ReadOnly is now effective
with self.assertRaisesRegex(
edgedb.TransactionError,
'cannot execute.*ReadOnly',
):
await conn.query('''
Insert X;
''')

# Reset the access mode on the system level, everything is back
await conn.query('''
CONFIGURE SESSION
SET default_transaction_access_mode := 'ReadWrite';
''')
await conn.query('''
CONFIGURE SYSTEM
RESET default_transaction_access_mode;
''')
await conn.query('''
CONFIGURE SESSION
RESET default_transaction_access_mode;
''')
await conn.query('''
Insert X;
''')


class TestServerProtoMigration(tb.QueryTestCase):

Expand Down

0 comments on commit acff63d

Please sign in to comment.