From 12cfd5be6d9ce66b4792f8d52d1faa4e32c350ff Mon Sep 17 00:00:00 2001 From: Sean Dague <sean@dague.net> Date: Tue, 27 Sep 2016 15:01:21 -0400 Subject: [PATCH] Unwind circular import issue with api / utils The DeleteFromSelect function was added to sqla.utils, which is mostly the home of 2 shadow table functions that need to import api.py to get constants related to shadow tables. We now exclusively use that function in api.py for archiving code. That causes some oddities around module import. This moves DeleteFromSelect so we can get rid of these odd late imports. Change-Id: I59c3444c2258f59a09a9c885bd9490055e278998 --- nova/db/sqlalchemy/api.py | 33 ++++++++++++++++++++++----------- nova/db/sqlalchemy/utils.py | 20 -------------------- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index a6743a13b..2fbd9ae5f 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -38,6 +38,7 @@ from six.moves import range import sqlalchemy as sa from sqlalchemy import and_ from sqlalchemy.exc import NoSuchTableError +from sqlalchemy.ext.compiler import compiles from sqlalchemy import MetaData from sqlalchemy import or_ from sqlalchemy.orm import aliased @@ -50,6 +51,7 @@ from sqlalchemy.schema import Table from sqlalchemy import sql from sqlalchemy.sql.expression import asc from sqlalchemy.sql.expression import desc +from sqlalchemy.sql.expression import UpdateBase from sqlalchemy.sql import false from sqlalchemy.sql import func from sqlalchemy.sql import null @@ -416,6 +418,23 @@ class InequalityCondition(object): return [field != value for value in self.values] +class DeleteFromSelect(UpdateBase): + def __init__(self, table, select, column): + self.table = table + self.select = select + self.column = column + + +# NOTE(guochbo): some versions of MySQL doesn't yet support subquery with +# 'LIMIT & IN/ALL/ANY/SOME' We need work around this with nesting select . +@compiles(DeleteFromSelect) +def visit_delete_from_select(element, compiler, **kw): + return "DELETE FROM %s WHERE %s in (SELECT T1.%s FROM (%s) as T1)" % ( + compiler.process(element.table, asfrom=True), + compiler.process(element.column), + element.column.name, + compiler.process(element.select)) + ################### @@ -6284,10 +6303,6 @@ def _archive_if_instance_deleted(table, shadow_table, instances, conn, Logic is: if I have a column called instance_uuid, and that instance is deleted, then I can be deleted. """ - # NOTE(guochbo): There is a circular import, nova.db.sqlalchemy.utils - # imports nova.db.sqlalchemy.api. - from nova.db.sqlalchemy import utils as db_utils - query_insert = shadow_table.insert(inline=True).\ from_select( [c.name for c in table.c], @@ -6302,8 +6317,8 @@ def _archive_if_instance_deleted(table, shadow_table, instances, conn, and_(instances.c.deleted != instances.c.deleted.default.arg, instances.c.uuid == table.c.instance_uuid)).\ order_by(table.c.id).limit(max_rows) - delete_statement = db_utils.DeleteFromSelect(table, query_delete, - table.c.id) + delete_statement = DeleteFromSelect(table, query_delete, + table.c.id) try: with conn.begin(): @@ -6323,10 +6338,6 @@ def _archive_deleted_rows_for_table(tablename, max_rows): :returns: number of rows archived """ - # NOTE(guochbo): There is a circular import, nova.db.sqlalchemy.utils - # imports nova.db.sqlalchemy.api. - from nova.db.sqlalchemy import utils as db_utils - engine = get_engine() conn = engine.connect() metadata = MetaData() @@ -6396,7 +6407,7 @@ def _archive_deleted_rows_for_table(tablename, max_rows): deleted_column != deleted_column.default.arg).\ order_by(column).limit(max_rows) - delete_statement = db_utils.DeleteFromSelect(table, query_delete, column) + delete_statement = DeleteFromSelect(table, query_delete, column) try: # Group the insert and delete in a transaction. with conn.begin(): diff --git a/nova/db/sqlalchemy/utils.py b/nova/db/sqlalchemy/utils.py index 7d0770982..f408cb9bd 100644 --- a/nova/db/sqlalchemy/utils.py +++ b/nova/db/sqlalchemy/utils.py @@ -17,9 +17,7 @@ from oslo_db import exception as db_exc from oslo_db.sqlalchemy import utils as oslodbutils from oslo_log import log as logging from sqlalchemy.exc import OperationalError -from sqlalchemy.ext.compiler import compiles from sqlalchemy import MetaData -from sqlalchemy.sql.expression import UpdateBase from sqlalchemy import Table from sqlalchemy.types import NullType @@ -31,24 +29,6 @@ from nova.i18n import _, _LE LOG = logging.getLogger(__name__) -class DeleteFromSelect(UpdateBase): - def __init__(self, table, select, column): - self.table = table - self.select = select - self.column = column - - -# NOTE(guochbo): some versions of MySQL doesn't yet support subquery with -# 'LIMIT & IN/ALL/ANY/SOME' We need work around this with nesting select . -@compiles(DeleteFromSelect) -def visit_delete_from_select(element, compiler, **kw): - return "DELETE FROM %s WHERE %s in (SELECT T1.%s FROM (%s) as T1)" % ( - compiler.process(element.table, asfrom=True), - compiler.process(element.column), - element.column.name, - compiler.process(element.select)) - - def check_shadow_table(migrate_engine, table_name): """This method checks that table with ``table_name`` and corresponding shadow table have same columns.