]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Return Row for CursorResult.inserted_primary_key
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 11 Apr 2021 21:15:55 +0000 (17:15 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 11 Apr 2021 21:15:55 +0000 (17:15 -0400)
The tuple returned by :attr:`.CursorResult.inserted_primary_key` is now a
:class:`_result.Row` object with a named tuple interface on top of the
existing tuple interface.

Fixes: #3314
Change-Id: I85677ef60d8329648f368bf497f634758f4e087b

doc/build/changelog/unreleased_14/3314.rst [new file with mode: 0644]
doc/build/tutorial/data.rst
lib/sqlalchemy/engine/cursor.py
lib/sqlalchemy/sql/compiler.py
test/sql/test_insert_exec.py

diff --git a/doc/build/changelog/unreleased_14/3314.rst b/doc/build/changelog/unreleased_14/3314.rst
new file mode 100644 (file)
index 0000000..80967c6
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: feature, sql
+    :tickets: 3314
+
+    The tuple returned by :attr:`.CursorResult.inserted_primary_key` is now a
+    :class:`_result.Row` object with a named tuple interface on top of the
+    existing tuple interface.
+
+
+
index f7c0c8e207d6bceafea76453f46a102c1f82b489..26016814b38b9e4d21f39fbf270e752d56098314 100644 (file)
@@ -131,6 +131,10 @@ first integer primary key value, which we can acquire using the
    to be populated regardless of whether or not "autoincrement" were used, hence
    to express a complete primary key it's a tuple.
 
+.. versionchanged:: 1.4.8 the tuple returned by
+   :attr:`_engine.CursorResult.inserted_primary_key` is now a named tuple
+   fullfilled by returning it as a :class:`_result.Row` object.
+
 .. _tutorial_core_insert_values_clause:
 
 INSERT usually generates the "values" clause automatically
index 6fcd18b4911965909a8028ccb3ae32d2a7ed90ef..ff9dd0d6affc79cef5fff7053c9615ceedfbdd65 100644 (file)
@@ -1413,22 +1413,28 @@ class BaseCursorResult(object):
     def inserted_primary_key(self):
         """Return the primary key for the row just inserted.
 
-        The return value is a list of scalar values
-        corresponding to the list of primary key columns
-        in the target table.
+        The return value is a :class:`_result.Row` object representing
+        a named tuple of primary key values in the order in which the
+        primary key columns are configured in the source
+        :class:`_schema.Table`.
 
-        This only applies to single row :func:`_expression.insert`
+        .. versionchanged:: 1.4.8 - the
+           :attr:`_engine.CursorResult.inserted_primary_key`
+           value is now a named tuple via the :class:`_result.Row` class,
+           rather than a plain tuple.
+
+        This accessor only applies to single row :func:`_expression.insert`
         constructs which did not explicitly specify
-        :meth:`_expression.Insert.returning`.
-
-        Note that primary key columns which specify a
-        server_default clause,
-        or otherwise do not qualify as "autoincrement"
-        columns (see the notes at :class:`_schema.Column`), and were
-        generated using the database-side default, will
-        appear in this list as ``None`` unless the backend
-        supports "returning" and the insert statement executed
-        with the "implicit returning" enabled.
+        :meth:`_expression.Insert.returning`.    Support for multirow inserts,
+        while not yet available for most backends, would be accessed using
+        the :attr:`_engine.CursorResult.inserted_primary_key_rows` accessor.
+
+        Note that primary key columns which specify a server_default clause, or
+        otherwise do not qualify as "autoincrement" columns (see the notes at
+        :class:`_schema.Column`), and were generated using the database-side
+        default, will appear in this list as ``None`` unless the backend
+        supports "returning" and the insert statement executed with the
+        "implicit returning" enabled.
 
         Raises :class:`~sqlalchemy.exc.InvalidRequestError` if the executed
         statement is not a compiled expression construct
index 9aa747056cacb35f55b3c1dea7a94262b97e4825..8eefa10d178806a9158beffc61ead614fef6d510 100644 (file)
@@ -1235,7 +1235,10 @@ class SQLCompiler(Compiled):
         )
 
     @util.memoized_property
+    @util.preload_module("sqlalchemy.engine.result")
     def _inserted_primary_key_from_lastrowid_getter(self):
+        result = util.preloaded.engine_result
+
         key_getter = self._key_getters_for_crud_column[2]
         table = self.statement.table
 
@@ -1253,14 +1256,16 @@ class SQLCompiler(Compiled):
         else:
             proc = None
 
+        row_fn = result.result_tuple([col.key for col in table.primary_key])
+
         def get(lastrowid, parameters):
             if proc is not None:
                 lastrowid = proc(lastrowid)
 
             if lastrowid is None:
-                return tuple(getter(parameters) for getter, col in getters)
+                return row_fn(getter(parameters) for getter, col in getters)
             else:
-                return tuple(
+                return row_fn(
                     lastrowid if col is autoinc_col else getter(parameters)
                     for getter, col in getters
                 )
@@ -1268,7 +1273,10 @@ class SQLCompiler(Compiled):
         return get
 
     @util.memoized_property
+    @util.preload_module("sqlalchemy.engine.result")
     def _inserted_primary_key_from_returning_getter(self):
+        result = util.preloaded.engine_result
+
         key_getter = self._key_getters_for_crud_column[2]
         table = self.statement.table
 
@@ -1281,8 +1289,10 @@ class SQLCompiler(Compiled):
             for col in table.primary_key
         ]
 
+        row_fn = result.result_tuple([col.key for col in table.primary_key])
+
         def get(row, parameters):
-            return tuple(
+            return row_fn(
                 getter(row) if use_row else getter(parameters)
                 for getter, use_row in getters
             )
index ba763772c7291f6d5309d1811af471b39fbfb7ee..76b4ba01ea89fc5c49b7ad6ed6ef0a8d6c7518ba 100644 (file)
@@ -112,9 +112,8 @@ class InsertExecTest(fixtures.TablesTest):
                 result = connection.execute(table_.insert(), values)
                 ret = values.copy()
 
-                for col, id_ in zip(
-                    table_.primary_key, result.inserted_primary_key
-                ):
+                ipk = result.inserted_primary_key
+                for col, id_ in zip(table_.primary_key, ipk):
                     ret[col.key] = id_
 
                 if result.lastrow_has_defaults():
@@ -131,7 +130,7 @@ class InsertExecTest(fixtures.TablesTest):
                     ).first()
                     for c in table_.c:
                         ret[c.key] = row._mapping[c]
-            return ret
+            return ret, ipk
 
         if testing.against("firebird", "postgresql", "oracle", "mssql"):
             assert testing.db.dialect.implicit_returning
@@ -147,8 +146,18 @@ class InsertExecTest(fixtures.TablesTest):
         for engine in test_engines:
             try:
                 table_.create(bind=engine, checkfirst=True)
-                i = insert_values(engine, table_, values)
+                i, ipk = insert_values(engine, table_, values)
                 eq_(i, assertvalues)
+
+                # named tuple tests
+                for col in table_.primary_key:
+                    eq_(getattr(ipk, col.key), assertvalues[col.key])
+                    eq_(ipk._mapping[col.key], assertvalues[col.key])
+
+                eq_(
+                    ipk._fields, tuple([col.key for col in table_.primary_key])
+                )
+
             finally:
                 table_.drop(bind=engine)