summaryrefslogtreecommitdiff
path: root/django/db
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2025-03-21 21:50:54 -0400
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2025-08-28 20:44:21 +0200
commit292b9e6fe8f23491680d9cc60f328562e2b1c823 (patch)
tree73aab051ae88301c5911c60a69bb8e290b0a4c27 /django/db
parentdc4ee9915228238bd24ce67645504f65eaf2f1fd (diff)
Refs #27222 -- Adapted RETURNING handling to be usable for UPDATE queries.
Renamed existing methods and abstractions used for INSERT … RETURNING to be generic enough to be used in the context of UPDATEs as well. This also consolidates SQL compliant implementations on BaseDatabaseOperations.
Diffstat (limited to 'django/db')
-rw-r--r--django/db/backends/base/operations.py22
-rw-r--r--django/db/backends/oracle/operations.py48
-rw-r--r--django/db/backends/oracle/utils.py2
-rw-r--r--django/db/models/sql/compiler.py20
4 files changed, 39 insertions, 53 deletions
diff --git a/django/db/backends/base/operations.py b/django/db/backends/base/operations.py
index f1b0c09abd..16a6296f9b 100644
--- a/django/db/backends/base/operations.py
+++ b/django/db/backends/base/operations.py
@@ -208,13 +208,6 @@ class BaseDatabaseOperations:
else:
return ["DISTINCT"], []
- def fetch_returned_insert_columns(self, cursor, returning_params):
- """
- Given a cursor object that has just performed an INSERT...RETURNING
- statement into a table, return the newly created data.
- """
- return cursor.fetchone()
-
def force_group_by(self):
"""
Return a GROUP BY clause to use with a HAVING clause when no grouping
@@ -358,11 +351,12 @@ class BaseDatabaseOperations:
"""
return value
- def return_insert_columns(self, fields):
+ def returning_columns(self, fields):
"""
- For backends that support returning columns as part of an insert query,
- return the SQL and params to append to the INSERT query. The returned
- fragment should contain a format string to hold the appropriate column.
+ For backends that support returning columns as part of an insert or
+ update query, return the SQL and params to append to the query.
+ The returned fragment should contain a format string to hold the
+ appropriate column.
"""
if not fields:
return "", ()
@@ -376,10 +370,10 @@ class BaseDatabaseOperations:
]
return "RETURNING %s" % ", ".join(columns), ()
- def fetch_returned_insert_rows(self, cursor):
+ def fetch_returned_rows(self, cursor, returning_params):
"""
- Given a cursor object that has just performed an INSERT...RETURNING
- statement into a table, return the tuple of returned data.
+ Given a cursor object for a DML query with a RETURNING statement,
+ return the selected returning rows of tuples.
"""
return cursor.fetchall()
diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py
index ce9ed7288d..bc152c4e6e 100644
--- a/django/db/backends/oracle/operations.py
+++ b/django/db/backends/oracle/operations.py
@@ -22,7 +22,7 @@ from django.utils.functional import cached_property
from django.utils.regex_helper import _lazy_re_compile
from .base import Database
-from .utils import BulkInsertMapper, InsertVar, Oracle_datetime
+from .utils import BoundVar, BulkInsertMapper, Oracle_datetime
class DatabaseOperations(BaseDatabaseOperations):
@@ -298,12 +298,27 @@ END;
def deferrable_sql(self):
return " DEFERRABLE INITIALLY DEFERRED"
- def fetch_returned_insert_columns(self, cursor, returning_params):
- columns = []
- for param in returning_params:
- value = param.get_value()
- columns.append(value[0])
- return tuple(columns)
+ def returning_columns(self, fields):
+ if not fields:
+ return "", ()
+ field_names = []
+ params = []
+ for field in fields:
+ field_names.append(
+ "%s.%s"
+ % (
+ self.quote_name(field.model._meta.db_table),
+ self.quote_name(field.column),
+ )
+ )
+ params.append(BoundVar(field))
+ return "RETURNING %s INTO %s" % (
+ ", ".join(field_names),
+ ", ".join(["%s"] * len(params)),
+ ), tuple(params)
+
+ def fetch_returned_rows(self, cursor, returning_params):
+ return list(zip(*(param.get_value() for param in returning_params)))
def no_limit_value(self):
return None
@@ -391,25 +406,6 @@ END;
match_option = "'i'"
return "REGEXP_LIKE(%%s, %%s, %s)" % match_option
- def return_insert_columns(self, fields):
- if not fields:
- return "", ()
- field_names = []
- params = []
- for field in fields:
- field_names.append(
- "%s.%s"
- % (
- self.quote_name(field.model._meta.db_table),
- self.quote_name(field.column),
- )
- )
- params.append(InsertVar(field))
- return "RETURNING %s INTO %s" % (
- ", ".join(field_names),
- ", ".join(["%s"] * len(params)),
- ), tuple(params)
-
def __foreign_key_constraints(self, table_name, recursive):
with self.connection.cursor() as cursor:
if recursive:
diff --git a/django/db/backends/oracle/utils.py b/django/db/backends/oracle/utils.py
index 57d97b3f77..fd7deab9a2 100644
--- a/django/db/backends/oracle/utils.py
+++ b/django/db/backends/oracle/utils.py
@@ -4,7 +4,7 @@ import decimal
from .base import Database
-class InsertVar:
+class BoundVar:
"""
A late-binding cursor variable that can be passed to Cursor.execute
as a parameter, in order to receive the id of the row created by an
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index f72ba907ad..73dfa5b87c 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -1890,7 +1890,7 @@ class SQLInsertCompiler(SQLCompiler):
result.append(on_conflict_suffix_sql)
# Skip empty r_sql to allow subclasses to customize behavior for
# 3rd party backends. Refs #19096.
- r_sql, self.returning_params = self.connection.ops.return_insert_columns(
+ r_sql, self.returning_params = self.connection.ops.returning_columns(
self.returning_fields
)
if r_sql:
@@ -1925,20 +1925,16 @@ class SQLInsertCompiler(SQLCompiler):
cursor.execute(sql, params)
if not self.returning_fields:
return []
+ obj_len = len(self.query.objs)
if (
self.connection.features.can_return_rows_from_bulk_insert
- and len(self.query.objs) > 1
+ and obj_len > 1
+ ) or (
+ self.connection.features.can_return_columns_from_insert and obj_len == 1
):
- rows = self.connection.ops.fetch_returned_insert_rows(cursor)
- cols = [field.get_col(opts.db_table) for field in self.returning_fields]
- elif self.connection.features.can_return_columns_from_insert:
- assert len(self.query.objs) == 1
- rows = [
- self.connection.ops.fetch_returned_insert_columns(
- cursor,
- self.returning_params,
- )
- ]
+ rows = self.connection.ops.fetch_returned_rows(
+ cursor, self.returning_params
+ )
cols = [field.get_col(opts.db_table) for field in self.returning_fields]
elif returning_fields and isinstance(
returning_field := returning_fields[0], AutoField