From: Mike Bayer Date: Sun, 11 Apr 2021 21:15:55 +0000 (-0400) Subject: Return Row for CursorResult.inserted_primary_key X-Git-Tag: rel_1_4_8~16 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=41de9a86c022250effbdd5aed4253df34628e103;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Return Row for CursorResult.inserted_primary_key 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 --- diff --git a/doc/build/changelog/unreleased_14/3314.rst b/doc/build/changelog/unreleased_14/3314.rst new file mode 100644 index 0000000000..80967c6644 --- /dev/null +++ b/doc/build/changelog/unreleased_14/3314.rst @@ -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. + + + diff --git a/doc/build/tutorial/data.rst b/doc/build/tutorial/data.rst index f7c0c8e207..26016814b3 100644 --- a/doc/build/tutorial/data.rst +++ b/doc/build/tutorial/data.rst @@ -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 diff --git a/lib/sqlalchemy/engine/cursor.py b/lib/sqlalchemy/engine/cursor.py index 6fcd18b491..ff9dd0d6af 100644 --- a/lib/sqlalchemy/engine/cursor.py +++ b/lib/sqlalchemy/engine/cursor.py @@ -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 diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 9aa747056c..8eefa10d17 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -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 ) diff --git a/test/sql/test_insert_exec.py b/test/sql/test_insert_exec.py index ba763772c7..76b4ba01ea 100644 --- a/test/sql/test_insert_exec.py +++ b/test/sql/test_insert_exec.py @@ -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)