diff options
-rw-r--r-- | oslo_db/sqlalchemy/engines.py | 28 | ||||
-rw-r--r-- | oslo_db/sqlalchemy/exc_filters.py | 89 | ||||
-rw-r--r-- | oslo_db/sqlalchemy/provision.py | 10 | ||||
-rw-r--r-- | oslo_db/sqlalchemy/utils.py | 63 | ||||
-rw-r--r-- | oslo_db/tests/sqlalchemy/test_exc_filters.py | 11 | ||||
-rw-r--r-- | oslo_db/tests/sqlalchemy/test_utils.py | 18 | ||||
-rw-r--r-- | oslo_db/tests/test_api.py | 6 |
7 files changed, 52 insertions, 173 deletions
diff --git a/oslo_db/sqlalchemy/engines.py b/oslo_db/sqlalchemy/engines.py index 7c36c8a..847c71a 100644 --- a/oslo_db/sqlalchemy/engines.py +++ b/oslo_db/sqlalchemy/engines.py @@ -22,7 +22,6 @@ import logging import os import re import time -from urllib import parse import debtcollector.removals import debtcollector.renames @@ -130,31 +129,6 @@ def _setup_logging(connection_debug=0): logger.setLevel(logging.WARNING) -def _extend_url_parameters(url, connection_parameters): - # TODO(zzzeek): remove hasattr() conditional when SQLAlchemy 1.4 is the - # minimum version in requirements; call update_query_string() - # unconditionally - if hasattr(url, "update_query_string"): - return url.update_query_string(connection_parameters, append=True) - - # TODO(zzzeek): remove the remainder of this method when SQLAlchemy 1.4 - # is the minimum version in requirements - for key, value in parse.parse_qs( - connection_parameters).items(): - if key in url.query: - existing = url.query[key] - if not isinstance(existing, list): - url.query[key] = existing = utils.to_list(existing) - existing.extend(value) - value = existing - else: - url.query[key] = value - if len(value) == 1: - url.query[key] = value[0] - - return url - - def _vet_url(url): if "+" not in url.drivername and not url.drivername.startswith("sqlite"): if url.drivername.startswith("mysql"): @@ -201,7 +175,7 @@ def create_engine(sql_connection, sqlite_fk=False, mysql_sql_mode=None, url = utils.make_url(sql_connection) if connection_parameters: - url = _extend_url_parameters(url, connection_parameters) + url = url.update_query_string(connection_parameters, append=True) _vet_url(url) diff --git a/oslo_db/sqlalchemy/exc_filters.py b/oslo_db/sqlalchemy/exc_filters.py index 420b5c7..4ad7721 100644 --- a/oslo_db/sqlalchemy/exc_filters.py +++ b/oslo_db/sqlalchemy/exc_filters.py @@ -44,7 +44,7 @@ def filters(dbname, exception_type, regex): """ def _receive(fn): _registry[dbname][exception_type].extend( - (fn, re.compile(reg)) + (fn, re.compile(reg, re.DOTALL)) for reg in ((regex,) if not isinstance(regex, tuple) else regex) ) @@ -432,50 +432,51 @@ def handler(context): dialect = compat.dialect_from_exception_context(context) for per_dialect in _dialect_registries(dialect): - for exc in ( - context.sqlalchemy_exception, - context.original_exception): + for exc in (context.sqlalchemy_exception, context.original_exception): for super_ in exc.__class__.__mro__: - if super_ in per_dialect: - regexp_reg = per_dialect[super_] - for fn, regexp in regexp_reg: - match = regexp.match(exc.args[0]) - if match: - try: - fn( - exc, - match, - dialect.name, - context.is_disconnect) - except exception.DBError as dbe: - if ( - context.connection is not None and - not context.connection.closed and - not context.connection.invalidated and - ROLLBACK_CAUSE_KEY - in context.connection.info - ): - dbe.cause = \ - context.connection.info.pop( - ROLLBACK_CAUSE_KEY) - - if isinstance( - dbe, exception.DBConnectionError): - context.is_disconnect = True - - # new in 2.0.5 - if ( - hasattr(context, "is_pre_ping") and - context.is_pre_ping - ): - # if this is a pre-ping, need to - # integrate with the built - # in pre-ping handler that doesnt know - # about DBConnectionError, just needs - # the updated status - return None - - return dbe + if super_ not in per_dialect: + continue + + regexp_reg = per_dialect[super_] + for fn, regexp in regexp_reg: + match = regexp.match(exc.args[0]) + if not match: + continue + + try: + fn( + exc, + match, + dialect.name, + context.is_disconnect, + ) + except exception.DBError as dbe: + if ( + context.connection is not None and + not context.connection.closed and + not context.connection.invalidated and + ROLLBACK_CAUSE_KEY in context.connection.info + ): + dbe.cause = context.connection.info.pop( + ROLLBACK_CAUSE_KEY, + ) + + if isinstance(dbe, exception.DBConnectionError): + context.is_disconnect = True + + # new in 2.0.5 + if ( + hasattr(context, "is_pre_ping") and + context.is_pre_ping + ): + # if this is a pre-ping, need to + # integrate with the built + # in pre-ping handler that doesnt know + # about DBConnectionError, just needs + # the updated status + return None + + return dbe def register_engine(engine): diff --git a/oslo_db/sqlalchemy/provision.py b/oslo_db/sqlalchemy/provision.py index a6cc527..96e050c 100644 --- a/oslo_db/sqlalchemy/provision.py +++ b/oslo_db/sqlalchemy/provision.py @@ -493,15 +493,7 @@ class BackendImpl(object, metaclass=abc.ABCMeta): """ url = utils.make_url(base_url) - - # TODO(zzzeek): remove hasattr() conditional in favor of "url.set()" - # when SQLAlchemy 1.4 is the minimum version in requirements - if hasattr(url, "set"): - url = url.set(database=ident) - else: - # TODO(zzzeek): remove when SQLAlchemy 1.4 - # is the minimum version in requirements - url.database = ident + url = url.set(database=ident) return url diff --git a/oslo_db/sqlalchemy/utils.py b/oslo_db/sqlalchemy/utils.py index 58b2486..f8f57c4 100644 --- a/oslo_db/sqlalchemy/utils.py +++ b/oslo_db/sqlalchemy/utils.py @@ -29,8 +29,6 @@ import debtcollector.removals from oslo_utils import timeutils import sqlalchemy from sqlalchemy import Boolean -from sqlalchemy import CheckConstraint -from sqlalchemy import Column from sqlalchemy.engine import Connectable from sqlalchemy.engine import url as sa_url from sqlalchemy import exc @@ -42,7 +40,6 @@ from sqlalchemy import MetaData from sqlalchemy.sql.expression import cast from sqlalchemy.sql.expression import literal_column from sqlalchemy.sql import text -from sqlalchemy import String from sqlalchemy import Table from oslo_db._i18n import _ @@ -443,23 +440,6 @@ def get_table(engine, name): return Table(name, metadata, autoload_with=engine) -def _get_not_supported_column(col_name_col_instance, column_name): - try: - column = col_name_col_instance[column_name] - except KeyError: - msg = _("Please specify column %s in col_name_col_instance " - "param. It is required because column has unsupported " - "type by SQLite.") - raise exception.ColumnError(msg % column_name) - - if not isinstance(column, Column): - msg = _("col_name_col_instance param has wrong type of " - "column instance for column %s It should be instance " - "of sqlalchemy.Column.") - raise exception.ColumnError(msg % column_name) - return column - - def drop_old_duplicate_entries_from_table(engine, table_name, use_soft_delete, *uc_column_names): """Drop all old rows having the same values for columns in uc_columns. @@ -519,49 +499,6 @@ def drop_old_duplicate_entries_from_table(engine, table_name, conn.execute(delete_statement) -def _get_default_deleted_value(table): - if isinstance(table.c.id.type, Integer): - return 0 - if isinstance(table.c.id.type, String): - return "" - raise exception.ColumnError(_("Unsupported id columns type")) - - -def _restore_indexes_on_deleted_columns(engine, table_name, indexes): - table = get_table(engine, table_name) - - real_indexes = get_indexes(engine, table_name) - existing_index_names = dict( - [(index['name'], index['column_names']) for index in real_indexes]) - - # NOTE(boris-42): Restore indexes on `deleted` column - for index in indexes: - if 'deleted' not in index['column_names']: - continue - name = index['name'] - if name in existing_index_names: - column_names = [table.c[c] for c in existing_index_names[name]] - old_index = Index(name, *column_names, unique=index["unique"]) - old_index.drop(engine) - - column_names = [table.c[c] for c in index['column_names']] - new_index = Index(index["name"], *column_names, unique=index["unique"]) - new_index.create(engine) - - -def _is_deleted_column_constraint(constraint): - # NOTE(boris-42): There is no other way to check is CheckConstraint - # associated with deleted column. - if not isinstance(constraint, CheckConstraint): - return False - sqltext = str(constraint.sqltext) - # NOTE(zzzeek): SQLite never reflected CHECK contraints here - # in any case until version 1.1. Safe to assume that any CHECK - # that's talking about the value of "deleted in (something)" is - # the boolean constraint we're looking to get rid of. - return bool(re.match(r".*deleted in \(.*\)", sqltext, re.I)) - - def get_db_connection_info(conn_pieces): database = conn_pieces.path.strip('/') loc_pieces = conn_pieces.netloc.split('@') diff --git a/oslo_db/tests/sqlalchemy/test_exc_filters.py b/oslo_db/tests/sqlalchemy/test_exc_filters.py index 796ba6c..816ef7e 100644 --- a/oslo_db/tests/sqlalchemy/test_exc_filters.py +++ b/oslo_db/tests/sqlalchemy/test_exc_filters.py @@ -416,16 +416,7 @@ class TestNonExistentDatabase( super(TestNonExistentDatabase, self).setUp() url = utils.make_url(self.engine.url) - - # TODO(zzzeek): remove hasattr() conditional in favor of "url.set()" - # when SQLAlchemy 1.4 is the minimum version in requirements - if hasattr(url, "set"): - self.url = url.set(database="non_existent_database") - else: - # TODO(zzzeek): remove when SQLAlchemy 1.4 - # is the minimum version in requirements - url.database = 'non_existent_database' - self.url = url + self.url = url.set(database="non_existent_database") def test_raise(self): matched = self.assertRaises( diff --git a/oslo_db/tests/sqlalchemy/test_utils.py b/oslo_db/tests/sqlalchemy/test_utils.py index 059d015..94a52eb 100644 --- a/oslo_db/tests/sqlalchemy/test_utils.py +++ b/oslo_db/tests/sqlalchemy/test_utils.py @@ -19,7 +19,6 @@ from urllib import parse import fixtures import sqlalchemy from sqlalchemy import Boolean, Index, Integer, DateTime, String -from sqlalchemy import CheckConstraint from sqlalchemy import MetaData, Table, Column from sqlalchemy import ForeignKey, ForeignKeyConstraint from sqlalchemy.dialects.postgresql import psycopg2 @@ -801,23 +800,6 @@ class TestMigrationUtils(db_test_base._DbTestCase): for value in soft_deleted_values: self.assertIn(value['id'], deleted_rows_ids) - def test_detect_boolean_deleted_constraint_detection(self): - table_name = 'abc' - table = Table(table_name, self.meta, - Column('id', Integer, primary_key=True), - Column('deleted', Boolean(create_constraint=True))) - ck = [ - const for const in table.constraints if - isinstance(const, CheckConstraint)][0] - - self.assertTrue(utils._is_deleted_column_constraint(ck)) - - self.assertFalse( - utils._is_deleted_column_constraint( - CheckConstraint("deleted > 5") - ) - ) - def test_get_foreign_key_constraint_name(self): table_1 = Table('table_name_1', self.meta, Column('id', Integer, primary_key=True), diff --git a/oslo_db/tests/test_api.py b/oslo_db/tests/test_api.py index 2540780..d45dce6 100644 --- a/oslo_db/tests/test_api.py +++ b/oslo_db/tests/test_api.py @@ -210,7 +210,8 @@ class DBRetryRequestCase(DBAPITestCase): some_method() - def test_retry_wrapper_reaches_limit(self): + @mock.patch('oslo_db.api.time.sleep', return_value=None) + def test_retry_wrapper_reaches_limit(self, mock_sleep): max_retries = 2 @api.wrap_db_retry(max_retries=max_retries) @@ -222,7 +223,8 @@ class DBRetryRequestCase(DBAPITestCase): self.assertRaises(ValueError, some_method, res) self.assertEqual(max_retries + 1, res['result']) - def test_retry_wrapper_exception_checker(self): + @mock.patch('oslo_db.api.time.sleep', return_value=None) + def test_retry_wrapper_exception_checker(self, mock_sleep): def exception_checker(exc): return isinstance(exc, ValueError) and exc.args[0] < 5 |