]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
- Fixed bug where :meth:`.Operations.bulk_insert` would not function
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 8 Mar 2014 05:15:46 +0000 (00:15 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 8 Mar 2014 05:15:46 +0000 (00:15 -0500)
properly when :meth:`.Operations.inline_literal` values were used,
either in --sql or non-sql mode.    The values will now render
directly in --sql mode.  For compatibility with "online" mode,
a new flag :paramref:`~.Operations.inline_literal.multiparams`
can be set to False which will cause each parameter set to be
compiled and executed with individual INSERT statements.
fixes #179

alembic/ddl/impl.py
alembic/ddl/mssql.py
alembic/operations.py
docs/build/changelog.rst
tests/test_bulk_insert.py

index 4b85a00144fb281e00ebff8f679549cfeed9a513..79cbd36e7d29aa0dc63b609ed3c595f7fb9466a3 100644 (file)
@@ -163,7 +163,7 @@ class DefaultImpl(with_metaclass(ImplMeta)):
     def drop_index(self, index):
         self._exec(schema.DropIndex(index))
 
-    def bulk_insert(self, table, rows):
+    def bulk_insert(self, table, rows, multiinsert=True):
         if not isinstance(rows, list):
             raise TypeError("List expected")
         elif rows and not isinstance(rows[0], dict):
@@ -171,7 +171,9 @@ class DefaultImpl(with_metaclass(ImplMeta)):
         if self.as_sql:
             for row in rows:
                 self._exec(table.insert(inline=True).values(**dict(
-                    (k, _literal_bindparam(k, v, type_=table.c[k].type))
+                    (k,
+                        _literal_bindparam(k, v, type_=table.c[k].type)
+                        if not isinstance(v, _literal_bindparam) else v)
                     for k, v in row.items()
                 )))
         else:
@@ -179,7 +181,11 @@ class DefaultImpl(with_metaclass(ImplMeta)):
             if not hasattr(table, '_autoincrement_column'):
                 table._autoincrement_column = None
             if rows:
-                self._exec(table.insert(inline=True), multiparams=rows)
+                if multiinsert:
+                    self._exec(table.insert(inline=True), multiparams=rows)
+                else:
+                    for row in rows:
+                        self._exec(table.insert(inline=True).values(**row))
 
     def compare_type(self, inspector_column, metadata_column):
 
index 095dbf347158cf4f95e39d0bd2fa4e4a2d60a202..58b57cf18a1d39c1110d449bc609b13185239587 100644 (file)
@@ -86,19 +86,19 @@ class MSSQLImpl(DefaultImpl):
                                 schema=schema,
                                 name=name)
 
-    def bulk_insert(self, table, rows):
+    def bulk_insert(self, table, rows, **kw):
         if self.as_sql:
             self._exec(
                 "SET IDENTITY_INSERT %s ON" %
                     self.dialect.identifier_preparer.format_table(table)
             )
-            super(MSSQLImpl, self).bulk_insert(table, rows)
+            super(MSSQLImpl, self).bulk_insert(table, rows, **kw)
             self._exec(
                 "SET IDENTITY_INSERT %s OFF" %
                     self.dialect.identifier_preparer.format_table(table)
             )
         else:
-            super(MSSQLImpl, self).bulk_insert(table, rows)
+            super(MSSQLImpl, self).bulk_insert(table, rows, **kw)
 
 
     def drop_column(self, table_name, column, **kw):
index dfb942b4a6141c9106793550f509e8bac8976933..c11d381802eaf8310bb14e36c9aecfdb357ca2be 100644 (file)
@@ -774,7 +774,7 @@ class Operations(object):
         t.append_constraint(const)
         self.impl.drop_constraint(const)
 
