1# engine/result.py
2# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7
8"""Define generic result set constructs."""
9
10from __future__ import annotations
11
12from enum import Enum
13import functools
14import itertools
15import operator
16import typing
17from typing import Any
18from typing import Callable
19from typing import cast
20from typing import Dict
21from typing import Generic
22from typing import Iterable
23from typing import Iterator
24from typing import List
25from typing import Mapping
26from typing import NoReturn
27from typing import Optional
28from typing import overload
29from typing import Sequence
30from typing import Set
31from typing import Tuple
32from typing import TYPE_CHECKING
33from typing import TypeVar
34from typing import Union
35
36from .row import Row
37from .row import RowMapping
38from .. import exc
39from .. import util
40from ..sql.base import _generative
41from ..sql.base import HasMemoized
42from ..sql.base import InPlaceGenerative
43from ..util import HasMemoized_ro_memoized_attribute
44from ..util import NONE_SET
45from ..util._has_cy import HAS_CYEXTENSION
46from ..util.typing import Literal
47from ..util.typing import Self
48
49if typing.TYPE_CHECKING or not HAS_CYEXTENSION:
50 from ._py_row import tuplegetter as tuplegetter
51else:
52 from sqlalchemy.cyextension.resultproxy import tuplegetter as tuplegetter
53
54if typing.TYPE_CHECKING:
55 from typing import Type
56
57 from .. import inspection
58 from ..sql import roles
59 from ..sql._typing import _HasClauseElement
60 from ..sql.elements import SQLCoreOperations
61 from ..sql.type_api import _ResultProcessorType
62
63_KeyType = Union[
64 str,
65 "SQLCoreOperations[Any]",
66 "roles.TypedColumnsClauseRole[Any]",
67 "roles.ColumnsClauseRole",
68 "Type[Any]",
69 "inspection.Inspectable[_HasClauseElement[Any]]",
70]
71_KeyIndexType = Union[_KeyType, int]
72
73# is overridden in cursor using _CursorKeyMapRecType
74_KeyMapRecType = Any
75
76_KeyMapType = Mapping[_KeyType, _KeyMapRecType]
77
78
79_RowData = Union[Row[Any], RowMapping, Any]
80"""A generic form of "row" that accommodates for the different kinds of
81"rows" that different result objects return, including row, row mapping, and
82scalar values"""
83
84_RawRowType = Tuple[Any, ...]
85"""represents the kind of row we get from a DBAPI cursor"""
86
87_R = TypeVar("_R", bound=_RowData)
88_T = TypeVar("_T", bound=Any)
89_TP = TypeVar("_TP", bound=Tuple[Any, ...])
90
91_InterimRowType = Union[_R, _RawRowType]
92"""a catchall "anything" kind of return type that can be applied
93across all the result types
94
95"""
96
97_InterimSupportsScalarsRowType = Union[Row[Any], Any]
98
99_ProcessorsType = Sequence[Optional["_ResultProcessorType[Any]"]]
100_TupleGetterType = Callable[[Sequence[Any]], Sequence[Any]]
101_UniqueFilterType = Callable[[Any], Any]
102_UniqueFilterStateType = Tuple[Set[Any], Optional[_UniqueFilterType]]
103
104
105class ResultMetaData:
106 """Base for metadata about result rows."""
107
108 __slots__ = ()
109
110 _tuplefilter: Optional[_TupleGetterType] = None
111 _translated_indexes: Optional[Sequence[int]] = None
112 _unique_filters: Optional[Sequence[Callable[[Any], Any]]] = None
113 _keymap: _KeyMapType
114 _keys: Sequence[str]
115 _processors: Optional[_ProcessorsType]
116 _key_to_index: Mapping[_KeyType, int]
117
118 @property
119 def keys(self) -> RMKeyView:
120 return RMKeyView(self)
121
122 def _has_key(self, key: object) -> bool:
123 raise NotImplementedError()
124
125 def _for_freeze(self) -> ResultMetaData:
126 raise NotImplementedError()
127
128 @overload
129 def _key_fallback(
130 self, key: Any, err: Optional[Exception], raiseerr: Literal[True] = ...
131 ) -> NoReturn: ...
132
133 @overload
134 def _key_fallback(
135 self,
136 key: Any,
137 err: Optional[Exception],
138 raiseerr: Literal[False] = ...,
139 ) -> None: ...
140
141 @overload
142 def _key_fallback(
143 self, key: Any, err: Optional[Exception], raiseerr: bool = ...
144 ) -> Optional[NoReturn]: ...
145
146 def _key_fallback(
147 self, key: Any, err: Optional[Exception], raiseerr: bool = True
148 ) -> Optional[NoReturn]:
149 assert raiseerr
150 raise KeyError(key) from err
151
152 def _raise_for_ambiguous_column_name(
153 self, rec: _KeyMapRecType
154 ) -> NoReturn:
155 raise NotImplementedError(
156 "ambiguous column name logic is implemented for "
157 "CursorResultMetaData"
158 )
159
160 def _index_for_key(
161 self, key: _KeyIndexType, raiseerr: bool
162 ) -> Optional[int]:
163 raise NotImplementedError()
164
165 def _indexes_for_keys(
166 self, keys: Sequence[_KeyIndexType]
167 ) -> Sequence[int]:
168 raise NotImplementedError()
169
170 def _metadata_for_keys(
171 self, keys: Sequence[_KeyIndexType]
172 ) -> Iterator[_KeyMapRecType]:
173 raise NotImplementedError()
174
175 def _reduce(self, keys: Sequence[_KeyIndexType]) -> ResultMetaData:
176 raise NotImplementedError()
177
178 def _getter(
179 self, key: Any, raiseerr: bool = True
180 ) -> Optional[Callable[[Row[Any]], Any]]:
181 index = self._index_for_key(key, raiseerr)
182
183 if index is not None:
184 return operator.itemgetter(index)
185 else:
186 return None
187
188 def _row_as_tuple_getter(
189 self, keys: Sequence[_KeyIndexType]
190 ) -> _TupleGetterType:
191 indexes = self._indexes_for_keys(keys)
192 return tuplegetter(*indexes)
193
194 def _make_key_to_index(
195 self, keymap: Mapping[_KeyType, Sequence[Any]], index: int
196 ) -> Mapping[_KeyType, int]:
197 return {
198 key: rec[index]
199 for key, rec in keymap.items()
200 if rec[index] is not None
201 }
202
203 def _key_not_found(self, key: Any, attr_error: bool) -> NoReturn:
204 if key in self._keymap:
205 # the index must be none in this case
206 self._raise_for_ambiguous_column_name(self._keymap[key])
207 else:
208 # unknown key
209 if attr_error:
210 try:
211 self._key_fallback(key, None)
212 except KeyError as ke:
213 raise AttributeError(ke.args[0]) from ke
214 else:
215 self._key_fallback(key, None)
216
217 @property
218 def _effective_processors(self) -> Optional[_ProcessorsType]:
219 if not self._processors or NONE_SET.issuperset(self._processors):
220 return None
221 else:
222 return self._processors
223
224
225class RMKeyView(typing.KeysView[Any]):
226 __slots__ = ("_parent", "_keys")
227
228 _parent: ResultMetaData
229 _keys: Sequence[str]
230
231 def __init__(self, parent: ResultMetaData):
232 self._parent = parent
233 self._keys = [k for k in parent._keys if k is not None]
234
235 def __len__(self) -> int:
236 return len(self._keys)
237
238 def __repr__(self) -> str:
239 return "{0.__class__.__name__}({0._keys!r})".format(self)
240
241 def __iter__(self) -> Iterator[str]:
242 return iter(self._keys)
243
244 def __contains__(self, item: Any) -> bool:
245 if isinstance(item, int):
246 return False
247
248 # note this also includes special key fallback behaviors
249 # which also don't seem to be tested in test_resultset right now
250 return self._parent._has_key(item)
251
252 def __eq__(self, other: Any) -> bool:
253 return list(other) == list(self)
254
255 def __ne__(self, other: Any) -> bool:
256 return list(other) != list(self)
257
258
259class SimpleResultMetaData(ResultMetaData):
260 """result metadata for in-memory collections."""
261
262 __slots__ = (
263 "_keys",
264 "_keymap",
265 "_processors",
266 "_tuplefilter",
267 "_translated_indexes",
268 "_unique_filters",
269 "_key_to_index",
270 )
271
272 _keys: Sequence[str]
273
274 def __init__(
275 self,
276 keys: Sequence[str],
277 extra: Optional[Sequence[Any]] = None,
278 _processors: Optional[_ProcessorsType] = None,
279 _tuplefilter: Optional[_TupleGetterType] = None,
280 _translated_indexes: Optional[Sequence[int]] = None,
281 _unique_filters: Optional[Sequence[Callable[[Any], Any]]] = None,
282 ):
283 self._keys = list(keys)
284 self._tuplefilter = _tuplefilter
285 self._translated_indexes = _translated_indexes
286 self._unique_filters = _unique_filters
287 if extra:
288 recs_names = [
289 (
290 (name,) + (extras if extras else ()),
291 (index, name, extras),
292 )
293 for index, (name, extras) in enumerate(zip(self._keys, extra))
294 ]
295 else:
296 recs_names = [
297 ((name,), (index, name, ()))
298 for index, name in enumerate(self._keys)
299 ]
300
301 self._keymap = {key: rec for keys, rec in recs_names for key in keys}
302
303 self._processors = _processors
304
305 self._key_to_index = self._make_key_to_index(self._keymap, 0)
306
307 def _has_key(self, key: object) -> bool:
308 return key in self._keymap
309
310 def _for_freeze(self) -> ResultMetaData:
311 unique_filters = self._unique_filters
312 if unique_filters and self._tuplefilter:
313 unique_filters = self._tuplefilter(unique_filters)
314
315 # TODO: are we freezing the result with or without uniqueness
316 # applied?
317 return SimpleResultMetaData(
318 self._keys,
319 extra=[self._keymap[key][2] for key in self._keys],
320 _unique_filters=unique_filters,
321 )
322
323 def __getstate__(self) -> Dict[str, Any]:
324 return {
325 "_keys": self._keys,
326 "_translated_indexes": self._translated_indexes,
327 }
328
329 def __setstate__(self, state: Dict[str, Any]) -> None:
330 if state["_translated_indexes"]:
331 _translated_indexes = state["_translated_indexes"]
332 _tuplefilter = tuplegetter(*_translated_indexes)
333 else:
334 _translated_indexes = _tuplefilter = None
335 self.__init__( # type: ignore
336 state["_keys"],
337 _translated_indexes=_translated_indexes,
338 _tuplefilter=_tuplefilter,
339 )
340
341 def _index_for_key(self, key: Any, raiseerr: bool = True) -> int:
342 if int in key.__class__.__mro__:
343 key = self._keys[key]
344 try:
345 rec = self._keymap[key]
346 except KeyError as ke:
347 rec = self._key_fallback(key, ke, raiseerr)
348
349 return rec[0] # type: ignore[no-any-return]
350
351 def _indexes_for_keys(self, keys: Sequence[Any]) -> Sequence[int]:
352 return [self._keymap[key][0] for key in keys]
353
354 def _metadata_for_keys(
355 self, keys: Sequence[Any]
356 ) -> Iterator[_KeyMapRecType]:
357 for key in keys:
358 if int in key.__class__.__mro__:
359 key = self._keys[key]
360
361 try:
362 rec = self._keymap[key]
363 except KeyError as ke:
364 rec = self._key_fallback(key, ke, True)
365
366 yield rec
367
368 def _reduce(self, keys: Sequence[Any]) -> ResultMetaData:
369 try:
370 metadata_for_keys = [
371 self._keymap[
372 self._keys[key] if int in key.__class__.__mro__ else key
373 ]
374 for key in keys
375 ]
376 except KeyError as ke:
377 self._key_fallback(ke.args[0], ke, True)
378
379 indexes: Sequence[int]
380 new_keys: Sequence[str]
381 extra: Sequence[Any]
382 indexes, new_keys, extra = zip(*metadata_for_keys)
383
384 if self._translated_indexes:
385 indexes = [self._translated_indexes[idx] for idx in indexes]
386
387 tup = tuplegetter(*indexes)
388
389 new_metadata = SimpleResultMetaData(
390 new_keys,
391 extra=extra,
392 _tuplefilter=tup,
393 _translated_indexes=indexes,
394 _processors=self._processors,
395 _unique_filters=self._unique_filters,
396 )
397
398 return new_metadata
399
400
401def result_tuple(
402 fields: Sequence[str], extra: Optional[Any] = None
403) -> Callable[[Iterable[Any]], Row[Any]]:
404 parent = SimpleResultMetaData(fields, extra)
405 return functools.partial(
406 Row, parent, parent._effective_processors, parent._key_to_index
407 )
408
409
410# a symbol that indicates to internal Result methods that
411# "no row is returned". We can't use None for those cases where a scalar
412# filter is applied to rows.
413class _NoRow(Enum):
414 _NO_ROW = 0
415
416
417_NO_ROW = _NoRow._NO_ROW
418
419
420class ResultInternal(InPlaceGenerative, Generic[_R]):
421 __slots__ = ()
422
423 _real_result: Optional[Result[Any]] = None
424 _generate_rows: bool = True
425 _row_logging_fn: Optional[Callable[[Any], Any]]
426
427 _unique_filter_state: Optional[_UniqueFilterStateType] = None
428 _post_creational_filter: Optional[Callable[[Any], Any]] = None
429 _is_cursor = False
430
431 _metadata: ResultMetaData
432
433 _source_supports_scalars: bool
434
435 def _fetchiter_impl(self) -> Iterator[_InterimRowType[Row[Any]]]:
436 raise NotImplementedError()
437
438 def _fetchone_impl(
439 self, hard_close: bool = False
440 ) -> Optional[_InterimRowType[Row[Any]]]:
441 raise NotImplementedError()
442
443 def _fetchmany_impl(
444 self, size: Optional[int] = None
445 ) -> List[_InterimRowType[Row[Any]]]:
446 raise NotImplementedError()
447
448 def _fetchall_impl(self) -> List[_InterimRowType[Row[Any]]]:
449 raise NotImplementedError()
450
451 def _soft_close(self, hard: bool = False) -> None:
452 raise NotImplementedError()
453
454 @HasMemoized_ro_memoized_attribute
455 def _row_getter(self) -> Optional[Callable[..., _R]]:
456 real_result: Result[Any] = (
457 self._real_result
458 if self._real_result
459 else cast("Result[Any]", self)
460 )
461
462 if real_result._source_supports_scalars:
463 if not self._generate_rows:
464 return None
465 else:
466 _proc = Row
467
468 def process_row(
469 metadata: ResultMetaData,
470 processors: Optional[_ProcessorsType],
471 key_to_index: Mapping[_KeyType, int],
472 scalar_obj: Any,
473 ) -> Row[Any]:
474 return _proc(
475 metadata, processors, key_to_index, (scalar_obj,)
476 )
477
478 else:
479 process_row = Row # type: ignore
480
481 metadata = self._metadata
482
483 key_to_index = metadata._key_to_index
484 processors = metadata._effective_processors
485 tf = metadata._tuplefilter
486
487 if tf and not real_result._source_supports_scalars:
488 if processors:
489 processors = tf(processors)
490
491 _make_row_orig: Callable[..., _R] = functools.partial( # type: ignore # noqa E501
492 process_row, metadata, processors, key_to_index
493 )
494
495 fixed_tf = tf
496
497 def make_row(row: _InterimRowType[Row[Any]]) -> _R:
498 return _make_row_orig(fixed_tf(row))
499
500 else:
501 make_row = functools.partial( # type: ignore
502 process_row, metadata, processors, key_to_index
503 )
504
505 if real_result._row_logging_fn:
506 _log_row = real_result._row_logging_fn
507 _make_row = make_row
508
509 def make_row(row: _InterimRowType[Row[Any]]) -> _R:
510 return _log_row(_make_row(row)) # type: ignore
511
512 return make_row
513
514 @HasMemoized_ro_memoized_attribute
515 def _iterator_getter(self) -> Callable[..., Iterator[_R]]:
516 make_row = self._row_getter
517
518 post_creational_filter = self._post_creational_filter
519
520 if self._unique_filter_state:
521 uniques, strategy = self._unique_strategy
522
523 def iterrows(self: Result[Any]) -> Iterator[_R]:
524 for raw_row in self._fetchiter_impl():
525 obj: _InterimRowType[Any] = (
526 make_row(raw_row) if make_row else raw_row
527 )
528 hashed = strategy(obj) if strategy else obj
529 if hashed in uniques:
530 continue
531 uniques.add(hashed)
532 if post_creational_filter:
533 obj = post_creational_filter(obj)
534 yield obj # type: ignore
535
536 else:
537
538 def iterrows(self: Result[Any]) -> Iterator[_R]:
539 for raw_row in self._fetchiter_impl():
540 row: _InterimRowType[Any] = (
541 make_row(raw_row) if make_row else raw_row
542 )
543 if post_creational_filter:
544 row = post_creational_filter(row)
545 yield row # type: ignore
546
547 return iterrows
548
549 def _raw_all_rows(self) -> List[_R]:
550 make_row = self._row_getter
551 assert make_row is not None
552 rows = self._fetchall_impl()
553 return [make_row(row) for row in rows]
554
555 def _allrows(self) -> List[_R]:
556 post_creational_filter = self._post_creational_filter
557
558 make_row = self._row_getter
559
560 rows = self._fetchall_impl()
561 made_rows: List[_InterimRowType[_R]]
562 if make_row:
563 made_rows = [make_row(row) for row in rows]
564 else:
565 made_rows = rows # type: ignore
566
567 interim_rows: List[_R]
568
569 if self._unique_filter_state:
570 uniques, strategy = self._unique_strategy
571
572 interim_rows = [
573 made_row # type: ignore
574 for made_row, sig_row in [
575 (
576 made_row,
577 strategy(made_row) if strategy else made_row,
578 )
579 for made_row in made_rows
580 ]
581 if sig_row not in uniques and not uniques.add(sig_row) # type: ignore # noqa: E501
582 ]
583 else:
584 interim_rows = made_rows # type: ignore
585
586 if post_creational_filter:
587 interim_rows = [
588 post_creational_filter(row) for row in interim_rows
589 ]
590 return interim_rows
591
592 @HasMemoized_ro_memoized_attribute
593 def _onerow_getter(
594 self,
595 ) -> Callable[..., Union[Literal[_NoRow._NO_ROW], _R]]:
596 make_row = self._row_getter
597
598 post_creational_filter = self._post_creational_filter
599
600 if self._unique_filter_state:
601 uniques, strategy = self._unique_strategy
602
603 def onerow(self: Result[Any]) -> Union[_NoRow, _R]:
604 _onerow = self._fetchone_impl
605 while True:
606 row = _onerow()
607 if row is None:
608 return _NO_ROW
609 else:
610 obj: _InterimRowType[Any] = (
611 make_row(row) if make_row else row
612 )
613 hashed = strategy(obj) if strategy else obj
614 if hashed in uniques:
615 continue
616 else:
617 uniques.add(hashed)
618 if post_creational_filter:
619 obj = post_creational_filter(obj)
620 return obj # type: ignore
621
622 else:
623
624 def onerow(self: Result[Any]) -> Union[_NoRow, _R]:
625 row = self._fetchone_impl()
626 if row is None:
627 return _NO_ROW
628 else:
629 interim_row: _InterimRowType[Any] = (
630 make_row(row) if make_row else row
631 )
632 if post_creational_filter:
633 interim_row = post_creational_filter(interim_row)
634 return interim_row # type: ignore
635
636 return onerow
637
638 @HasMemoized_ro_memoized_attribute
639 def _manyrow_getter(self) -> Callable[..., List[_R]]:
640 make_row = self._row_getter
641
642 post_creational_filter = self._post_creational_filter
643
644 if self._unique_filter_state:
645 uniques, strategy = self._unique_strategy
646
647 def filterrows(
648 make_row: Optional[Callable[..., _R]],
649 rows: List[Any],
650 strategy: Optional[Callable[[List[Any]], Any]],
651 uniques: Set[Any],
652 ) -> List[_R]:
653 if make_row:
654 rows = [make_row(row) for row in rows]
655
656 if strategy:
657 made_rows = (
658 (made_row, strategy(made_row)) for made_row in rows
659 )
660 else:
661 made_rows = ((made_row, made_row) for made_row in rows)
662 return [
663 made_row
664 for made_row, sig_row in made_rows
665 if sig_row not in uniques and not uniques.add(sig_row) # type: ignore # noqa: E501
666 ]
667
668 def manyrows(
669 self: ResultInternal[_R], num: Optional[int]
670 ) -> List[_R]:
671 collect: List[_R] = []
672
673 _manyrows = self._fetchmany_impl
674
675 if num is None:
676 # if None is passed, we don't know the default
677 # manyrows number, DBAPI has this as cursor.arraysize
678 # different DBAPIs / fetch strategies may be different.
679 # do a fetch to find what the number is. if there are
680 # only fewer rows left, then it doesn't matter.
681 real_result = (
682 self._real_result
683 if self._real_result
684 else cast("Result[Any]", self)
685 )
686 if real_result._yield_per:
687 num_required = num = real_result._yield_per
688 else:
689 rows = _manyrows(num)
690 num = len(rows)
691 assert make_row is not None
692 collect.extend(
693 filterrows(make_row, rows, strategy, uniques)
694 )
695 num_required = num - len(collect)
696 else:
697 num_required = num
698
699 assert num is not None
700
701 while num_required:
702 rows = _manyrows(num_required)
703 if not rows:
704 break
705
706 collect.extend(
707 filterrows(make_row, rows, strategy, uniques)
708 )
709 num_required = num - len(collect)
710
711 if post_creational_filter:
712 collect = [post_creational_filter(row) for row in collect]
713 return collect
714
715 else:
716
717 def manyrows(
718 self: ResultInternal[_R], num: Optional[int]
719 ) -> List[_R]:
720 if num is None:
721 real_result = (
722 self._real_result
723 if self._real_result
724 else cast("Result[Any]", self)
725 )
726 num = real_result._yield_per
727
728 rows: List[_InterimRowType[Any]] = self._fetchmany_impl(num)
729 if make_row:
730 rows = [make_row(row) for row in rows]
731 if post_creational_filter:
732 rows = [post_creational_filter(row) for row in rows]
733 return rows # type: ignore
734
735 return manyrows
736
737 @overload
738 def _only_one_row(
739 self: ResultInternal[Row[Any]],
740 raise_for_second_row: bool,
741 raise_for_none: bool,
742 scalar: Literal[True],
743 ) -> Any: ...
744
745 @overload
746 def _only_one_row(
747 self,
748 raise_for_second_row: bool,
749 raise_for_none: Literal[True],
750 scalar: bool,
751 ) -> _R: ...
752
753 @overload
754 def _only_one_row(
755 self,
756 raise_for_second_row: bool,
757 raise_for_none: bool,
758 scalar: bool,
759 ) -> Optional[_R]: ...
760
761 def _only_one_row(
762 self,
763 raise_for_second_row: bool,
764 raise_for_none: bool,
765 scalar: bool,
766 ) -> Optional[_R]:
767 onerow = self._fetchone_impl
768
769 row: Optional[_InterimRowType[Any]] = onerow(hard_close=True)
770 if row is None:
771 if raise_for_none:
772 raise exc.NoResultFound(
773 "No row was found when one was required"
774 )
775 else:
776 return None
777
778 if scalar and self._source_supports_scalars:
779 self._generate_rows = False
780 make_row = None
781 else:
782 make_row = self._row_getter
783
784 try:
785 row = make_row(row) if make_row else row
786 except:
787 self._soft_close(hard=True)
788 raise
789
790 if raise_for_second_row:
791 if self._unique_filter_state:
792 # for no second row but uniqueness, need to essentially
793 # consume the entire result :(
794 uniques, strategy = self._unique_strategy
795
796 existing_row_hash = strategy(row) if strategy else row
797
798 while True:
799 next_row: Any = onerow(hard_close=True)
800 if next_row is None:
801 next_row = _NO_ROW
802 break
803
804 try:
805 next_row = make_row(next_row) if make_row else next_row
806
807 if strategy:
808 assert next_row is not _NO_ROW
809 if existing_row_hash == strategy(next_row):
810 continue
811 elif row == next_row:
812 continue
813 # here, we have a row and it's different
814 break
815 except:
816 self._soft_close(hard=True)
817 raise
818 else:
819 next_row = onerow(hard_close=True)
820 if next_row is None:
821 next_row = _NO_ROW
822
823 if next_row is not _NO_ROW:
824 self._soft_close(hard=True)
825 raise exc.MultipleResultsFound(
826 "Multiple rows were found when exactly one was required"
827 if raise_for_none
828 else "Multiple rows were found when one or none "
829 "was required"
830 )
831 else:
832 # if we checked for second row then that would have
833 # closed us :)
834 self._soft_close(hard=True)
835
836 if not scalar:
837 post_creational_filter = self._post_creational_filter
838 if post_creational_filter:
839 row = post_creational_filter(row)
840
841 if scalar and make_row:
842 return row[0] # type: ignore
843 else:
844 return row # type: ignore
845
846 def _iter_impl(self) -> Iterator[_R]:
847 return self._iterator_getter(self)
848
849 def _next_impl(self) -> _R:
850 row = self._onerow_getter(self)
851 if row is _NO_ROW:
852 raise StopIteration()
853 else:
854 return row
855
856 @_generative
857 def _column_slices(self, indexes: Sequence[_KeyIndexType]) -> Self:
858 real_result = (
859 self._real_result
860 if self._real_result
861 else cast("Result[Any]", self)
862 )
863
864 if not real_result._source_supports_scalars or len(indexes) != 1:
865 self._metadata = self._metadata._reduce(indexes)
866
867 assert self._generate_rows
868
869 return self
870
871 @HasMemoized.memoized_attribute
872 def _unique_strategy(self) -> _UniqueFilterStateType:
873 assert self._unique_filter_state is not None
874 uniques, strategy = self._unique_filter_state
875
876 real_result = (
877 self._real_result
878 if self._real_result is not None
879 else cast("Result[Any]", self)
880 )
881
882 if not strategy and self._metadata._unique_filters:
883 if (
884 real_result._source_supports_scalars
885 and not self._generate_rows
886 ):
887 strategy = self._metadata._unique_filters[0]
888 else:
889 filters = self._metadata._unique_filters
890 if self._metadata._tuplefilter:
891 filters = self._metadata._tuplefilter(filters)
892
893 strategy = operator.methodcaller("_filter_on_values", filters)
894 return uniques, strategy
895
896
897class _WithKeys:
898 __slots__ = ()
899
900 _metadata: ResultMetaData
901
902 # used mainly to share documentation on the keys method.
903 def keys(self) -> RMKeyView:
904 """Return an iterable view which yields the string keys that would
905 be represented by each :class:`_engine.Row`.
906
907 The keys can represent the labels of the columns returned by a core
908 statement or the names of the orm classes returned by an orm
909 execution.
910
911 The view also can be tested for key containment using the Python
912 ``in`` operator, which will test both for the string keys represented
913 in the view, as well as for alternate keys such as column objects.
914
915 .. versionchanged:: 1.4 a key view object is returned rather than a
916 plain list.
917
918
919 """
920 return self._metadata.keys
921
922
923class Result(_WithKeys, ResultInternal[Row[_TP]]):
924 """Represent a set of database results.
925
926 .. versionadded:: 1.4 The :class:`_engine.Result` object provides a
927 completely updated usage model and calling facade for SQLAlchemy
928 Core and SQLAlchemy ORM. In Core, it forms the basis of the
929 :class:`_engine.CursorResult` object which replaces the previous
930 :class:`_engine.ResultProxy` interface. When using the ORM, a
931 higher level object called :class:`_engine.ChunkedIteratorResult`
932 is normally used.
933
934 .. note:: In SQLAlchemy 1.4 and above, this object is
935 used for ORM results returned by :meth:`_orm.Session.execute`, which can
936 yield instances of ORM mapped objects either individually or within
937 tuple-like rows. Note that the :class:`_engine.Result` object does not
938 deduplicate instances or rows automatically as is the case with the
939 legacy :class:`_orm.Query` object. For in-Python de-duplication of
940 instances or rows, use the :meth:`_engine.Result.unique` modifier
941 method.
942
943 .. seealso::
944
945 :ref:`tutorial_fetching_rows` - in the :doc:`/tutorial/index`
946
947 """
948
949 __slots__ = ("_metadata", "__dict__")
950
951 _row_logging_fn: Optional[Callable[[Row[Any]], Row[Any]]] = None
952
953 _source_supports_scalars: bool = False
954
955 _yield_per: Optional[int] = None
956
957 _attributes: util.immutabledict[Any, Any] = util.immutabledict()
958
959 def __init__(self, cursor_metadata: ResultMetaData):
960 self._metadata = cursor_metadata
961
962 def __enter__(self) -> Self:
963 return self
964
965 def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
966 self.close()
967
968 def close(self) -> None:
969 """Hard close this :class:`_engine.Result`.
970
971 The behavior of this method is implementation specific, and is
972 not implemented by default. The method should generally end
973 the resources in use by the result object and also cause any
974 subsequent iteration or row fetching to raise
975 :class:`.ResourceClosedError`.
976
977 .. versionadded:: 1.4.27 - ``.close()`` was previously not generally
978 available for all :class:`_engine.Result` classes, instead only
979 being available on the :class:`_engine.CursorResult` returned for
980 Core statement executions. As most other result objects, namely the
981 ones used by the ORM, are proxying a :class:`_engine.CursorResult`
982 in any case, this allows the underlying cursor result to be closed
983 from the outside facade for the case when the ORM query is using
984 the ``yield_per`` execution option where it does not immediately
985 exhaust and autoclose the database cursor.
986
987 """
988 self._soft_close(hard=True)
989
990 @property
991 def _soft_closed(self) -> bool:
992 raise NotImplementedError()
993
994 @property
995 def closed(self) -> bool:
996 """Return ``True`` if this :class:`_engine.Result` was **hard closed**
997 by explicitly calling the :meth:`close` method.
998
999 The attribute is **not** True if the :class:`_engine.Result` was only
1000 **soft closed**; a "soft close" is the style of close that takes place
1001 for example when the :class:`.CursorResult` is returned for a DML
1002 only statement without RETURNING, or when all result rows are fetched.
1003
1004 .. seealso::
1005
1006 :attr:`.CursorResult.returns_rows` - attribute specific to
1007 :class:`.CursorResult` which indicates if the result is one that
1008 may return zero or more rows
1009
1010 """
1011 raise NotImplementedError()
1012
1013 @_generative
1014 def yield_per(self, num: int) -> Self:
1015 """Configure the row-fetching strategy to fetch ``num`` rows at a time.
1016
1017 This impacts the underlying behavior of the result when iterating over
1018 the result object, or otherwise making use of methods such as
1019 :meth:`_engine.Result.fetchone` that return one row at a time. Data
1020 from the underlying cursor or other data source will be buffered up to
1021 this many rows in memory, and the buffered collection will then be
1022 yielded out one row at a time or as many rows are requested. Each time
1023 the buffer clears, it will be refreshed to this many rows or as many
1024 rows remain if fewer remain.
1025
1026 The :meth:`_engine.Result.yield_per` method is generally used in
1027 conjunction with the
1028 :paramref:`_engine.Connection.execution_options.stream_results`
1029 execution option, which will allow the database dialect in use to make
1030 use of a server side cursor, if the DBAPI supports a specific "server
1031 side cursor" mode separate from its default mode of operation.
1032
1033 .. tip::
1034
1035 Consider using the
1036 :paramref:`_engine.Connection.execution_options.yield_per`
1037 execution option, which will simultaneously set
1038 :paramref:`_engine.Connection.execution_options.stream_results`
1039 to ensure the use of server side cursors, as well as automatically
1040 invoke the :meth:`_engine.Result.yield_per` method to establish
1041 a fixed row buffer size at once.
1042
1043 The :paramref:`_engine.Connection.execution_options.yield_per`
1044 execution option is available for ORM operations, with
1045 :class:`_orm.Session`-oriented use described at
1046 :ref:`orm_queryguide_yield_per`. The Core-only version which works
1047 with :class:`_engine.Connection` is new as of SQLAlchemy 1.4.40.
1048
1049 .. versionadded:: 1.4
1050
1051 :param num: number of rows to fetch each time the buffer is refilled.
1052 If set to a value below 1, fetches all rows for the next buffer.
1053
1054 .. seealso::
1055
1056 :ref:`engine_stream_results` - describes Core behavior for
1057 :meth:`_engine.Result.yield_per`
1058
1059 :ref:`orm_queryguide_yield_per` - in the :ref:`queryguide_toplevel`
1060
1061 """
1062 self._yield_per = num
1063 return self
1064
1065 @_generative
1066 def unique(self, strategy: Optional[_UniqueFilterType] = None) -> Self:
1067 """Apply unique filtering to the objects returned by this
1068 :class:`_engine.Result`.
1069
1070 When this filter is applied with no arguments, the rows or objects
1071 returned will filtered such that each row is returned uniquely. The
1072 algorithm used to determine this uniqueness is by default the Python
1073 hashing identity of the whole tuple. In some cases a specialized
1074 per-entity hashing scheme may be used, such as when using the ORM, a
1075 scheme is applied which works against the primary key identity of
1076 returned objects.
1077
1078 The unique filter is applied **after all other filters**, which means
1079 if the columns returned have been refined using a method such as the
1080 :meth:`_engine.Result.columns` or :meth:`_engine.Result.scalars`
1081 method, the uniquing is applied to **only the column or columns
1082 returned**. This occurs regardless of the order in which these
1083 methods have been called upon the :class:`_engine.Result` object.
1084
1085 The unique filter also changes the calculus used for methods like
1086 :meth:`_engine.Result.fetchmany` and :meth:`_engine.Result.partitions`.
1087 When using :meth:`_engine.Result.unique`, these methods will continue
1088 to yield the number of rows or objects requested, after uniquing
1089 has been applied. However, this necessarily impacts the buffering
1090 behavior of the underlying cursor or datasource, such that multiple
1091 underlying calls to ``cursor.fetchmany()`` may be necessary in order
1092 to accumulate enough objects in order to provide a unique collection
1093 of the requested size.
1094
1095 :param strategy: a callable that will be applied to rows or objects
1096 being iterated, which should return an object that represents the
1097 unique value of the row. A Python ``set()`` is used to store
1098 these identities. If not passed, a default uniqueness strategy
1099 is used which may have been assembled by the source of this
1100 :class:`_engine.Result` object.
1101
1102 """
1103 self._unique_filter_state = (set(), strategy)
1104 return self
1105
1106 def columns(self, *col_expressions: _KeyIndexType) -> Self:
1107 r"""Establish the columns that should be returned in each row.
1108
1109 This method may be used to limit the columns returned as well
1110 as to reorder them. The given list of expressions are normally
1111 a series of integers or string key names. They may also be
1112 appropriate :class:`.ColumnElement` objects which correspond to
1113 a given statement construct.
1114
1115 .. versionchanged:: 2.0 Due to a bug in 1.4, the
1116 :meth:`_engine.Result.columns` method had an incorrect behavior
1117 where calling upon the method with just one index would cause the
1118 :class:`_engine.Result` object to yield scalar values rather than
1119 :class:`_engine.Row` objects. In version 2.0, this behavior
1120 has been corrected such that calling upon
1121 :meth:`_engine.Result.columns` with a single index will
1122 produce a :class:`_engine.Result` object that continues
1123 to yield :class:`_engine.Row` objects, which include
1124 only a single column.
1125
1126 E.g.::
1127
1128 statement = select(table.c.x, table.c.y, table.c.z)
1129 result = connection.execute(statement)
1130
1131 for z, y in result.columns("z", "y"):
1132 ...
1133
1134 Example of using the column objects from the statement itself::
1135
1136 for z, y in result.columns(
1137 statement.selected_columns.c.z, statement.selected_columns.c.y
1138 ):
1139 ...
1140
1141 .. versionadded:: 1.4
1142
1143 :param \*col_expressions: indicates columns to be returned. Elements
1144 may be integer row indexes, string column names, or appropriate
1145 :class:`.ColumnElement` objects corresponding to a select construct.
1146
1147 :return: this :class:`_engine.Result` object with the modifications
1148 given.
1149
1150 """
1151 return self._column_slices(col_expressions)
1152
1153 @overload
1154 def scalars(self: Result[Tuple[_T]]) -> ScalarResult[_T]: ...
1155
1156 @overload
1157 def scalars(
1158 self: Result[Tuple[_T]], index: Literal[0]
1159 ) -> ScalarResult[_T]: ...
1160
1161 @overload
1162 def scalars(self, index: _KeyIndexType = 0) -> ScalarResult[Any]: ...
1163
1164 def scalars(self, index: _KeyIndexType = 0) -> ScalarResult[Any]:
1165 """Return a :class:`_engine.ScalarResult` filtering object which
1166 will return single elements rather than :class:`_row.Row` objects.
1167
1168 E.g.::
1169
1170 >>> result = conn.execute(text("select int_id from table"))
1171 >>> result.scalars().all()
1172 [1, 2, 3]
1173
1174 When results are fetched from the :class:`_engine.ScalarResult`
1175 filtering object, the single column-row that would be returned by the
1176 :class:`_engine.Result` is instead returned as the column's value.
1177
1178 .. versionadded:: 1.4
1179
1180 :param index: integer or row key indicating the column to be fetched
1181 from each row, defaults to ``0`` indicating the first column.
1182
1183 :return: a new :class:`_engine.ScalarResult` filtering object referring
1184 to this :class:`_engine.Result` object.
1185
1186 """
1187 return ScalarResult(self, index)
1188
1189 def _getter(
1190 self, key: _KeyIndexType, raiseerr: bool = True
1191 ) -> Optional[Callable[[Row[Any]], Any]]:
1192 """return a callable that will retrieve the given key from a
1193 :class:`_engine.Row`.
1194
1195 """
1196 if self._source_supports_scalars:
1197 raise NotImplementedError(
1198 "can't use this function in 'only scalars' mode"
1199 )
1200 return self._metadata._getter(key, raiseerr)
1201
1202 def _tuple_getter(self, keys: Sequence[_KeyIndexType]) -> _TupleGetterType:
1203 """return a callable that will retrieve the given keys from a
1204 :class:`_engine.Row`.
1205
1206 """
1207 if self._source_supports_scalars:
1208 raise NotImplementedError(
1209 "can't use this function in 'only scalars' mode"
1210 )
1211 return self._metadata._row_as_tuple_getter(keys)
1212
1213 def mappings(self) -> MappingResult:
1214 """Apply a mappings filter to returned rows, returning an instance of
1215 :class:`_engine.MappingResult`.
1216
1217 When this filter is applied, fetching rows will return
1218 :class:`_engine.RowMapping` objects instead of :class:`_engine.Row`
1219 objects.
1220
1221 .. versionadded:: 1.4
1222
1223 :return: a new :class:`_engine.MappingResult` filtering object
1224 referring to this :class:`_engine.Result` object.
1225
1226 """
1227
1228 return MappingResult(self)
1229
1230 @property
1231 def t(self) -> TupleResult[_TP]:
1232 """Apply a "typed tuple" typing filter to returned rows.
1233
1234 The :attr:`_engine.Result.t` attribute is a synonym for
1235 calling the :meth:`_engine.Result.tuples` method.
1236
1237 .. versionadded:: 2.0
1238
1239 """
1240 return self # type: ignore
1241
1242 def tuples(self) -> TupleResult[_TP]:
1243 """Apply a "typed tuple" typing filter to returned rows.
1244
1245 This method returns the same :class:`_engine.Result` object
1246 at runtime,
1247 however annotates as returning a :class:`_engine.TupleResult` object
1248 that will indicate to :pep:`484` typing tools that plain typed
1249 ``Tuple`` instances are returned rather than rows. This allows
1250 tuple unpacking and ``__getitem__`` access of :class:`_engine.Row`
1251 objects to by typed, for those cases where the statement invoked
1252 itself included typing information.
1253
1254 .. versionadded:: 2.0
1255
1256 :return: the :class:`_engine.TupleResult` type at typing time.
1257
1258 .. seealso::
1259
1260 :attr:`_engine.Result.t` - shorter synonym
1261
1262 :attr:`_engine.Row._t` - :class:`_engine.Row` version
1263
1264 """
1265
1266 return self # type: ignore
1267
1268 def _raw_row_iterator(self) -> Iterator[_RowData]:
1269 """Return a safe iterator that yields raw row data.
1270
1271 This is used by the :meth:`_engine.Result.merge` method
1272 to merge multiple compatible results together.
1273
1274 """
1275 raise NotImplementedError()
1276
1277 def __iter__(self) -> Iterator[Row[_TP]]:
1278 return self._iter_impl()
1279
1280 def __next__(self) -> Row[_TP]:
1281 return self._next_impl()
1282
1283 def partitions(
1284 self, size: Optional[int] = None
1285 ) -> Iterator[Sequence[Row[_TP]]]:
1286 """Iterate through sub-lists of rows of the size given.
1287
1288 Each list will be of the size given, excluding the last list to
1289 be yielded, which may have a small number of rows. No empty
1290 lists will be yielded.
1291
1292 The result object is automatically closed when the iterator
1293 is fully consumed.
1294
1295 Note that the backend driver will usually buffer the entire result
1296 ahead of time unless the
1297 :paramref:`.Connection.execution_options.stream_results` execution
1298 option is used indicating that the driver should not pre-buffer
1299 results, if possible. Not all drivers support this option and
1300 the option is silently ignored for those who do not.
1301
1302 When using the ORM, the :meth:`_engine.Result.partitions` method
1303 is typically more effective from a memory perspective when it is
1304 combined with use of the
1305 :ref:`yield_per execution option <orm_queryguide_yield_per>`,
1306 which instructs both the DBAPI driver to use server side cursors,
1307 if available, as well as instructs the ORM loading internals to only
1308 build a certain amount of ORM objects from a result at a time before
1309 yielding them out.
1310
1311 .. versionadded:: 1.4
1312
1313 :param size: indicate the maximum number of rows to be present
1314 in each list yielded. If None, makes use of the value set by
1315 the :meth:`_engine.Result.yield_per`, method, if it were called,
1316 or the :paramref:`_engine.Connection.execution_options.yield_per`
1317 execution option, which is equivalent in this regard. If
1318 yield_per weren't set, it makes use of the
1319 :meth:`_engine.Result.fetchmany` default, which may be backend
1320 specific and not well defined.
1321
1322 :return: iterator of lists
1323
1324 .. seealso::
1325
1326 :ref:`engine_stream_results`
1327
1328 :ref:`orm_queryguide_yield_per` - in the :ref:`queryguide_toplevel`
1329
1330 """
1331
1332 getter = self._manyrow_getter
1333
1334 while True:
1335 partition = getter(self, size)
1336 if partition:
1337 yield partition
1338 else:
1339 break
1340
1341 def fetchall(self) -> Sequence[Row[_TP]]:
1342 """A synonym for the :meth:`_engine.Result.all` method."""
1343
1344 return self._allrows()
1345
1346 def fetchone(self) -> Optional[Row[_TP]]:
1347 """Fetch one row.
1348
1349 When all rows are exhausted, returns None.
1350
1351 This method is provided for backwards compatibility with
1352 SQLAlchemy 1.x.x.
1353
1354 To fetch the first row of a result only, use the
1355 :meth:`_engine.Result.first` method. To iterate through all
1356 rows, iterate the :class:`_engine.Result` object directly.
1357
1358 :return: a :class:`_engine.Row` object if no filters are applied,
1359 or ``None`` if no rows remain.
1360
1361 """
1362 row = self._onerow_getter(self)
1363 if row is _NO_ROW:
1364 return None
1365 else:
1366 return row
1367
1368 def fetchmany(self, size: Optional[int] = None) -> Sequence[Row[_TP]]:
1369 """Fetch many rows.
1370
1371 When all rows are exhausted, returns an empty sequence.
1372
1373 This method is provided for backwards compatibility with
1374 SQLAlchemy 1.x.x.
1375
1376 To fetch rows in groups, use the :meth:`_engine.Result.partitions`
1377 method.
1378
1379 :return: a sequence of :class:`_engine.Row` objects.
1380
1381 .. seealso::
1382
1383 :meth:`_engine.Result.partitions`
1384
1385 """
1386
1387 return self._manyrow_getter(self, size)
1388
1389 def all(self) -> Sequence[Row[_TP]]:
1390 """Return all rows in a sequence.
1391
1392 Closes the result set after invocation. Subsequent invocations
1393 will return an empty sequence.
1394
1395 .. versionadded:: 1.4
1396
1397 :return: a sequence of :class:`_engine.Row` objects.
1398
1399 .. seealso::
1400
1401 :ref:`engine_stream_results` - How to stream a large result set
1402 without loading it completely in python.
1403
1404 """
1405
1406 return self._allrows()
1407
1408 def first(self) -> Optional[Row[_TP]]:
1409 """Fetch the first row or ``None`` if no row is present.
1410
1411 Closes the result set and discards remaining rows.
1412
1413 .. note:: This method returns one **row**, e.g. tuple, by default.
1414 To return exactly one single scalar value, that is, the first
1415 column of the first row, use the
1416 :meth:`_engine.Result.scalar` method,
1417 or combine :meth:`_engine.Result.scalars` and
1418 :meth:`_engine.Result.first`.
1419
1420 Additionally, in contrast to the behavior of the legacy ORM
1421 :meth:`_orm.Query.first` method, **no limit is applied** to the
1422 SQL query which was invoked to produce this
1423 :class:`_engine.Result`;
1424 for a DBAPI driver that buffers results in memory before yielding
1425 rows, all rows will be sent to the Python process and all but
1426 the first row will be discarded.
1427
1428 .. seealso::
1429
1430 :ref:`migration_20_unify_select`
1431
1432 :return: a :class:`_engine.Row` object, or None
1433 if no rows remain.
1434
1435 .. seealso::
1436
1437 :meth:`_engine.Result.scalar`
1438
1439 :meth:`_engine.Result.one`
1440
1441 """
1442
1443 return self._only_one_row(
1444 raise_for_second_row=False, raise_for_none=False, scalar=False
1445 )
1446
1447 def one_or_none(self) -> Optional[Row[_TP]]:
1448 """Return at most one result or raise an exception.
1449
1450 Returns ``None`` if the result has no rows.
1451 Raises :class:`.MultipleResultsFound`
1452 if multiple rows are returned.
1453
1454 .. versionadded:: 1.4
1455
1456 :return: The first :class:`_engine.Row` or ``None`` if no row
1457 is available.
1458
1459 :raises: :class:`.MultipleResultsFound`
1460
1461 .. seealso::
1462
1463 :meth:`_engine.Result.first`
1464
1465 :meth:`_engine.Result.one`
1466
1467 """
1468 return self._only_one_row(
1469 raise_for_second_row=True, raise_for_none=False, scalar=False
1470 )
1471
1472 @overload
1473 def scalar_one(self: Result[Tuple[_T]]) -> _T: ...
1474
1475 @overload
1476 def scalar_one(self) -> Any: ...
1477
1478 def scalar_one(self) -> Any:
1479 """Return exactly one scalar result or raise an exception.
1480
1481 This is equivalent to calling :meth:`_engine.Result.scalars` and
1482 then :meth:`_engine.ScalarResult.one`.
1483
1484 .. seealso::
1485
1486 :meth:`_engine.ScalarResult.one`
1487
1488 :meth:`_engine.Result.scalars`
1489
1490 """
1491 return self._only_one_row(
1492 raise_for_second_row=True, raise_for_none=True, scalar=True
1493 )
1494
1495 @overload
1496 def scalar_one_or_none(self: Result[Tuple[_T]]) -> Optional[_T]: ...
1497
1498 @overload
1499 def scalar_one_or_none(self) -> Optional[Any]: ...
1500
1501 def scalar_one_or_none(self) -> Optional[Any]:
1502 """Return exactly one scalar result or ``None``.
1503
1504 This is equivalent to calling :meth:`_engine.Result.scalars` and
1505 then :meth:`_engine.ScalarResult.one_or_none`.
1506
1507 .. seealso::
1508
1509 :meth:`_engine.ScalarResult.one_or_none`
1510
1511 :meth:`_engine.Result.scalars`
1512
1513 """
1514 return self._only_one_row(
1515 raise_for_second_row=True, raise_for_none=False, scalar=True
1516 )
1517
1518 def one(self) -> Row[_TP]:
1519 """Return exactly one row or raise an exception.
1520
1521 Raises :class:`_exc.NoResultFound` if the result returns no
1522 rows, or :class:`_exc.MultipleResultsFound` if multiple rows
1523 would be returned.
1524
1525 .. note:: This method returns one **row**, e.g. tuple, by default.
1526 To return exactly one single scalar value, that is, the first
1527 column of the first row, use the
1528 :meth:`_engine.Result.scalar_one` method, or combine
1529 :meth:`_engine.Result.scalars` and
1530 :meth:`_engine.Result.one`.
1531
1532 .. versionadded:: 1.4
1533
1534 :return: The first :class:`_engine.Row`.
1535
1536 :raises: :class:`.MultipleResultsFound`, :class:`.NoResultFound`
1537
1538 .. seealso::
1539
1540 :meth:`_engine.Result.first`
1541
1542 :meth:`_engine.Result.one_or_none`
1543
1544 :meth:`_engine.Result.scalar_one`
1545
1546 """
1547 return self._only_one_row(
1548 raise_for_second_row=True, raise_for_none=True, scalar=False
1549 )
1550
1551 @overload
1552 def scalar(self: Result[Tuple[_T]]) -> Optional[_T]: ...
1553
1554 @overload
1555 def scalar(self) -> Any: ...
1556
1557 def scalar(self) -> Any:
1558 """Fetch the first column of the first row, and close the result set.
1559
1560 Returns ``None`` if there are no rows to fetch.
1561
1562 No validation is performed to test if additional rows remain.
1563
1564 After calling this method, the object is fully closed,
1565 e.g. the :meth:`_engine.CursorResult.close`
1566 method will have been called.
1567
1568 :return: a Python scalar value, or ``None`` if no rows remain.
1569
1570 """
1571 return self._only_one_row(
1572 raise_for_second_row=False, raise_for_none=False, scalar=True
1573 )
1574
1575 def freeze(self) -> FrozenResult[_TP]:
1576 """Return a callable object that will produce copies of this
1577 :class:`_engine.Result` when invoked.
1578
1579 The callable object returned is an instance of
1580 :class:`_engine.FrozenResult`.
1581
1582 This is used for result set caching. The method must be called
1583 on the result when it has been unconsumed, and calling the method
1584 will consume the result fully. When the :class:`_engine.FrozenResult`
1585 is retrieved from a cache, it can be called any number of times where
1586 it will produce a new :class:`_engine.Result` object each time
1587 against its stored set of rows.
1588
1589 .. seealso::
1590
1591 :ref:`do_orm_execute_re_executing` - example usage within the
1592 ORM to implement a result-set cache.
1593
1594 """
1595
1596 return FrozenResult(self)
1597
1598 def merge(self, *others: Result[Any]) -> MergedResult[_TP]:
1599 """Merge this :class:`_engine.Result` with other compatible result
1600 objects.
1601
1602 The object returned is an instance of :class:`_engine.MergedResult`,
1603 which will be composed of iterators from the given result
1604 objects.
1605
1606 The new result will use the metadata from this result object.
1607 The subsequent result objects must be against an identical
1608 set of result / cursor metadata, otherwise the behavior is
1609 undefined.
1610
1611 """
1612 return MergedResult(self._metadata, (self,) + others)
1613
1614
1615class FilterResult(ResultInternal[_R]):
1616 """A wrapper for a :class:`_engine.Result` that returns objects other than
1617 :class:`_engine.Row` objects, such as dictionaries or scalar objects.
1618
1619 :class:`_engine.FilterResult` is the common base for additional result
1620 APIs including :class:`_engine.MappingResult`,
1621 :class:`_engine.ScalarResult` and :class:`_engine.AsyncResult`.
1622
1623 """
1624
1625 __slots__ = (
1626 "_real_result",
1627 "_post_creational_filter",
1628 "_metadata",
1629 "_unique_filter_state",
1630 "__dict__",
1631 )
1632
1633 _post_creational_filter: Optional[Callable[[Any], Any]]
1634
1635 _real_result: Result[Any]
1636
1637 def __enter__(self) -> Self:
1638 return self
1639
1640 def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
1641 self._real_result.__exit__(type_, value, traceback)
1642
1643 @_generative
1644 def yield_per(self, num: int) -> Self:
1645 """Configure the row-fetching strategy to fetch ``num`` rows at a time.
1646
1647 The :meth:`_engine.FilterResult.yield_per` method is a pass through
1648 to the :meth:`_engine.Result.yield_per` method. See that method's
1649 documentation for usage notes.
1650
1651 .. versionadded:: 1.4.40 - added :meth:`_engine.FilterResult.yield_per`
1652 so that the method is available on all result set implementations
1653
1654 .. seealso::
1655
1656 :ref:`engine_stream_results` - describes Core behavior for
1657 :meth:`_engine.Result.yield_per`
1658
1659 :ref:`orm_queryguide_yield_per` - in the :ref:`queryguide_toplevel`
1660
1661 """
1662 self._real_result = self._real_result.yield_per(num)
1663 return self
1664
1665 def _soft_close(self, hard: bool = False) -> None:
1666 self._real_result._soft_close(hard=hard)
1667
1668 @property
1669 def _soft_closed(self) -> bool:
1670 return self._real_result._soft_closed
1671
1672 @property
1673 def closed(self) -> bool:
1674 """Return ``True`` if this :class:`_engine.Result` being
1675 proxied by this :class:`_engine.FilterResult` was
1676 **hard closed** by explicitly calling the :meth:`_engine.Result.close`
1677 method.
1678
1679 This is a direct proxy for the :attr:`_engine.Result.closed` attribute;
1680 see that attribute for details.
1681
1682 """
1683 return self._real_result.closed
1684
1685 def close(self) -> None:
1686 """Close this :class:`_engine.FilterResult`.
1687
1688 .. versionadded:: 1.4.43
1689
1690 """
1691 self._real_result.close()
1692
1693 @property
1694 def _attributes(self) -> Dict[Any, Any]:
1695 return self._real_result._attributes
1696
1697 def _fetchiter_impl(self) -> Iterator[_InterimRowType[Row[Any]]]:
1698 return self._real_result._fetchiter_impl()
1699
1700 def _fetchone_impl(
1701 self, hard_close: bool = False
1702 ) -> Optional[_InterimRowType[Row[Any]]]:
1703 return self._real_result._fetchone_impl(hard_close=hard_close)
1704
1705 def _fetchall_impl(self) -> List[_InterimRowType[Row[Any]]]:
1706 return self._real_result._fetchall_impl()
1707
1708 def _fetchmany_impl(
1709 self, size: Optional[int] = None
1710 ) -> List[_InterimRowType[Row[Any]]]:
1711 return self._real_result._fetchmany_impl(size=size)
1712
1713
1714class ScalarResult(FilterResult[_R]):
1715 """A wrapper for a :class:`_engine.Result` that returns scalar values
1716 rather than :class:`_row.Row` values.
1717
1718 The :class:`_engine.ScalarResult` object is acquired by calling the
1719 :meth:`_engine.Result.scalars` method.
1720
1721 A special limitation of :class:`_engine.ScalarResult` is that it has
1722 no ``fetchone()`` method; since the semantics of ``fetchone()`` are that
1723 the ``None`` value indicates no more results, this is not compatible
1724 with :class:`_engine.ScalarResult` since there is no way to distinguish
1725 between ``None`` as a row value versus ``None`` as an indicator. Use
1726 ``next(result)`` to receive values individually.
1727
1728 """
1729
1730 __slots__ = ()
1731
1732 _generate_rows = False
1733
1734 _post_creational_filter: Optional[Callable[[Any], Any]]
1735
1736 def __init__(self, real_result: Result[Any], index: _KeyIndexType):
1737 self._real_result = real_result
1738
1739 if real_result._source_supports_scalars:
1740 self._metadata = real_result._metadata
1741 self._post_creational_filter = None
1742 else:
1743 self._metadata = real_result._metadata._reduce([index])
1744 self._post_creational_filter = operator.itemgetter(0)
1745
1746 self._unique_filter_state = real_result._unique_filter_state
1747
1748 def unique(self, strategy: Optional[_UniqueFilterType] = None) -> Self:
1749 """Apply unique filtering to the objects returned by this
1750 :class:`_engine.ScalarResult`.
1751
1752 See :meth:`_engine.Result.unique` for usage details.
1753
1754 """
1755 self._unique_filter_state = (set(), strategy)
1756 return self
1757
1758 def partitions(self, size: Optional[int] = None) -> Iterator[Sequence[_R]]:
1759 """Iterate through sub-lists of elements of the size given.
1760
1761 Equivalent to :meth:`_engine.Result.partitions` except that
1762 scalar values, rather than :class:`_engine.Row` objects,
1763 are returned.
1764
1765 """
1766
1767 getter = self._manyrow_getter
1768
1769 while True:
1770 partition = getter(self, size)
1771 if partition:
1772 yield partition
1773 else:
1774 break
1775
1776 def fetchall(self) -> Sequence[_R]:
1777 """A synonym for the :meth:`_engine.ScalarResult.all` method."""
1778
1779 return self._allrows()
1780
1781 def fetchmany(self, size: Optional[int] = None) -> Sequence[_R]:
1782 """Fetch many objects.
1783
1784 Equivalent to :meth:`_engine.Result.fetchmany` except that
1785 scalar values, rather than :class:`_engine.Row` objects,
1786 are returned.
1787
1788 """
1789 return self._manyrow_getter(self, size)
1790
1791 def all(self) -> Sequence[_R]:
1792 """Return all scalar values in a sequence.
1793
1794 Equivalent to :meth:`_engine.Result.all` except that
1795 scalar values, rather than :class:`_engine.Row` objects,
1796 are returned.
1797
1798 """
1799 return self._allrows()
1800
1801 def __iter__(self) -> Iterator[_R]:
1802 return self._iter_impl()
1803
1804 def __next__(self) -> _R:
1805 return self._next_impl()
1806
1807 def first(self) -> Optional[_R]:
1808 """Fetch the first object or ``None`` if no object is present.
1809
1810 Equivalent to :meth:`_engine.Result.first` except that
1811 scalar values, rather than :class:`_engine.Row` objects,
1812 are returned.
1813
1814
1815 """
1816 return self._only_one_row(
1817 raise_for_second_row=False, raise_for_none=False, scalar=False
1818 )
1819
1820 def one_or_none(self) -> Optional[_R]:
1821 """Return at most one object or raise an exception.
1822
1823 Equivalent to :meth:`_engine.Result.one_or_none` except that
1824 scalar values, rather than :class:`_engine.Row` objects,
1825 are returned.
1826
1827 """
1828 return self._only_one_row(
1829 raise_for_second_row=True, raise_for_none=False, scalar=False
1830 )
1831
1832 def one(self) -> _R:
1833 """Return exactly one object or raise an exception.
1834
1835 Equivalent to :meth:`_engine.Result.one` except that
1836 scalar values, rather than :class:`_engine.Row` objects,
1837 are returned.
1838
1839 """
1840 return self._only_one_row(
1841 raise_for_second_row=True, raise_for_none=True, scalar=False
1842 )
1843
1844
1845class TupleResult(FilterResult[_R], util.TypingOnly):
1846 """A :class:`_engine.Result` that's typed as returning plain
1847 Python tuples instead of rows.
1848
1849 Since :class:`_engine.Row` acts like a tuple in every way already,
1850 this class is a typing only class, regular :class:`_engine.Result` is
1851 still used at runtime.
1852
1853 """
1854
1855 __slots__ = ()
1856
1857 if TYPE_CHECKING:
1858
1859 def partitions(
1860 self, size: Optional[int] = None
1861 ) -> Iterator[Sequence[_R]]:
1862 """Iterate through sub-lists of elements of the size given.
1863
1864 Equivalent to :meth:`_engine.Result.partitions` except that
1865 tuple values, rather than :class:`_engine.Row` objects,
1866 are returned.
1867
1868 """
1869 ...
1870
1871 def fetchone(self) -> Optional[_R]:
1872 """Fetch one tuple.
1873
1874 Equivalent to :meth:`_engine.Result.fetchone` except that
1875 tuple values, rather than :class:`_engine.Row`
1876 objects, are returned.
1877
1878 """
1879 ...
1880
1881 def fetchall(self) -> Sequence[_R]:
1882 """A synonym for the :meth:`_engine.ScalarResult.all` method."""
1883 ...
1884
1885 def fetchmany(self, size: Optional[int] = None) -> Sequence[_R]:
1886 """Fetch many objects.
1887
1888 Equivalent to :meth:`_engine.Result.fetchmany` except that
1889 tuple values, rather than :class:`_engine.Row` objects,
1890 are returned.
1891
1892 """
1893 ...
1894
1895 def all(self) -> Sequence[_R]: # noqa: A001
1896 """Return all scalar values in a sequence.
1897
1898 Equivalent to :meth:`_engine.Result.all` except that
1899 tuple values, rather than :class:`_engine.Row` objects,
1900 are returned.
1901
1902 """
1903 ...
1904
1905 def __iter__(self) -> Iterator[_R]: ...
1906
1907 def __next__(self) -> _R: ...
1908
1909 def first(self) -> Optional[_R]:
1910 """Fetch the first object or ``None`` if no object is present.
1911
1912 Equivalent to :meth:`_engine.Result.first` except that
1913 tuple values, rather than :class:`_engine.Row` objects,
1914 are returned.
1915
1916
1917 """
1918 ...
1919
1920 def one_or_none(self) -> Optional[_R]:
1921 """Return at most one object or raise an exception.
1922
1923 Equivalent to :meth:`_engine.Result.one_or_none` except that
1924 tuple values, rather than :class:`_engine.Row` objects,
1925 are returned.
1926
1927 """
1928 ...
1929
1930 def one(self) -> _R:
1931 """Return exactly one object or raise an exception.
1932
1933 Equivalent to :meth:`_engine.Result.one` except that
1934 tuple values, rather than :class:`_engine.Row` objects,
1935 are returned.
1936
1937 """
1938 ...
1939
1940 @overload
1941 def scalar_one(self: TupleResult[Tuple[_T]]) -> _T: ...
1942
1943 @overload
1944 def scalar_one(self) -> Any: ...
1945
1946 def scalar_one(self) -> Any:
1947 """Return exactly one scalar result or raise an exception.
1948
1949 This is equivalent to calling :meth:`_engine.Result.scalars`
1950 and then :meth:`_engine.ScalarResult.one`.
1951
1952 .. seealso::
1953
1954 :meth:`_engine.ScalarResult.one`
1955
1956 :meth:`_engine.Result.scalars`
1957
1958 """
1959 ...
1960
1961 @overload
1962 def scalar_one_or_none(
1963 self: TupleResult[Tuple[_T]],
1964 ) -> Optional[_T]: ...
1965
1966 @overload
1967 def scalar_one_or_none(self) -> Optional[Any]: ...
1968
1969 def scalar_one_or_none(self) -> Optional[Any]:
1970 """Return exactly one or no scalar result.
1971
1972 This is equivalent to calling :meth:`_engine.Result.scalars`
1973 and then :meth:`_engine.ScalarResult.one_or_none`.
1974
1975 .. seealso::
1976
1977 :meth:`_engine.ScalarResult.one_or_none`
1978
1979 :meth:`_engine.Result.scalars`
1980
1981 """
1982 ...
1983
1984 @overload
1985 def scalar(self: TupleResult[Tuple[_T]]) -> Optional[_T]: ...
1986
1987 @overload
1988 def scalar(self) -> Any: ...
1989
1990 def scalar(self) -> Any:
1991 """Fetch the first column of the first row, and close the result
1992 set.
1993
1994 Returns ``None`` if there are no rows to fetch.
1995
1996 No validation is performed to test if additional rows remain.
1997
1998 After calling this method, the object is fully closed,
1999 e.g. the :meth:`_engine.CursorResult.close`
2000 method will have been called.
2001
2002 :return: a Python scalar value , or ``None`` if no rows remain.
2003
2004 """
2005 ...
2006
2007
2008class MappingResult(_WithKeys, FilterResult[RowMapping]):
2009 """A wrapper for a :class:`_engine.Result` that returns dictionary values
2010 rather than :class:`_engine.Row` values.
2011
2012 The :class:`_engine.MappingResult` object is acquired by calling the
2013 :meth:`_engine.Result.mappings` method.
2014
2015 """
2016
2017 __slots__ = ()
2018
2019 _generate_rows = True
2020
2021 _post_creational_filter = operator.attrgetter("_mapping")
2022
2023 def __init__(self, result: Result[Any]):
2024 self._real_result = result
2025 self._unique_filter_state = result._unique_filter_state
2026 self._metadata = result._metadata
2027 if result._source_supports_scalars:
2028 self._metadata = self._metadata._reduce([0])
2029
2030 def unique(self, strategy: Optional[_UniqueFilterType] = None) -> Self:
2031 """Apply unique filtering to the objects returned by this
2032 :class:`_engine.MappingResult`.
2033
2034 See :meth:`_engine.Result.unique` for usage details.
2035
2036 """
2037 self._unique_filter_state = (set(), strategy)
2038 return self
2039
2040 def columns(self, *col_expressions: _KeyIndexType) -> Self:
2041 """Establish the columns that should be returned in each row."""
2042 return self._column_slices(col_expressions)
2043
2044 def partitions(
2045 self, size: Optional[int] = None
2046 ) -> Iterator[Sequence[RowMapping]]:
2047 """Iterate through sub-lists of elements of the size given.
2048
2049 Equivalent to :meth:`_engine.Result.partitions` except that
2050 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
2051 objects, are returned.
2052
2053 """
2054
2055 getter = self._manyrow_getter
2056
2057 while True:
2058 partition = getter(self, size)
2059 if partition:
2060 yield partition
2061 else:
2062 break
2063
2064 def fetchall(self) -> Sequence[RowMapping]:
2065 """A synonym for the :meth:`_engine.MappingResult.all` method."""
2066
2067 return self._allrows()
2068
2069 def fetchone(self) -> Optional[RowMapping]:
2070 """Fetch one object.
2071
2072 Equivalent to :meth:`_engine.Result.fetchone` except that
2073 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
2074 objects, are returned.
2075
2076 """
2077
2078 row = self._onerow_getter(self)
2079 if row is _NO_ROW:
2080 return None
2081 else:
2082 return row
2083
2084 def fetchmany(self, size: Optional[int] = None) -> Sequence[RowMapping]:
2085 """Fetch many objects.
2086
2087 Equivalent to :meth:`_engine.Result.fetchmany` except that
2088 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
2089 objects, are returned.
2090
2091 """
2092
2093 return self._manyrow_getter(self, size)
2094
2095 def all(self) -> Sequence[RowMapping]:
2096 """Return all scalar values in a sequence.
2097
2098 Equivalent to :meth:`_engine.Result.all` except that
2099 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
2100 objects, are returned.
2101
2102 """
2103
2104 return self._allrows()
2105
2106 def __iter__(self) -> Iterator[RowMapping]:
2107 return self._iter_impl()
2108
2109 def __next__(self) -> RowMapping:
2110 return self._next_impl()
2111
2112 def first(self) -> Optional[RowMapping]:
2113 """Fetch the first object or ``None`` if no object is present.
2114
2115 Equivalent to :meth:`_engine.Result.first` except that
2116 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
2117 objects, are returned.
2118
2119
2120 """
2121 return self._only_one_row(
2122 raise_for_second_row=False, raise_for_none=False, scalar=False
2123 )
2124
2125 def one_or_none(self) -> Optional[RowMapping]:
2126 """Return at most one object or raise an exception.
2127
2128 Equivalent to :meth:`_engine.Result.one_or_none` except that
2129 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
2130 objects, are returned.
2131
2132 """
2133 return self._only_one_row(
2134 raise_for_second_row=True, raise_for_none=False, scalar=False
2135 )
2136
2137 def one(self) -> RowMapping:
2138 """Return exactly one object or raise an exception.
2139
2140 Equivalent to :meth:`_engine.Result.one` except that
2141 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
2142 objects, are returned.
2143
2144 """
2145 return self._only_one_row(
2146 raise_for_second_row=True, raise_for_none=True, scalar=False
2147 )
2148
2149
2150class FrozenResult(Generic[_TP]):
2151 """Represents a :class:`_engine.Result` object in a "frozen" state suitable
2152 for caching.
2153
2154 The :class:`_engine.FrozenResult` object is returned from the
2155 :meth:`_engine.Result.freeze` method of any :class:`_engine.Result`
2156 object.
2157
2158 A new iterable :class:`_engine.Result` object is generated from a fixed
2159 set of data each time the :class:`_engine.FrozenResult` is invoked as
2160 a callable::
2161
2162
2163 result = connection.execute(query)
2164
2165 frozen = result.freeze()
2166
2167 unfrozen_result_one = frozen()
2168
2169 for row in unfrozen_result_one:
2170 print(row)
2171
2172 unfrozen_result_two = frozen()
2173 rows = unfrozen_result_two.all()
2174
2175 # ... etc
2176
2177 .. versionadded:: 1.4
2178
2179 .. seealso::
2180
2181 :ref:`do_orm_execute_re_executing` - example usage within the
2182 ORM to implement a result-set cache.
2183
2184 :func:`_orm.loading.merge_frozen_result` - ORM function to merge
2185 a frozen result back into a :class:`_orm.Session`.
2186
2187 """
2188
2189 data: Sequence[Any]
2190
2191 def __init__(self, result: Result[_TP]):
2192 self.metadata = result._metadata._for_freeze()
2193 self._source_supports_scalars = result._source_supports_scalars
2194 self._attributes = result._attributes
2195
2196 if self._source_supports_scalars:
2197 self.data = list(result._raw_row_iterator())
2198 else:
2199 self.data = result.fetchall()
2200
2201 def rewrite_rows(self) -> Sequence[Sequence[Any]]:
2202 if self._source_supports_scalars:
2203 return [[elem] for elem in self.data]
2204 else:
2205 return [list(row) for row in self.data]
2206
2207 def with_new_rows(
2208 self, tuple_data: Sequence[Row[_TP]]
2209 ) -> FrozenResult[_TP]:
2210 fr = FrozenResult.__new__(FrozenResult)
2211 fr.metadata = self.metadata
2212 fr._attributes = self._attributes
2213 fr._source_supports_scalars = self._source_supports_scalars
2214
2215 if self._source_supports_scalars:
2216 fr.data = [d[0] for d in tuple_data]
2217 else:
2218 fr.data = tuple_data
2219 return fr
2220
2221 def __call__(self) -> Result[_TP]:
2222 result: IteratorResult[_TP] = IteratorResult(
2223 self.metadata, iter(self.data)
2224 )
2225 result._attributes = self._attributes
2226 result._source_supports_scalars = self._source_supports_scalars
2227 return result
2228
2229
2230class IteratorResult(Result[_TP]):
2231 """A :class:`_engine.Result` that gets data from a Python iterator of
2232 :class:`_engine.Row` objects or similar row-like data.
2233
2234 .. versionadded:: 1.4
2235
2236 """
2237
2238 _hard_closed = False
2239 _soft_closed = False
2240
2241 def __init__(
2242 self,
2243 cursor_metadata: ResultMetaData,
2244 iterator: Iterator[_InterimSupportsScalarsRowType],
2245 raw: Optional[Result[Any]] = None,
2246 _source_supports_scalars: bool = False,
2247 ):
2248 self._metadata = cursor_metadata
2249 self.iterator = iterator
2250 self.raw = raw
2251 self._source_supports_scalars = _source_supports_scalars
2252
2253 @property
2254 def closed(self) -> bool:
2255 """Return ``True`` if this :class:`_engine.IteratorResult` was
2256 **hard closed** by explicitly calling the :meth:`_engine.Result.close`
2257 method.
2258
2259 The attribute is **not** True if the :class:`_engine.Result` was only
2260 **soft closed**; a "soft close" is the style of close that takes place
2261 for example when the :class:`.CursorResult` is returned for a DML
2262 only statement without RETURNING, or when all result rows are fetched.
2263
2264 .. seealso::
2265
2266 :attr:`.CursorResult.returns_rows` - attribute specific to
2267 :class:`.CursorResult` which indicates if the result is one that
2268 may return zero or more rows
2269
2270 """
2271 return self._hard_closed
2272
2273 def _soft_close(self, hard: bool = False, **kw: Any) -> None:
2274 if hard:
2275 self._hard_closed = True
2276 if self.raw is not None:
2277 self.raw._soft_close(hard=hard, **kw)
2278 self.iterator = iter([])
2279 self._reset_memoizations()
2280 self._soft_closed = True
2281
2282 def _raise_hard_closed(self) -> NoReturn:
2283 raise exc.ResourceClosedError("This result object is closed.")
2284
2285 def _raw_row_iterator(self) -> Iterator[_RowData]:
2286 return self.iterator
2287
2288 def _fetchiter_impl(self) -> Iterator[_InterimSupportsScalarsRowType]:
2289 if self._hard_closed:
2290 self._raise_hard_closed()
2291 return self.iterator
2292
2293 def _fetchone_impl(
2294 self, hard_close: bool = False
2295 ) -> Optional[_InterimRowType[Row[Any]]]:
2296 if self._hard_closed:
2297 self._raise_hard_closed()
2298
2299 row = next(self.iterator, _NO_ROW)
2300 if row is _NO_ROW:
2301 self._soft_close(hard=hard_close)
2302 return None
2303 else:
2304 return row
2305
2306 def _fetchall_impl(self) -> List[_InterimRowType[Row[Any]]]:
2307 if self._hard_closed:
2308 self._raise_hard_closed()
2309 try:
2310 return list(self.iterator)
2311 finally:
2312 self._soft_close()
2313
2314 def _fetchmany_impl(
2315 self, size: Optional[int] = None
2316 ) -> List[_InterimRowType[Row[Any]]]:
2317 if self._hard_closed:
2318 self._raise_hard_closed()
2319
2320 return list(itertools.islice(self.iterator, 0, size))
2321
2322
2323def null_result() -> IteratorResult[Any]:
2324 return IteratorResult(SimpleResultMetaData([]), iter([]))
2325
2326
2327class ChunkedIteratorResult(IteratorResult[_TP]):
2328 """An :class:`_engine.IteratorResult` that works from an
2329 iterator-producing callable.
2330
2331 The given ``chunks`` argument is a function that is given a number of rows
2332 to return in each chunk, or ``None`` for all rows. The function should
2333 then return an un-consumed iterator of lists, each list of the requested
2334 size.
2335
2336 The function can be called at any time again, in which case it should
2337 continue from the same result set but adjust the chunk size as given.
2338
2339 .. versionadded:: 1.4
2340
2341 """
2342
2343 def __init__(
2344 self,
2345 cursor_metadata: ResultMetaData,
2346 chunks: Callable[
2347 [Optional[int]], Iterator[Sequence[_InterimRowType[_R]]]
2348 ],
2349 source_supports_scalars: bool = False,
2350 raw: Optional[Result[Any]] = None,
2351 dynamic_yield_per: bool = False,
2352 ):
2353 self._metadata = cursor_metadata
2354 self.chunks = chunks
2355 self._source_supports_scalars = source_supports_scalars
2356 self.raw = raw
2357 self.iterator = itertools.chain.from_iterable(self.chunks(None))
2358 self.dynamic_yield_per = dynamic_yield_per
2359
2360 @_generative
2361 def yield_per(self, num: int) -> Self:
2362 # TODO: this throws away the iterator which may be holding
2363 # onto a chunk. the yield_per cannot be changed once any
2364 # rows have been fetched. either find a way to enforce this,
2365 # or we can't use itertools.chain and will instead have to
2366 # keep track.
2367
2368 self._yield_per = num
2369 self.iterator = itertools.chain.from_iterable(self.chunks(num))
2370 return self
2371
2372 def _soft_close(self, hard: bool = False, **kw: Any) -> None:
2373 super()._soft_close(hard=hard, **kw)
2374 self.chunks = lambda size: [] # type: ignore
2375
2376 def _fetchmany_impl(
2377 self, size: Optional[int] = None
2378 ) -> List[_InterimRowType[Row[Any]]]:
2379 if self.dynamic_yield_per:
2380 self.iterator = itertools.chain.from_iterable(self.chunks(size))
2381 return super()._fetchmany_impl(size=size)
2382
2383
2384class MergedResult(IteratorResult[_TP]):
2385 """A :class:`_engine.Result` that is merged from any number of
2386 :class:`_engine.Result` objects.
2387
2388 Returned by the :meth:`_engine.Result.merge` method.
2389
2390 .. versionadded:: 1.4
2391
2392 """
2393
2394 closed = False
2395 rowcount: Optional[int]
2396
2397 def __init__(
2398 self, cursor_metadata: ResultMetaData, results: Sequence[Result[_TP]]
2399 ):
2400 self._results = results
2401 super().__init__(
2402 cursor_metadata,
2403 itertools.chain.from_iterable(
2404 r._raw_row_iterator() for r in results
2405 ),
2406 )
2407
2408 self._unique_filter_state = results[0]._unique_filter_state
2409 self._yield_per = results[0]._yield_per
2410
2411 # going to try something w/ this in next rev
2412 self._source_supports_scalars = results[0]._source_supports_scalars
2413
2414 self._attributes = self._attributes.merge_with(
2415 *[r._attributes for r in results]
2416 )
2417
2418 def _soft_close(self, hard: bool = False, **kw: Any) -> None:
2419 for r in self._results:
2420 r._soft_close(hard=hard, **kw)
2421 if hard:
2422 self.closed = True