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