-    def bulk_insert(self, table, rows):
+    def bulk_insert(self, table, rows, multiinsert=True):
         """Issue a "bulk insert" operation using the current
         migration context.
 
@@ -808,8 +808,55 @@ class Operations(object):
                             'create_date':date(2008, 8, 15)},
                 ]
             )
+
+        When using --sql mode, some datatypes may not render inline automatically,
+        such as dates and other special types.   When this issue is present,
+        :meth:`.Operations.inline_literal` may be used::
+
+            op.bulk_insert(accounts_table,
+                [
+                    {'id':1, 'name':'John Smith',
+                            'create_date':op.inline_literal("2010-10-05")},
+                    {'id':2, 'name':'Ed Williams',
+                            'create_date':op.inline_literal("2007-05-27")},
+                    {'id':3, 'name':'Wendy Jones',
+                            'create_date':op.inline_literal("2008-08-15")},
+                ],
+                multiparams=False
+            )
+
+        When using :meth:`.Operations.inline_literal` in conjunction with
+        :meth:`.Operations.bulk_insert`, in order for the statement to work
+        in "online" (e.g. non --sql) mode, the
+        :paramref:`~.Operations.inline_literal.multiparams`
+        flag should be set to ``False``, which will have the effect of
+        individual INSERT statements being emitted to the database, each
+        with a distinct VALUES clause, so that the "inline" values can
+        still be rendered, rather than attempting to pass the values
+        as bound parameters.
+
+        .. versionadded:: 0.6.4 :meth:`.Operations.inline_literal` can now
+           be used with :meth:`.Operations.bulk_insert`, and the
+           :paramref:`~.Operations.inline_literal.multiparams` flag has
+           been added to assist in this usage when running in "online"
+           mode.
+
+        :param table: a table object which represents the target of the INSERT.
+
+        :param rows: a list of dictionaries indicating rows.
+
+        :param multiinsert: when at its default of True and --sql mode is not
+           enabled, the INSERT statement will be executed using
+           "executemany()" style, where all elements in the list of dictionaries
+           are passed as bound parameters in a single list.   Setting this
+           to False results in individual INSERT statements being emitted
+           per parameter set, and is needed in those cases where non-literal
+           values are present in the parameter sets.
+
+           .. versionadded:: 0.6.4
+
           """
-        self.impl.bulk_insert(table, rows)
+        self.impl.bulk_insert(table, rows, multiinsert=multiinsert)
 
     def inline_literal(self, value, type_=None):
         """Produce an 'inline literal' expression, suitable for
index bf58c6fa5954fc789a3f3db63c2681aecf984e79..69b64f992137c79af1979330c6d805ac59cb9e5c 100644 (file)
@@ -5,6 +5,18 @@ Changelog
 .. changelog::
     :version: 0.6.4
 
+    .. change::
+      :tags: bug
+      :tickets: 179
+
+      Fixed bug where :meth:`.Operations.bulk_insert` would not function
+      properly when :meth:`.Operations.inline_literal` values were used,
+      either in --sql or non-sql mode.    The values will now render
+      directly in --sql mode.  For compatibility with "online" mode,
+      a new flag :paramref:`~.Operations.inline_literal.multiparams`
+      can be set to False which will cause each parameter set to be
+      compiled and executed with individual INSERT statements.
+
     .. change::
       :tags: bug, py3k
       :tickets: 175
index 8133a2969fb99f6926af60798a25564189731b59..cc567319988ff613817d1008e9fc564c5cd38517 100644 (file)
@@ -4,6 +4,7 @@ from alembic import op
 from sqlalchemy import Integer, String
 from sqlalchemy.sql import table, column
 from sqlalchemy import Table, Column, MetaData
+from sqlalchemy.types import TypeEngine
 
 from . import op_fixture, eq_, assert_raises_message
 
@@ -108,6 +109,24 @@ def test_bulk_insert_mssql():
         'INSERT INTO ins_table (id, v1, v2) VALUES (:id, :v1, :v2)'
     )
 
+def test_bulk_insert_inline_literal_as_sql():
+    context = op_fixture('postgresql', True)
+
+    class MyType(TypeEngine):
+        pass
+
+    t1 = table('t', column('id', Integer), column('data', MyType()))
+
+    op.bulk_insert(t1, [
+        {'id': 1, 'data': op.inline_literal('d1')},
+        {'id': 2, 'data': op.inline_literal('d2')},
+    ])
+    context.assert_(
+        "INSERT INTO t (id, data) VALUES (1, 'd1')",
+        "INSERT INTO t (id, data) VALUES (2, 'd2')"
+    )
+
+
 def test_bulk_insert_as_sql():
     context = _test_bulk_insert('default', True)
     context.assert_(
@@ -204,3 +223,22 @@ class RoundTripTest(TestCase):
             ]
         )
 
+    def test_bulk_insert_inline_literal(self):
+        class MyType(TypeEngine):
+            pass
+
+        t1 = table('foo', column('id', Integer), column('data', MyType()))
+
+        self.op.bulk_insert(t1, [
+            {'id': 1, 'data': self.op.inline_literal('d1')},
+            {'id': 2, 'data': self.op.inline_literal('d2')},
+        ], multiinsert=False)
+
+        eq_(
+            self.conn.execute("select id, data from foo").fetchall(),
+            [
+                (1, "d1"),
+                (2, "d2"),
+            ]
+        )
+