main commit
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-10-16 16:30:25 +09:00
parent 91c7e04474
commit 537e7b363f
1146 changed files with 45926 additions and 77196 deletions

View File

@@ -1,5 +1,5 @@
# engine/cursor.py
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
# Copyright (C) 2005-2023 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
@@ -20,7 +20,6 @@ from typing import Any
from typing import cast
from typing import ClassVar
from typing import Dict
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Mapping
@@ -121,7 +120,7 @@ _CursorKeyMapRecType = Tuple[
List[Any], # MD_OBJECTS
str, # MD_LOOKUP_KEY
str, # MD_RENDERED_NAME
Optional["_ResultProcessorType[Any]"], # MD_PROCESSOR
Optional["_ResultProcessorType"], # MD_PROCESSOR
Optional[str], # MD_UNTRANSLATED
]
@@ -135,7 +134,7 @@ _NonAmbigCursorKeyMapRecType = Tuple[
List[Any],
str,
str,
Optional["_ResultProcessorType[Any]"],
Optional["_ResultProcessorType"],
str,
]
@@ -152,7 +151,7 @@ class CursorResultMetaData(ResultMetaData):
"_translated_indexes",
"_safe_for_cache",
"_unpickled",
"_key_to_index",
"_key_to_index"
# don't need _unique_filters support here for now. Can be added
# if a need arises.
)
@@ -226,11 +225,9 @@ class CursorResultMetaData(ResultMetaData):
{
key: (
# int index should be None for ambiguous key
(
value[0] + offset
if value[0] is not None and key not in keymap
else None
),
value[0] + offset
if value[0] is not None and key not in keymap
else None,
value[1] + offset,
*value[2:],
)
@@ -365,11 +362,13 @@ class CursorResultMetaData(ResultMetaData):
) = context.result_column_struct
num_ctx_cols = len(result_columns)
else:
result_columns = cols_are_ordered = ( # type: ignore
result_columns = ( # type: ignore
cols_are_ordered
) = (
num_ctx_cols
) = ad_hoc_textual = loose_column_name_matching = (
textual_ordered
) = False
) = (
ad_hoc_textual
) = loose_column_name_matching = textual_ordered = False
# merge cursor.description with the column info
# present in the compiled structure, if any
@@ -689,7 +688,6 @@ class CursorResultMetaData(ResultMetaData):
% (num_ctx_cols, len(cursor_description))
)
seen = set()
for (
idx,
colname,
@@ -1163,7 +1161,7 @@ class BufferedRowCursorFetchStrategy(CursorFetchStrategy):
result = conn.execution_options(
stream_results=True, max_row_buffer=50
).execute(text("select * from table"))
).execute(text("select * from table"))
.. versionadded:: 1.4 ``max_row_buffer`` may now exceed 1000 rows.
@@ -1248,9 +1246,8 @@ class BufferedRowCursorFetchStrategy(CursorFetchStrategy):
if size is None:
return self.fetchall(result, dbapi_cursor)
rb = self._rowbuffer
lb = len(rb)
close = False
buf = list(self._rowbuffer)
lb = len(buf)
if size > lb:
try:
new = dbapi_cursor.fetchmany(size - lb)
@@ -1258,15 +1255,13 @@ class BufferedRowCursorFetchStrategy(CursorFetchStrategy):
self.handle_exception(result, dbapi_cursor, e)
else:
if not new:
# defer closing since it may clear the row buffer
close = True
result._soft_close()
else:
rb.extend(new)
buf.extend(new)
res = [rb.popleft() for _ in range(min(size, len(rb)))]
if close:
result._soft_close()
return res
result = buf[0:size]
self._rowbuffer = collections.deque(buf[size:])
return result
def fetchall(self, result, dbapi_cursor):
try:
@@ -1290,16 +1285,12 @@ class FullyBufferedCursorFetchStrategy(CursorFetchStrategy):
__slots__ = ("_rowbuffer", "alternate_cursor_description")
def __init__(
self,
dbapi_cursor: Optional[DBAPICursor],
alternate_description: Optional[_DBAPICursorDescription] = None,
initial_buffer: Optional[Iterable[Any]] = None,
self, dbapi_cursor, alternate_description=None, initial_buffer=None
):
self.alternate_cursor_description = alternate_description
if initial_buffer is not None:
self._rowbuffer = collections.deque(initial_buffer)
else:
assert dbapi_cursor is not None
self._rowbuffer = collections.deque(dbapi_cursor.fetchall())
def yield_per(self, result, dbapi_cursor, num):
@@ -1324,8 +1315,9 @@ class FullyBufferedCursorFetchStrategy(CursorFetchStrategy):
if size is None:
return self.fetchall(result, dbapi_cursor)
rb = self._rowbuffer
rows = [rb.popleft() for _ in range(min(size, len(rb)))]
buf = list(self._rowbuffer)
rows = buf[0:size]
self._rowbuffer = collections.deque(buf[size:])
if not rows:
result._soft_close()
return rows
@@ -1358,15 +1350,15 @@ class _NoResultMetaData(ResultMetaData):
self._we_dont_return_rows()
@property
def _keymap(self): # type: ignore[override]
def _keymap(self):
self._we_dont_return_rows()
@property
def _key_to_index(self): # type: ignore[override]
def _key_to_index(self):
self._we_dont_return_rows()
@property
def _processors(self): # type: ignore[override]
def _processors(self):
self._we_dont_return_rows()
@property
@@ -1446,7 +1438,6 @@ class CursorResult(Result[_T]):
metadata = self._init_metadata(context, cursor_description)
_make_row: Any
_make_row = functools.partial(
Row,
metadata,
@@ -1619,11 +1610,11 @@ class CursorResult(Result[_T]):
"""
if not self.context.compiled:
raise exc.InvalidRequestError(
"Statement is not a compiled expression construct."
"Statement is not a compiled " "expression construct."
)
elif not self.context.isinsert:
raise exc.InvalidRequestError(
"Statement is not an insert() expression construct."
"Statement is not an insert() " "expression construct."
)
elif self.context._is_explicit_returning:
raise exc.InvalidRequestError(
@@ -1690,11 +1681,11 @@ class CursorResult(Result[_T]):
"""
if not self.context.compiled:
raise exc.InvalidRequestError(
"Statement is not a compiled expression construct."
"Statement is not a compiled " "expression construct."
)
elif not self.context.isupdate:
raise exc.InvalidRequestError(
"Statement is not an update() expression construct."
"Statement is not an update() " "expression construct."
)
elif self.context.executemany:
return self.context.compiled_parameters
@@ -1712,11 +1703,11 @@ class CursorResult(Result[_T]):
"""
if not self.context.compiled:
raise exc.InvalidRequestError(
"Statement is not a compiled expression construct."
"Statement is not a compiled " "expression construct."
)
elif not self.context.isinsert:
raise exc.InvalidRequestError(
"Statement is not an insert() expression construct."
"Statement is not an insert() " "expression construct."
)
elif self.context.executemany:
return self.context.compiled_parameters
@@ -1761,9 +1752,11 @@ class CursorResult(Result[_T]):
r1 = connection.execute(
users.insert().returning(
users.c.user_name, users.c.user_id, sort_by_parameter_order=True
users.c.user_name,
users.c.user_id,
sort_by_parameter_order=True
),
user_values,
user_values
)
r2 = connection.execute(
@@ -1771,16 +1764,19 @@ class CursorResult(Result[_T]):
addresses.c.address_id,
addresses.c.address,
addresses.c.user_id,
sort_by_parameter_order=True,
sort_by_parameter_order=True
),
address_values,
address_values
)
rows = r1.splice_horizontally(r2).all()
assert rows == [
("john", 1, 1, "foo@bar.com", 1),
("jack", 2, 2, "bar@bat.com", 2),
]
assert (
rows ==
[
("john", 1, 1, "foo@bar.com", 1),
("jack", 2, 2, "bar@bat.com", 2),
]
)
.. versionadded:: 2.0
@@ -1789,7 +1785,7 @@ class CursorResult(Result[_T]):
:meth:`.CursorResult.splice_vertically`
""" # noqa: E501
"""
clone = self._generate()
total_rows = [
@@ -1924,7 +1920,7 @@ class CursorResult(Result[_T]):
if not self.context.compiled:
raise exc.InvalidRequestError(
"Statement is not a compiled expression construct."
"Statement is not a compiled " "expression construct."
)
elif not self.context.isinsert and not self.context.isupdate:
raise exc.InvalidRequestError(
@@ -1947,7 +1943,7 @@ class CursorResult(Result[_T]):
if not self.context.compiled:
raise exc.InvalidRequestError(
"Statement is not a compiled expression construct."
"Statement is not a compiled " "expression construct."
)
elif not self.context.isinsert and not self.context.isupdate:
raise exc.InvalidRequestError(
@@ -1978,28 +1974,8 @@ class CursorResult(Result[_T]):
def rowcount(self) -> int:
"""Return the 'rowcount' for this result.
The primary purpose of 'rowcount' is to report the number of rows
matched by the WHERE criterion of an UPDATE or DELETE statement
executed once (i.e. for a single parameter set), which may then be
compared to the number of rows expected to be updated or deleted as a
means of asserting data integrity.
This attribute is transferred from the ``cursor.rowcount`` attribute
of the DBAPI before the cursor is closed, to support DBAPIs that
don't make this value available after cursor close. Some DBAPIs may
offer meaningful values for other kinds of statements, such as INSERT
and SELECT statements as well. In order to retrieve ``cursor.rowcount``
for these statements, set the
:paramref:`.Connection.execution_options.preserve_rowcount`
execution option to True, which will cause the ``cursor.rowcount``
value to be unconditionally memoized before any results are returned
or the cursor is closed, regardless of statement type.
For cases where the DBAPI does not support rowcount for a particular
kind of statement and/or execution, the returned value will be ``-1``,
which is delivered directly from the DBAPI and is part of :pep:`249`.
All DBAPIs should support rowcount for single-parameter-set
UPDATE and DELETE statements, however.
The 'rowcount' reports the number of rows *matched*
by the WHERE criterion of an UPDATE or DELETE statement.
.. note::
@@ -2008,47 +1984,38 @@ class CursorResult(Result[_T]):
* This attribute returns the number of rows *matched*,
which is not necessarily the same as the number of rows
that were actually *modified*. For example, an UPDATE statement
that were actually *modified* - an UPDATE statement, for example,
may have no net change on a given row if the SET values
given are the same as those present in the row already.
Such a row would be matched but not modified.
On backends that feature both styles, such as MySQL,
rowcount is configured to return the match
rowcount is configured by default to return the match
count in all cases.
* :attr:`_engine.CursorResult.rowcount` in the default case is
*only* useful in conjunction with an UPDATE or DELETE statement,
and only with a single set of parameters. For other kinds of
statements, SQLAlchemy will not attempt to pre-memoize the value
unless the
:paramref:`.Connection.execution_options.preserve_rowcount`
execution option is used. Note that contrary to :pep:`249`, many
DBAPIs do not support rowcount values for statements that are not
UPDATE or DELETE, particularly when rows are being returned which
are not fully pre-buffered. DBAPIs that dont support rowcount
for a particular kind of statement should return the value ``-1``
for such statements.
* :attr:`_engine.CursorResult.rowcount`
is *only* useful in conjunction
with an UPDATE or DELETE statement. Contrary to what the Python
DBAPI says, it does *not* reliably return the
number of rows available from the results of a SELECT statement
as DBAPIs cannot support this functionality when rows are
unbuffered.
* :attr:`_engine.CursorResult.rowcount` may not be meaningful
when executing a single statement with multiple parameter sets
(i.e. an :term:`executemany`). Most DBAPIs do not sum "rowcount"
values across multiple parameter sets and will return ``-1``
when accessed.
* :attr:`_engine.CursorResult.rowcount`
may not be fully implemented by
all dialects. In particular, most DBAPIs do not support an
aggregate rowcount result from an executemany call.
The :meth:`_engine.CursorResult.supports_sane_rowcount` and
:meth:`_engine.CursorResult.supports_sane_multi_rowcount` methods
will report from the dialect if each usage is known to be
supported.
* SQLAlchemy's :ref:`engine_insertmanyvalues` feature does support
a correct population of :attr:`_engine.CursorResult.rowcount`
when the :paramref:`.Connection.execution_options.preserve_rowcount`
execution option is set to True.
* Statements that use RETURNING may not support rowcount, returning
a ``-1`` value instead.
* Statements that use RETURNING may not return a correct
rowcount.
.. seealso::
:ref:`tutorial_update_delete_rowcount` - in the :ref:`unified_tutorial`
:paramref:`.Connection.execution_options.preserve_rowcount`
""" # noqa: E501
try:
return self.context.rowcount
@@ -2142,7 +2109,8 @@ class CursorResult(Result[_T]):
def merge(self, *others: Result[Any]) -> MergedResult[Any]:
merged_result = super().merge(*others)
if self.context._has_rowcount:
setup_rowcounts = self.context._has_rowcount
if setup_rowcounts:
merged_result.rowcount = sum(
cast("CursorResult[Any]", result).rowcount
for result in (self,) + others