1
0
mirror of https://github.com/django/django.git synced 2025-06-05 03:29:12 +00:00

Fixed #35918 -- Added support for execute_sql to directly return row counts.

This commit is contained in:
Raphael Gaschignard 2024-11-19 14:50:24 +10:00 committed by Sarah Boyce
parent d97cacc2ae
commit ddefc3fed1
4 changed files with 38 additions and 34 deletions

View File

@ -26,7 +26,7 @@ from django.db.models.deletion import Collector
from django.db.models.expressions import Case, F, Value, When from django.db.models.expressions import Case, F, Value, When
from django.db.models.functions import Cast, Trunc from django.db.models.functions import Cast, Trunc
from django.db.models.query_utils import FilteredRelation, Q from django.db.models.query_utils import FilteredRelation, Q
from django.db.models.sql.constants import CURSOR, GET_ITERATOR_CHUNK_SIZE from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE, ROW_COUNT
from django.db.models.utils import ( from django.db.models.utils import (
AltersData, AltersData,
create_namedtuple_class, create_namedtuple_class,
@ -1209,11 +1209,7 @@ class QuerySet(AltersData):
""" """
query = self.query.clone() query = self.query.clone()
query.__class__ = sql.DeleteQuery query.__class__ = sql.DeleteQuery
cursor = query.get_compiler(using).execute_sql(CURSOR) return query.get_compiler(using).execute_sql(ROW_COUNT)
if cursor:
with cursor:
return cursor.rowcount
return 0
_raw_delete.alters_data = True _raw_delete.alters_data = True
@ -1252,7 +1248,7 @@ class QuerySet(AltersData):
# Clear any annotations so that they won't be present in subqueries. # Clear any annotations so that they won't be present in subqueries.
query.annotations = {} query.annotations = {}
with transaction.mark_for_rollback_on_error(using=self.db): with transaction.mark_for_rollback_on_error(using=self.db):
rows = query.get_compiler(self.db).execute_sql(CURSOR) rows = query.get_compiler(self.db).execute_sql(ROW_COUNT)
self._result_cache = None self._result_cache = None
return rows return rows
@ -1277,7 +1273,7 @@ class QuerySet(AltersData):
# Clear any annotations so that they won't be present in subqueries. # Clear any annotations so that they won't be present in subqueries.
query.annotations = {} query.annotations = {}
self._result_cache = None self._result_cache = None
return query.get_compiler(self.db).execute_sql(CURSOR) return query.get_compiler(self.db).execute_sql(ROW_COUNT)
_update.alters_data = True _update.alters_data = True
_update.queryset_only = False _update.queryset_only = False

View File

@ -19,6 +19,7 @@ from django.db.models.sql.constants import (
MULTI, MULTI,
NO_RESULTS, NO_RESULTS,
ORDER_DIR, ORDER_DIR,
ROW_COUNT,
SINGLE, SINGLE,
) )
from django.db.models.sql.query import Query, get_order_dir from django.db.models.sql.query import Query, get_order_dir
@ -1596,15 +1597,15 @@ class SQLCompiler:
): ):
""" """
Run the query against the database and return the result(s). The Run the query against the database and return the result(s). The
return value is a single data item if result_type is SINGLE, or an return value depends on the value of result_type.
iterator over the results if the result_type is MULTI.
result_type is either MULTI (use fetchmany() to retrieve all rows), When result_type is:
SINGLE (only retrieve a single row), or None. In this last case, the - MULTI: Retrieves all rows using fetchmany(). Wraps in an iterator for
cursor is returned if any query is executed, since it's used by chunked reads when supported.
subclasses such as InsertQuery). It's possible, however, that no query - SINGLE: Retrieves a single row using fetchone().
is needed, as the filters describe an empty set. In that case, None is - ROW_COUNT: Retrieves the number of rows in the result.
returned, to avoid any unnecessary database interaction. - CURSOR: Runs the query, and returns the cursor object. It is the
caller's responsibility to close the cursor.
""" """
result_type = result_type or NO_RESULTS result_type = result_type or NO_RESULTS
try: try:
@ -1627,6 +1628,11 @@ class SQLCompiler:
cursor.close() cursor.close()
raise raise
if result_type == ROW_COUNT:
try:
return cursor.rowcount
finally:
cursor.close()
if result_type == CURSOR: if result_type == CURSOR:
# Give the caller the cursor to process and close. # Give the caller the cursor to process and close.
return cursor return cursor
@ -2069,19 +2075,19 @@ class SQLUpdateCompiler(SQLCompiler):
non-empty query that is executed. Row counts for any subsequent, non-empty query that is executed. Row counts for any subsequent,
related queries are not available. related queries are not available.
""" """
cursor = super().execute_sql(result_type) row_count = super().execute_sql(result_type)
try: is_empty = row_count is None
rows = cursor.rowcount if cursor else 0 row_count = row_count or 0
is_empty = cursor is None
finally:
if cursor:
cursor.close()
for query in self.query.get_related_updates(): for query in self.query.get_related_updates():
aux_rows = query.get_compiler(self.using).execute_sql(result_type) # If the result_type is NO_RESULTS then the aux_row_count is None.
if is_empty and aux_rows: aux_row_count = query.get_compiler(self.using).execute_sql(result_type)
rows = aux_rows if is_empty and aux_row_count:
# Returns the row count for any related updates as the number of
# rows updated.
row_count = aux_row_count
is_empty = False is_empty = False
return rows return row_count
def pre_sql_setup(self): def pre_sql_setup(self):
""" """

View File

@ -11,8 +11,10 @@ GET_ITERATOR_CHUNK_SIZE = 100
# How many results to expect from a cursor.execute call # How many results to expect from a cursor.execute call
MULTI = "multi" MULTI = "multi"
SINGLE = "single" SINGLE = "single"
CURSOR = "cursor"
NO_RESULTS = "no results" NO_RESULTS = "no results"
# Rather than returning results, returns:
CURSOR = "cursor"
ROW_COUNT = "row count"
ORDER_DIR = { ORDER_DIR = {
"ASC": ("ASC", "DESC"), "ASC": ("ASC", "DESC"),

View File

@ -3,7 +3,11 @@ Query subclasses which provide extra functionality beyond simple data retrieval.
""" """
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.db.models.sql.constants import CURSOR, GET_ITERATOR_CHUNK_SIZE, NO_RESULTS from django.db.models.sql.constants import (
GET_ITERATOR_CHUNK_SIZE,
NO_RESULTS,
ROW_COUNT,
)
from django.db.models.sql.query import Query from django.db.models.sql.query import Query
__all__ = ["DeleteQuery", "UpdateQuery", "InsertQuery", "AggregateQuery"] __all__ = ["DeleteQuery", "UpdateQuery", "InsertQuery", "AggregateQuery"]
@ -17,11 +21,7 @@ class DeleteQuery(Query):
def do_query(self, table, where, using): def do_query(self, table, where, using):
self.alias_map = {table: self.alias_map[table]} self.alias_map = {table: self.alias_map[table]}
self.where = where self.where = where
cursor = self.get_compiler(using).execute_sql(CURSOR) return self.get_compiler(using).execute_sql(ROW_COUNT)
if cursor:
with cursor:
return cursor.rowcount
return 0
def delete_batch(self, pk_list, using): def delete_batch(self, pk_list, using):
""" """