.. changelog::
:version: 1.0.0
+ .. change::
+ :tags: bug, sql
+ :tickets: 3169
+
+ The INSERT...FROM SELECT construct now implies ``inline=True``
+ on :class:`.Insert`. This helps to fix a bug where an
+ INSERT...FROM SELECT construct would inadvertently be compiled
+ as "implicit returning" on supporting backends, which would
+ cause breakage in the case of an INSERT that inserts zero rows
+ (as implicit returning expects a row), as well as arbitrary
+ return data in the case of an INSERT that inserts multiple
+ rows (e.g. only the first row of many).
+ A similar change is also applied to an INSERT..VALUES
+ with multiple parameter sets; implicit RETURNING will no longer emit
+ for this statement either. As both of these constructs deal
+ with varible numbers of rows, the
+ :attr:`.ResultProxy.inserted_primary_key` accessor does not
+ apply. Previously, there was a documentation note that one
+ may prefer ``inline=True`` with INSERT..FROM SELECT as some databases
+ don't support returning and therefore can't do "implicit" returning,
+ but there's no reason an INSERT...FROM SELECT needs implicit returning
+ in any case. Regular explicit :meth:`.Insert.returning` should
+ be used to return variable numbers of result rows if inserted
+ data is needed.
+
.. change::
:tags: bug, orm
:tickets: 3167
.. versionadded:: 0.8
Support for multiple-VALUES INSERT statements.
+ .. versionchanged:: 1.0.0 an INSERT that uses a multiple-VALUES
+ clause, even a list of length one,
+ implies that the :paramref:`.Insert.inline` flag is set to
+ True, indicating that the statement will not attempt to fetch
+ the "last inserted primary key" or other defaults. The statement
+ deals with an arbitrary number of rows, so the
+ :attr:`.ResultProxy.inserted_primary_key` accessor does not apply.
.. seealso::
dynamically render the VALUES clause at execution time based on
the parameters passed to :meth:`.Connection.execute`.
- :param inline: if True, SQL defaults will be compiled 'inline' into
- the statement and not pre-executed.
+ :param inline: if True, no attempt will be made to retrieve the
+ SQL-generated default values to be provided within the statement;
+ in particular,
+ this allows SQL expressions to be rendered 'inline' within the
+ statement without the need to pre-execute them beforehand; for
+ backends that support "returning", this turns off the "implicit
+ returning" feature for the statement.
If both `values` and compile-time bind parameters are present, the
compile-time bind parameters override the information specified
would normally raise an exception if these column lists don't
correspond.
- .. note::
-
- Depending on backend, it may be necessary for the :class:`.Insert`
- statement to be constructed using the ``inline=True`` flag; this
- flag will prevent the implicit usage of ``RETURNING`` when the
- ``INSERT`` statement is rendered, which isn't supported on a
- backend such as Oracle in conjunction with an ``INSERT..SELECT``
- combination::
-
- sel = select([table1.c.a, table1.c.b]).where(table1.c.c > 5)
- ins = table2.insert(inline=True).from_select(['a', 'b'], sel)
+ .. versionchanged:: 1.0.0 an INSERT that uses FROM SELECT
+ implies that the :paramref:`.Insert.inline` flag is set to
+ True, indicating that the statement will not attempt to fetch
+ the "last inserted primary key" or other defaults. The statement
+ deals with an arbitrary number of rows, so the
+ :attr:`.ResultProxy.inserted_primary_key` accessor does not apply.
.. note::
self._process_colparams(dict((n, Null()) for n in names))
self.select_names = names
+ self.inline = True
self.select = _interpret_as_select(select)
def _copy_internals(self, clone=_clone, **kw):
Column('name', String(30)),
Column('description', String(30)))
Table('myothertable', metadata,
- Column('otherid', Integer),
+ Column('otherid', Integer, primary_key=True),
Column('othername', String(30)))
dialect=default.DefaultDialect()
)
+ def test_insert_from_select_returning(self):
+ table1 = self.tables.mytable
+ sel = select([table1.c.myid, table1.c.name]).where(
+ table1.c.name == 'foo')
+ ins = self.tables.myothertable.insert().\
+ from_select(("otherid", "othername"), sel).returning(
+ self.tables.myothertable.c.otherid
+ )
+ self.assert_compile(
+ ins,
+ "INSERT INTO myothertable (otherid, othername) "
+ "SELECT mytable.myid, mytable.name FROM mytable "
+ "WHERE mytable.name = %(name_1)s RETURNING myothertable.otherid",
+ checkparams={"name_1": "foo"},
+ dialect="postgresql"
+ )
+
def test_insert_from_select_select(self):
table1 = self.tables.mytable
sel = select([table1.c.myid, table1.c.name]).where(
)
ins = mytable.insert().\
from_select(
- [mytable.c.name, mytable.c.description], sel)
+ [mytable.c.name, mytable.c.description], sel)
self.assert_compile(
ins,
"INSERT INTO mytable (name, description) "
)
+class InsertImplicitReturningTest(
+ _InsertTestBase, fixtures.TablesTest, AssertsCompiledSQL):
+ __dialect__ = postgresql.dialect(implicit_returning=True)
+
+ def test_insert_select(self):
+ table1 = self.tables.mytable
+ sel = select([table1.c.myid, table1.c.name]).where(
+ table1.c.name == 'foo')
+ ins = self.tables.myothertable.insert().\
+ from_select(("otherid", "othername"), sel)
+ self.assert_compile(
+ ins,
+ "INSERT INTO myothertable (otherid, othername) "
+ "SELECT mytable.myid, mytable.name FROM mytable "
+ "WHERE mytable.name = %(name_1)s",
+ checkparams={"name_1": "foo"}
+ )
+
+ def test_insert_select_return_defaults(self):
+ table1 = self.tables.mytable
+ sel = select([table1.c.myid, table1.c.name]).where(
+ table1.c.name == 'foo')
+ ins = self.tables.myothertable.insert().\
+ from_select(("otherid", "othername"), sel).\
+ return_defaults(self.tables.myothertable.c.otherid)
+ self.assert_compile(
+ ins,
+ "INSERT INTO myothertable (otherid, othername) "
+ "SELECT mytable.myid, mytable.name FROM mytable "
+ "WHERE mytable.name = %(name_1)s",
+ checkparams={"name_1": "foo"}
+ )
+
+ def test_insert_multiple_values(self):
+ ins = self.tables.myothertable.insert().values([
+ {"othername": "foo"},
+ {"othername": "bar"},
+ ])
+ self.assert_compile(
+ ins,
+ "INSERT INTO myothertable (othername) "
+ "VALUES (%(othername_0)s), "
+ "(%(othername_1)s)",
+ checkparams={
+ 'othername_1': 'bar',
+ 'othername_0': 'foo'}
+ )
+
+ def test_insert_multiple_values_return_defaults(self):
+ # TODO: not sure if this should raise an
+ # error or what
+ ins = self.tables.myothertable.insert().values([
+ {"othername": "foo"},
+ {"othername": "bar"},
+ ]).return_defaults(self.tables.myothertable.c.otherid)
+ self.assert_compile(
+ ins,
+ "INSERT INTO myothertable (othername) "
+ "VALUES (%(othername_0)s), "
+ "(%(othername_1)s)",
+ checkparams={
+ 'othername_1': 'bar',
+ 'othername_0': 'foo'}
+ )
+
+ def test_insert_single_list_values(self):
+ ins = self.tables.myothertable.insert().values([
+ {"othername": "foo"},
+ ])
+ self.assert_compile(
+ ins,
+ "INSERT INTO myothertable (othername) "
+ "VALUES (%(othername_0)s)",
+ checkparams={'othername_0': 'foo'}
+ )
+
+ def test_insert_single_element_values(self):
+ ins = self.tables.myothertable.insert().values(
+ {"othername": "foo"},
+ )
+ self.assert_compile(
+ ins,
+ "INSERT INTO myothertable (othername) "
+ "VALUES (%(othername)s) RETURNING myothertable.otherid",
+ checkparams={'othername': 'foo'}
+ )
+
+
class EmptyTest(_InsertTestBase, fixtures.TablesTest, AssertsCompiledSQL):
__dialect__ = 'default'