16from .
import connection, discover, exceptions, pool, service, testsuite_db
55 def __init__(self, conninfo: connection.PgConnectionInfo):
58 self.
_conn: psycopg2.extensions.connection |
None =
None
59 self.
_tables: list[str] |
None =
None
61 self.
_executer = concurrent.futures.ThreadPoolExecutor(max_workers=1)
63 def initialize(self, cleanup_exclude_tables: frozenset[str]):
67 with contextlib.closing(cursor):
68 cursor.execute(LIST_TABLES_SQL)
72 if table[0]
not in cleanup_exclude_tables
78 def conninfo(self) -> connection.PgConnectionInfo:
80 :py:class:`testsuite.databases.pgsql.connection.PgConnectionInfo`
85 def conn(self) -> psycopg2.extensions.connection:
86 """:returns: :py:class:`psycopg2.extensions.connection`"""
89 'Postgresql connection to {} was unexpectedly closed'.format(
97 self.
_conn.autocommit =
True
100 def cursor(self, **kwargs) -> psycopg2.extensions.cursor:
101 """:returns: :py:class:`psycopg2.extensions.cursor`"""
105 """Returns dictionary cursor, see psycopg2.extras.DictCursor
107 :returns: :py:class:`psycopg2.extensions.cursor`
109 kwargs[
'cursor_factory'] = psycopg2.extras.DictCursor
110 return self.
cursor(**kwargs)
113 """Apply queries to database"""
115 with contextlib.closing(cursor):
121 for query
in queries:
127 def schedule_truncation(self):
130 with contextlib.closing(cursor):
136 def _try_truncate_tables(self, cursor) -> None:
137 for _
in range(TRUNCATE_RETRIES):
141 except psycopg2.extensions.TransactionRollbackError
as exc:
142 logger.warning(
'Truncate table failed: %r', exc)
143 time.sleep(TRUNCATE_RETRY_DELAY)
147 def _truncate_tables(self, cursor) -> None:
150 TRUNCATE_SQL_TEMPLATE.format(tables=
','.join(self.
_tables)),
154 def _apply_query(cursor, query: PgQuery) ->
None:
156 cursor.execute(query.body)
157 except psycopg2.DatabaseError
as exc:
159 f
'PostgreSQL apply query error\nQuery from: {query.source}\n'
162 error_message += f
'File path: {query.path}\n'
163 error_message +=
'\n' + str(exc)
212 _applied_schemas: dict[str, set[pathlib.Path]]
213 _connections: dict[str, ConnectionWrapper]
219 pgsql_conninfo: connection.PgConnectionInfo,
222 skip_applied_schemas: bool,
234 def initialize(self) -> None:
246 def get_connection_cached(self, dbname) -> ConnectionWrapper:
253 def initialize_sharded_db(
255 database: discover.PgShardedDatabase,
258 'Initializing database %s for service %s...',
260 database.service_name,
262 for shard
in database.shards:
265 def _initialize_shard(self, shard: discover.PgShard) ->
None:
266 logger.debug(
'Initializing shard %s', shard.dbname)
272 current_hash = shard.get_schema_hash()
273 if applied_hash
is not None and current_hash == applied_hash:
274 logger.debug(
'Shard %s: schema is up to date', shard.dbname)
283 def _create_database(self, dbname: str) ->
None:
287 logger.debug(
'Creating database %s', dbname)
289 with connection.cursor()
as cursor:
290 cursor.execute(DROP_DATABASE_TEMPLATE.format(dbname))
291 cursor.execute(CREATE_DATABASE_TEMPLATE.format(dbname))
294 def _apply_schema(self, shard: discover.PgShard) ->
None:
296 for path
in shard.files:
297 if path
in applied_schemas:
300 applied_schemas.add(path)
303 for path
in shard.migrations:
304 if path
in applied_schemas:
307 applied_schemas.add(path)
309 def _run_script(self, dbname, path) -> None:
311 'Running sql script %s against database %s',
329 command_alias=
'psql',
333 f
'Failed to run psql script for DB {dbname!r}, see logs\n'
338 def _run_pgmigrate(self, dbname, path) -> None:
340 'Running migrations from %s against database %s',
359 command_alias=
'pgmigrate',
363 f
'Failed to run pgmigrate for DB {dbname!r}, see logs\n'
374 def _get_connection_uri(self, dbname: str) -> str:
375 return self.
_conninfo.replace(dbname=dbname).get_uri()
377 def _get_connection_dsn(self, dbname: str) -> str:
378 return self.
_conninfo.replace(dbname=dbname).get_dsn()