Skip to content

Commit 292b9e6

Browse files
charettesfelixxm
authored andcommitted
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.
1 parent dc4ee99 commit 292b9e6

5 files changed

Lines changed: 51 additions & 53 deletions

File tree

django/db/backends/base/operations.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,6 @@ def distinct_sql(self, fields, params):
208208
else:
209209
return ["DISTINCT"], []
210210

211-
def fetch_returned_insert_columns(self, cursor, returning_params):
212-
"""
213-
Given a cursor object that has just performed an INSERT...RETURNING
214-
statement into a table, return the newly created data.
215-
"""
216-
return cursor.fetchone()
217-
218211
def force_group_by(self):
219212
"""
220213
Return a GROUP BY clause to use with a HAVING clause when no grouping
@@ -358,11 +351,12 @@ def process_clob(self, value):
358351
"""
359352
return value
360353

361-
def return_insert_columns(self, fields):
354+
def returning_columns(self, fields):
362355
"""
363-
For backends that support returning columns as part of an insert query,
364-
return the SQL and params to append to the INSERT query. The returned
365-
fragment should contain a format string to hold the appropriate column.
356+
For backends that support returning columns as part of an insert or
357+
update query, return the SQL and params to append to the query.
358+
The returned fragment should contain a format string to hold the
359+
appropriate column.
366360
"""
367361
if not fields:
368362
return "", ()
@@ -376,10 +370,10 @@ def return_insert_columns(self, fields):
376370
]
377371
return "RETURNING %s" % ", ".join(columns), ()
378372

379-
def fetch_returned_insert_rows(self, cursor):
373+
def fetch_returned_rows(self, cursor, returning_params):
380374
"""
381-
Given a cursor object that has just performed an INSERT...RETURNING
382-
statement into a table, return the tuple of returned data.
375+
Given a cursor object for a DML query with a RETURNING statement,
376+
return the selected returning rows of tuples.
383377
"""
384378
return cursor.fetchall()
385379

django/db/backends/oracle/operations.py

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from django.utils.regex_helper import _lazy_re_compile
2323

2424
from .base import Database
25-
from .utils import BulkInsertMapper, InsertVar, Oracle_datetime
25+
from .utils import BoundVar, BulkInsertMapper, Oracle_datetime
2626

2727

2828
class DatabaseOperations(BaseDatabaseOperations):
@@ -298,12 +298,27 @@ def convert_empty_bytes(value, expression, connection):
298298
def deferrable_sql(self):
299299
return " DEFERRABLE INITIALLY DEFERRED"
300300

301-
def fetch_returned_insert_columns(self, cursor, returning_params):
302-
columns = []
303-
for param in returning_params:
304-
value = param.get_value()
305-
columns.append(value[0])
306-
return tuple(columns)
301+
def returning_columns(self, fields):
302+
if not fields:
303+
return "", ()
304+
field_names = []
305+
params = []
306+
for field in fields:
307+
field_names.append(
308+
"%s.%s"
309+
% (
310+
self.quote_name(field.model._meta.db_table),
311+
self.quote_name(field.column),
312+
)
313+
)
314+
params.append(BoundVar(field))
315+
return "RETURNING %s INTO %s" % (
316+
", ".join(field_names),
317+
", ".join(["%s"] * len(params)),
318+
), tuple(params)
319+
320+
def fetch_returned_rows(self, cursor, returning_params):
321+
return list(zip(*(param.get_value() for param in returning_params)))
307322

308323
def no_limit_value(self):
309324
return None
@@ -391,25 +406,6 @@ def regex_lookup(self, lookup_type):
391406
match_option = "'i'"
392407
return "REGEXP_LIKE(%%s, %%s, %s)" % match_option
393408

394-
def return_insert_columns(self, fields):
395-
if not fields:
396-
return "", ()
397-
field_names = []
398-
params = []
399-
for field in fields:
400-
field_names.append(
401-
"%s.%s"
402-
% (
403-
self.quote_name(field.model._meta.db_table),
404-
self.quote_name(field.column),
405-
)
406-
)
407-
params.append(InsertVar(field))
408-
return "RETURNING %s INTO %s" % (
409-
", ".join(field_names),
410-
", ".join(["%s"] * len(params)),
411-
), tuple(params)
412-
413409
def __foreign_key_constraints(self, table_name, recursive):
414410
with self.connection.cursor() as cursor:
415411
if recursive:

django/db/backends/oracle/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from .base import Database
55

66

7-
class InsertVar:
7+
class BoundVar:
88
"""
99
A late-binding cursor variable that can be passed to Cursor.execute
1010
as a parameter, in order to receive the id of the row created by an

django/db/models/sql/compiler.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1890,7 +1890,7 @@ def as_sql(self):
18901890
result.append(on_conflict_suffix_sql)
18911891
# Skip empty r_sql to allow subclasses to customize behavior for
18921892
# 3rd party backends. Refs #19096.
1893-
r_sql, self.returning_params = self.connection.ops.return_insert_columns(
1893+
r_sql, self.returning_params = self.connection.ops.returning_columns(
18941894
self.returning_fields
18951895
)
18961896
if r_sql:
@@ -1925,20 +1925,16 @@ def execute_sql(self, returning_fields=None):
19251925
cursor.execute(sql, params)
19261926
if not self.returning_fields:
19271927
return []
1928+
obj_len = len(self.query.objs)
19281929
if (
19291930
self.connection.features.can_return_rows_from_bulk_insert
1930-
and len(self.query.objs) > 1
1931+
and obj_len > 1
1932+
) or (
1933+
self.connection.features.can_return_columns_from_insert and obj_len == 1
19311934
):
1932-
rows = self.connection.ops.fetch_returned_insert_rows(cursor)
1933-
cols = [field.get_col(opts.db_table) for field in self.returning_fields]
1934-
elif self.connection.features.can_return_columns_from_insert:
1935-
assert len(self.query.objs) == 1
1936-
rows = [
1937-
self.connection.ops.fetch_returned_insert_columns(
1938-
cursor,
1939-
self.returning_params,
1940-
)
1941-
]
1935+
rows = self.connection.ops.fetch_returned_rows(
1936+
cursor, self.returning_params
1937+
)
19421938
cols = [field.get_col(opts.db_table) for field in self.returning_fields]
19431939
elif returning_fields and isinstance(
19441940
returning_field := returning_fields[0], AutoField

docs/releases/6.0.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,18 @@ backends.
402402
* :class:`~django.db.backends.base.schema.BaseDatabaseSchemaEditor` and
403403
PostgreSQL backends no longer use ``CASCADE`` when dropping a column.
404404

405+
* ``DatabaseOperations.return_insert_columns()`` and
406+
``DatabaseOperations.fetch_returned_insert_rows()`` methods are renamed to
407+
``returning_columns()`` and ``fetch_returned_rows()``, respectively, to
408+
denote they can be used in the context ``UPDATE … RETURNING`` statements as
409+
well as ``INSERT … RETURNING``.
410+
411+
* The ``DatabaseOperations.fetch_returned_insert_columns()`` method is removed
412+
and the ``fetch_returned_rows()`` method replacing
413+
``fetch_returned_insert_rows()`` expects both a ``cursor`` and
414+
``returning_params`` to be provided just like
415+
``fetch_returned_insert_columns()`` did.
416+
405417
Dropped support for MariaDB 10.5
406418
--------------------------------
407419

0 commit comments

Comments
 (0)