From: Mike Bayer Date: Thu, 19 Dec 2013 21:02:14 +0000 (-0500) Subject: - Fixed bug with :meth:`.Insert.from_select` method where the order X-Git-Tag: rel_0_8_5~70 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2042493228a92fede38f39cd144d6ea74abbcb21;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - 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] --- diff --git a/doc/build/changelog/changelog_08.rst b/doc/build/changelog/changelog_08.rst index 4141585e07..5b6a99f3eb 100644 --- a/doc/build/changelog/changelog_08.rst +++ b/doc/build/changelog/changelog_08.rst @@ -11,6 +11,18 @@ .. changelog:: :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 diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index ad92396eef..608023f517 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -1671,10 +1671,17 @@ class SQLCompiler(engine.Compiled): 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[sql._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 sql._is_literal(value): diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index aa655f34fe..21c0c73afd 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -6357,7 +6357,7 @@ class Insert(ValuesBase): **kwargs): 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 @@ -6400,6 +6400,13 @@ class Insert(ValuesBase): 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 """ @@ -6410,6 +6417,7 @@ class Insert(ValuesBase): 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): diff --git a/test/sql/test_insert.py b/test/sql/test_insert.py index e1171532d9..5c3b9b6c98 100644 --- a/test/sql/test_insert.py +++ b/test/sql/test_insert.py @@ -133,6 +133,35 @@ class InsertTest(_InsertTestBase, fixtures.TablesTest, AssertsCompiledSQL): 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')