Mike Bayer avatar Mike Bayer committed 5f76f29

- Fixed bug with :meth:`.Insert.from_select` method where the order
of the given names would not be taken into account when generating
the INSERT statement, thus producing a mismatch versus the column
names in the given SELECT statement. Also noted that
:meth:`.Insert.from_select` implies that Python-side insert defaults
cannot be used, since the statement has no VALUES clause. [ticket:2895]

Comments (0)

Files changed (4)

doc/build/changelog/changelog_08.rst

     :version: 0.8.5
 
     .. change::
+        :tags: bug, sql
+        :versions: 0.9.0b2
+        :tickets: 2895
+
+        Fixed bug with :meth:`.Insert.from_select` method where the order
+        of the given names would not be taken into account when generating
+        the INSERT statement, thus producing a mismatch versus the column
+        names in the given SELECT statement.  Also noted that
+        :meth:`.Insert.from_select` implies that Python-side insert defaults
+        cannot be used, since the statement has no VALUES clause.
+
+    .. change::
         :tags: enhancement, sql
         :versions: 0.9.0b2
 

lib/sqlalchemy/sql/compiler.py

                     elif c.server_onupdate is not None:
                         self.postfetch.append(c)
 
-        # iterating through columns at the top to maintain ordering.
-        # otherwise we might iterate through individual sets of
-        # "defaults", "primary key cols", etc.
-        for c in stmt.table.columns:
+        if self.isinsert and stmt.select_names:
+            # for an insert from select, we can only use names that
+            # are given, so only select for those names.
+            cols = (stmt.table.c[elements._column_as_key(name)]
+                        for name in stmt.select_names)
+        else:
+            # iterate through all table columns to maintain
+            # ordering, even for those cols that aren't included
+            cols = stmt.table.columns
+
+        for c in cols:
             if c.key in parameters and c.key not in check_columns:
                 value = parameters.pop(c.key)
                 if elements._is_literal(value):

lib/sqlalchemy/sql/dml.py

         """
         ValuesBase.__init__(self, table, values, prefixes)
         self._bind = bind
-        self.select = None
+        self.select = self.select_names = None
         self.inline = inline
         self._returning = returning
         self.kwargs = kwargs
              sel = select([table1.c.a, table1.c.b]).where(table1.c.c > 5)
              ins = table2.insert(inline=True).from_select(['a', 'b'], sel)
 
+        .. note::
+
+           A SELECT..INSERT construct in SQL has no VALUES clause.  Therefore
+           :class:`.Column` objects which utilize Python-side defaults
+           (e.g. as described at :ref:`metadata_defaults_toplevel`)
+           will **not** take effect when using :meth:`.Insert.from_select`.
+
         .. versionadded:: 0.8.3
 
         """
         self.parameters, self._has_multi_parameters = \
                 self._process_colparams(dict((n, Null()) for n in names))
 
+        self.select_names = names
         self.select = _interpret_as_select(select)
 
     def _copy_internals(self, clone=_clone, **kw):

test/sql/test_insert.py

             checkparams={"name_1": "foo"}
         )
 
+    def test_insert_from_select_select_alt_ordering(self):
+        table1 = self.tables.mytable
+        sel = select([table1.c.name, table1.c.myid]).where(table1.c.name == 'foo')
+        ins = self.tables.myothertable.insert().\
+                    from_select(("othername", "otherid"), sel)
+        self.assert_compile(
+            ins,
+            "INSERT INTO myothertable (othername, otherid) "
+            "SELECT mytable.name, mytable.myid FROM mytable "
+            "WHERE mytable.name = :name_1",
+            checkparams={"name_1": "foo"}
+        )
+
+    def test_insert_from_select_select_no_defaults(self):
+        metadata = MetaData()
+        table = Table('sometable', metadata,
+            Column('id', Integer, primary_key=True),
+            Column('foo', Integer, default=func.foobar()))
+        table1 = self.tables.mytable
+        sel = select([table1.c.myid]).where(table1.c.name == 'foo')
+        ins = table.insert().\
+                    from_select(["id"], sel)
+        self.assert_compile(
+            ins,
+            "INSERT INTO sometable (id) SELECT mytable.myid "
+            "FROM mytable WHERE mytable.name = :name_1",
+            checkparams={"name_1": "foo"}
+        )
+
     def test_insert_mix_select_values_exception(self):
         table1 = self.tables.mytable
         sel = select([table1.c.myid, table1.c.name]).where(table1.c.name == 'foo')
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.