diff options
author | Timofey Durakov <tdurakov@mirantis.com> | 2015-03-11 17:58:40 +0300 |
---|---|---|
committer | Timofey Durakov <tdurakov@mirantis.com> | 2015-03-13 13:44:54 +0300 |
commit | 74b539baba99951563bce68799ac55fe824a7b60 (patch) | |
tree | f05bb62f347aebdef857f5ce5d00782baf6641a4 | |
parent | 700a01bc77838720512bacad5977658f47091916 (diff) | |
download | oslo-db-74b539baba99951563bce68799ac55fe824a7b60.tar.gz |
Fixed bug in InsertFromSelect columns order
Previous realization generates query string like:
`INSERT INTO tbl1 SELECT tbl2.ID, tbl2.name,
tbl2.currdate FROM tbl2;`. It doesn't specify
columns in INSERT clause. If tbl1 columns differs
from tbl2, execution of insert fails, or silently insert
data in incorrect columns. To fix this implementation of
class changed to default SQLAlchemy insert from select.
added extra parameter 'columns', which allows to determine
column order in INSERT clause.
Closes-Bug:#1430884
Change-Id: Ica5cdea10d9aa253f395ec553559dba54e062028
-rw-r--r-- | oslo_db/sqlalchemy/utils.py | 45 | ||||
-rw-r--r-- | oslo_db/tests/sqlalchemy/test_utils.py | 73 |
2 files changed, 105 insertions, 13 deletions
diff --git a/oslo_db/sqlalchemy/utils.py b/oslo_db/sqlalchemy/utils.py index 932d409..8b18851 100644 --- a/oslo_db/sqlalchemy/utils.py +++ b/oslo_db/sqlalchemy/utils.py @@ -30,14 +30,12 @@ from sqlalchemy import Column from sqlalchemy.engine import Connectable from sqlalchemy.engine import reflection from sqlalchemy.engine import url as sa_url -from sqlalchemy.ext.compiler import compiles from sqlalchemy import func from sqlalchemy import Index from sqlalchemy import inspect from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy.sql.expression import literal_column -from sqlalchemy.sql.expression import UpdateBase from sqlalchemy.sql import text from sqlalchemy import String from sqlalchemy import Table @@ -346,19 +344,40 @@ def get_table(engine, name): return Table(name, metadata, autoload=True) -class InsertFromSelect(UpdateBase): - """Form the base for `INSERT INTO table (SELECT ... )` statement.""" - def __init__(self, table, select): - self.table = table - self.select = select +class InsertFromSelect(object): + """Form the base for `INSERT INTO table (SELECT ... )` statement. + DEPRECATED: this class is deprecated and will be removed from oslo.db + in a few releases. Use default SQLAlchemy insert from select implementation + instead -@compiles(InsertFromSelect) -def visit_insert_from_select(element, compiler, **kw): - """Form the `INSERT INTO table (SELECT ... )` statement.""" - return "INSERT INTO %s %s" % ( - compiler.process(element.table, asfrom=True), - compiler.process(element.select)) + :param table: table to insert records + :param select: select query + :param cols: list of columns to specify in insert clause + :return: SQLAlchemy :class:`Insert` object instance + + Usage: + + .. code-block:: python + + select = sql.select(table_from) + insert = InsertFromSelect(table_to, select, + ['id', 'name', 'insert_date']) + engine.execute(insert) + + """ + # NOTE(tdurakov): Insert from select implementation added to SQLAlchemy + # starting from version 0.8.7. Default SQLAlchemy implementation should be + # used instead of this. Deprecated. + + def __new__(cls, table, select, cols=None): + if not cols: + cols = [c.name for c in table.c] + + return table.insert(inline=True).from_select(cols, select) + + def __init__(self, table, select, cols=None): + pass def _get_not_supported_column(col_name_col_instance, column_name): diff --git a/oslo_db/tests/sqlalchemy/test_utils.py b/oslo_db/tests/sqlalchemy/test_utils.py index 06a1445..79bd2ec 100644 --- a/oslo_db/tests/sqlalchemy/test_utils.py +++ b/oslo_db/tests/sqlalchemy/test_utils.py @@ -508,6 +508,79 @@ class TestMigrationUtils(db_test_base.DbTestCase): # Verify we really have 4 rows in insert_table self.assertEqual(len(rows), 4) + def test_insert_from_select_with_specified_columns(self): + insert_table_name = "__test_insert_to_table__" + select_table_name = "__test_select_from_table__" + uuidstrs = [] + for unused in range(10): + uuidstrs.append(uuid.uuid4().hex) + insert_table = Table( + insert_table_name, self.meta, + Column('id', Integer, primary_key=True, + nullable=False, autoincrement=True), + Column('uuid', String(36), nullable=False)) + select_table = Table( + select_table_name, self.meta, + Column('id', Integer, primary_key=True, + nullable=False, autoincrement=True), + Column('uuid', String(36), nullable=False)) + + insert_table.create() + select_table.create() + # Add 10 rows to select_table + for uuidstr in uuidstrs: + ins_stmt = select_table.insert().values(uuid=uuidstr) + self.conn.execute(ins_stmt) + + # Select 4 rows in one chunk from select_table + column = select_table.c.id + query_insert = select([select_table], + select_table.c.id < 5).order_by(column) + insert_statement = utils.InsertFromSelect(insert_table, + query_insert, ['id', 'uuid']) + result_insert = self.conn.execute(insert_statement) + # Verify we insert 4 rows + self.assertEqual(result_insert.rowcount, 4) + + query_all = select([insert_table]).where( + insert_table.c.uuid.in_(uuidstrs)) + rows = self.conn.execute(query_all).fetchall() + # Verify we really have 4 rows in insert_table + self.assertEqual(len(rows), 4) + + def test_insert_from_select_with_specified_columns_negative(self): + insert_table_name = "__test_insert_to_table__" + select_table_name = "__test_select_from_table__" + uuidstrs = [] + for unused in range(10): + uuidstrs.append(uuid.uuid4().hex) + insert_table = Table( + insert_table_name, self.meta, + Column('id', Integer, primary_key=True, + nullable=False, autoincrement=True), + Column('uuid', String(36), nullable=False)) + select_table = Table( + select_table_name, self.meta, + Column('id', Integer, primary_key=True, + nullable=False, autoincrement=True), + Column('uuid', String(36), nullable=False)) + + insert_table.create() + select_table.create() + # Add 10 rows to select_table + for uuidstr in uuidstrs: + ins_stmt = select_table.insert().values(uuid=uuidstr) + self.conn.execute(ins_stmt) + + # Select 4 rows in one chunk from select_table + column = select_table.c.id + query_insert = select([select_table], + select_table.c.id < 5).order_by(column) + insert_statement = utils.InsertFromSelect(insert_table, + query_insert, ['uuid', 'id']) + self.assertRaises(exception.DBError, self.conn.execute, + insert_statement) + class PostgesqlTestMigrations(TestMigrationUtils, db_test_base.PostgreSQLOpportunisticTestCase): |