-
Notifications
You must be signed in to change notification settings - Fork 399
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Invalidate statement cache on schema changes affecting statement result.
PostgreSQL will raise an exception when it detects that the result type of the query has changed from when the statement was prepared. This may happen, for example, after an ALTER TABLE or SET search_path. When this happens, and there is no transaction running, we can simply re-prepare the statement and try again. If the transaction _is_ running, this error will put it into an error state, and we have no choice but to raise an exception. The original error is somewhat cryptic, so we raise a custom InvalidCachedStatementError with the original server exception as context. In either case we clear the statement cache for this connection and all other connections of the pool this connection belongs to (if any). See #72 and #76 for discussion. Fixes: #72.
- Loading branch information
Showing
9 changed files
with
274 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# Copyright (C) 2016-present the ayncpg authors and contributors | ||
# <see AUTHORS file> | ||
# | ||
# This module is part of asyncpg and is released under | ||
# the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
|
||
import asyncpg | ||
from asyncpg import _testbase as tb | ||
|
||
|
||
class TestCacheInvalidation(tb.ConnectedTestCase): | ||
async def test_prepare_cache_invalidation_silent(self): | ||
await self.con.execute('CREATE TABLE tab1(a int, b int)') | ||
|
||
try: | ||
await self.con.execute('INSERT INTO tab1 VALUES (1, 2)') | ||
result = await self.con.fetchrow('SELECT * FROM tab1') | ||
self.assertEqual(result, (1, 2)) | ||
|
||
await self.con.execute( | ||
'ALTER TABLE tab1 ALTER COLUMN b SET DATA TYPE text') | ||
|
||
result = await self.con.fetchrow('SELECT * FROM tab1') | ||
self.assertEqual(result, (1, '2')) | ||
finally: | ||
await self.con.execute('DROP TABLE tab1') | ||
|
||
async def test_prepare_cache_invalidation_in_transaction(self): | ||
await self.con.execute('CREATE TABLE tab1(a int, b int)') | ||
|
||
try: | ||
await self.con.execute('INSERT INTO tab1 VALUES (1, 2)') | ||
result = await self.con.fetchrow('SELECT * FROM tab1') | ||
self.assertEqual(result, (1, 2)) | ||
|
||
await self.con.execute( | ||
'ALTER TABLE tab1 ALTER COLUMN b SET DATA TYPE text') | ||
|
||
with self.assertRaisesRegex(asyncpg.InvalidCachedStatementError, | ||
'cached statement plan is invalid'): | ||
async with self.con.transaction(): | ||
result = await self.con.fetchrow('SELECT * FROM tab1') | ||
|
||
# This is now OK, | ||
result = await self.con.fetchrow('SELECT * FROM tab1') | ||
self.assertEqual(result, (1, '2')) | ||
finally: | ||
await self.con.execute('DROP TABLE tab1') | ||
|
||
async def test_prepare_cache_invalidation_in_pool(self): | ||
pool = await self.create_pool(database='postgres', | ||
min_size=2, max_size=2) | ||
|
||
await self.con.execute('CREATE TABLE tab1(a int, b int)') | ||
|
||
try: | ||
await self.con.execute('INSERT INTO tab1 VALUES (1, 2)') | ||
|
||
con1 = await pool.acquire() | ||
con2 = await pool.acquire() | ||
|
||
result = await con1.fetchrow('SELECT * FROM tab1') | ||
self.assertEqual(result, (1, 2)) | ||
|
||
result = await con2.fetchrow('SELECT * FROM tab1') | ||
self.assertEqual(result, (1, 2)) | ||
|
||
await self.con.execute( | ||
'ALTER TABLE tab1 ALTER COLUMN b SET DATA TYPE text') | ||
|
||
# con1 tries the same plan, will invalidate the cache | ||
# for the entire pool. | ||
result = await con1.fetchrow('SELECT * FROM tab1') | ||
self.assertEqual(result, (1, '2')) | ||
|
||
async with con2.transaction(): | ||
# This should work, as con1 should have invalidated | ||
# the plan cache. | ||
result = await con2.fetchrow('SELECT * FROM tab1') | ||
self.assertEqual(result, (1, '2')) | ||
|
||
finally: | ||
await self.con.execute('DROP TABLE tab1') | ||
await pool.close() |
Oops, something went wrong.