12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787 |
- # engine/result.py
- # Copyright (C) 2005-2022 the SQLAlchemy authors and contributors
- # <see AUTHORS file>
- #
- # This module is part of SQLAlchemy and is released under
- # the MIT License: https://www.opensource.org/licenses/mit-license.php
- """Define generic result set constructs."""
- import functools
- import itertools
- import operator
- from .row import _baserow_usecext
- from .row import Row
- from .. import exc
- from .. import util
- from ..sql.base import _generative
- from ..sql.base import HasMemoized
- from ..sql.base import InPlaceGenerative
- from ..util import collections_abc
- from ..util import py2k
- if _baserow_usecext:
- from sqlalchemy.cresultproxy import tuplegetter
- _row_as_tuple = tuplegetter
- else:
- def tuplegetter(*indexes):
- it = operator.itemgetter(*indexes)
- if len(indexes) > 1:
- return it
- else:
- return lambda row: (it(row),)
- def _row_as_tuple(*indexes):
- # circumvent LegacyRow.__getitem__ pointing to
- # _get_by_key_impl_mapping for now. otherwise we could
- # use itemgetter
- getters = [
- operator.methodcaller("_get_by_int_impl", index)
- for index in indexes
- ]
- return lambda rec: tuple([getter(rec) for getter in getters])
- class ResultMetaData(object):
- """Base for metadata about result rows."""
- __slots__ = ()
- _tuplefilter = None
- _translated_indexes = None
- _unique_filters = None
- @property
- def keys(self):
- return RMKeyView(self)
- def _has_key(self, key):
- raise NotImplementedError()
- def _for_freeze(self):
- raise NotImplementedError()
- def _key_fallback(self, key, err, raiseerr=True):
- assert raiseerr
- util.raise_(KeyError(key), replace_context=err)
- def _warn_for_nonint(self, key):
- util.warn_deprecated_20(
- "Retrieving row members using strings or other non-integers is "
- "deprecated; use row._mapping for a dictionary interface "
- "to the row"
- )
- def _raise_for_nonint(self, key):
- raise TypeError(
- "TypeError: tuple indices must be integers or slices, not %s"
- % type(key).__name__
- )
- def _index_for_key(self, keys, raiseerr):
- raise NotImplementedError()
- def _metadata_for_keys(self, key):
- raise NotImplementedError()
- def _reduce(self, keys):
- raise NotImplementedError()
- def _getter(self, key, raiseerr=True):
- index = self._index_for_key(key, raiseerr)
- if index is not None:
- return operator.itemgetter(index)
- else:
- return None
- def _row_as_tuple_getter(self, keys):
- indexes = self._indexes_for_keys(keys)
- return _row_as_tuple(*indexes)
- class RMKeyView(collections_abc.KeysView):
- __slots__ = ("_parent", "_keys")
- def __init__(self, parent):
- self._parent = parent
- self._keys = [k for k in parent._keys if k is not None]
- def __len__(self):
- return len(self._keys)
- def __repr__(self):
- return "{0.__class__.__name__}({0._keys!r})".format(self)
- def __iter__(self):
- return iter(self._keys)
- def __contains__(self, item):
- if not _baserow_usecext and isinstance(item, int):
- return False
- # note this also includes special key fallback behaviors
- # which also don't seem to be tested in test_resultset right now
- return self._parent._has_key(item)
- def __eq__(self, other):
- return list(other) == list(self)
- def __ne__(self, other):
- return list(other) != list(self)
- class SimpleResultMetaData(ResultMetaData):
- """result metadata for in-memory collections."""
- __slots__ = (
- "_keys",
- "_keymap",
- "_processors",
- "_tuplefilter",
- "_translated_indexes",
- "_unique_filters",
- )
- def __init__(
- self,
- keys,
- extra=None,
- _processors=None,
- _tuplefilter=None,
- _translated_indexes=None,
- _unique_filters=None,
- ):
- self._keys = list(keys)
- self._tuplefilter = _tuplefilter
- self._translated_indexes = _translated_indexes
- self._unique_filters = _unique_filters
- if extra:
- recs_names = [
- (
- (name,) + extras,
- (index, name, extras),
- )
- for index, (name, extras) in enumerate(zip(self._keys, extra))
- ]
- else:
- recs_names = [
- ((name,), (index, name, ()))
- for index, name in enumerate(self._keys)
- ]
- self._keymap = {key: rec for keys, rec in recs_names for key in keys}
- self._processors = _processors
- def _has_key(self, key):
- return key in self._keymap
- def _for_freeze(self):
- unique_filters = self._unique_filters
- if unique_filters and self._tuplefilter:
- unique_filters = self._tuplefilter(unique_filters)
- # TODO: are we freezing the result with or without uniqueness
- # applied?
- return SimpleResultMetaData(
- self._keys,
- extra=[self._keymap[key][2] for key in self._keys],
- _unique_filters=unique_filters,
- )
- def __getstate__(self):
- return {
- "_keys": self._keys,
- "_translated_indexes": self._translated_indexes,
- }
- def __setstate__(self, state):
- if state["_translated_indexes"]:
- _translated_indexes = state["_translated_indexes"]
- _tuplefilter = tuplegetter(*_translated_indexes)
- else:
- _translated_indexes = _tuplefilter = None
- self.__init__(
- state["_keys"],
- _translated_indexes=_translated_indexes,
- _tuplefilter=_tuplefilter,
- )
- def _contains(self, value, row):
- return value in row._data
- def _index_for_key(self, key, raiseerr=True):
- if int in key.__class__.__mro__:
- key = self._keys[key]
- try:
- rec = self._keymap[key]
- except KeyError as ke:
- rec = self._key_fallback(key, ke, raiseerr)
- return rec[0]
- def _indexes_for_keys(self, keys):
- return [self._keymap[key][0] for key in keys]
- def _metadata_for_keys(self, keys):
- for key in keys:
- if int in key.__class__.__mro__:
- key = self._keys[key]
- try:
- rec = self._keymap[key]
- except KeyError as ke:
- rec = self._key_fallback(key, ke, True)
- yield rec
- def _reduce(self, keys):
- try:
- metadata_for_keys = [
- self._keymap[
- self._keys[key] if int in key.__class__.__mro__ else key
- ]
- for key in keys
- ]
- except KeyError as ke:
- self._key_fallback(ke.args[0], ke, True)
- indexes, new_keys, extra = zip(*metadata_for_keys)
- if self._translated_indexes:
- indexes = [self._translated_indexes[idx] for idx in indexes]
- tup = tuplegetter(*indexes)
- new_metadata = SimpleResultMetaData(
- new_keys,
- extra=extra,
- _tuplefilter=tup,
- _translated_indexes=indexes,
- _processors=self._processors,
- _unique_filters=self._unique_filters,
- )
- return new_metadata
- def result_tuple(fields, extra=None):
- parent = SimpleResultMetaData(fields, extra)
- return functools.partial(
- Row, parent, parent._processors, parent._keymap, Row._default_key_style
- )
- # a symbol that indicates to internal Result methods that
- # "no row is returned". We can't use None for those cases where a scalar
- # filter is applied to rows.
- _NO_ROW = util.symbol("NO_ROW")
- class ResultInternal(InPlaceGenerative):
- _real_result = None
- _generate_rows = True
- _unique_filter_state = None
- _post_creational_filter = None
- @HasMemoized.memoized_attribute
- def _row_getter(self):
- real_result = self._real_result if self._real_result else self
- if real_result._source_supports_scalars:
- if not self._generate_rows:
- return None
- else:
- _proc = real_result._process_row
- def process_row(
- metadata, processors, keymap, key_style, scalar_obj
- ):
- return _proc(
- metadata, processors, keymap, key_style, (scalar_obj,)
- )
- else:
- process_row = real_result._process_row
- key_style = real_result._process_row._default_key_style
- metadata = self._metadata
- keymap = metadata._keymap
- processors = metadata._processors
- tf = metadata._tuplefilter
- if tf and not real_result._source_supports_scalars:
- if processors:
- processors = tf(processors)
- _make_row_orig = functools.partial(
- process_row, metadata, processors, keymap, key_style
- )
- def make_row(row):
- return _make_row_orig(tf(row))
- else:
- make_row = functools.partial(
- process_row, metadata, processors, keymap, key_style
- )
- fns = ()
- if real_result._row_logging_fn:
- fns = (real_result._row_logging_fn,)
- else:
- fns = ()
- if fns:
- _make_row = make_row
- def make_row(row):
- row = _make_row(row)
- for fn in fns:
- row = fn(row)
- return row
- return make_row
- @HasMemoized.memoized_attribute
- def _iterator_getter(self):
- make_row = self._row_getter
- post_creational_filter = self._post_creational_filter
- if self._unique_filter_state:
- uniques, strategy = self._unique_strategy
- def iterrows(self):
- for row in self._fetchiter_impl():
- obj = make_row(row) if make_row else row
- hashed = strategy(obj) if strategy else obj
- if hashed in uniques:
- continue
- uniques.add(hashed)
- if post_creational_filter:
- obj = post_creational_filter(obj)
- yield obj
- else:
- def iterrows(self):
- for row in self._fetchiter_impl():
- row = make_row(row) if make_row else row
- if post_creational_filter:
- row = post_creational_filter(row)
- yield row
- return iterrows
- def _raw_all_rows(self):
- make_row = self._row_getter
- rows = self._fetchall_impl()
- return [make_row(row) for row in rows]
- def _allrows(self):
- post_creational_filter = self._post_creational_filter
- make_row = self._row_getter
- rows = self._fetchall_impl()
- if make_row:
- made_rows = [make_row(row) for row in rows]
- else:
- made_rows = rows
- if self._unique_filter_state:
- uniques, strategy = self._unique_strategy
- rows = [
- made_row
- for made_row, sig_row in [
- (
- made_row,
- strategy(made_row) if strategy else made_row,
- )
- for made_row in made_rows
- ]
- if sig_row not in uniques and not uniques.add(sig_row)
- ]
- else:
- rows = made_rows
- if post_creational_filter:
- rows = [post_creational_filter(row) for row in rows]
- return rows
- @HasMemoized.memoized_attribute
- def _onerow_getter(self):
- make_row = self._row_getter
- post_creational_filter = self._post_creational_filter
- if self._unique_filter_state:
- uniques, strategy = self._unique_strategy
- def onerow(self):
- _onerow = self._fetchone_impl
- while True:
- row = _onerow()
- if row is None:
- return _NO_ROW
- else:
- obj = make_row(row) if make_row else row
- hashed = strategy(obj) if strategy else obj
- if hashed in uniques:
- continue
- else:
- uniques.add(hashed)
- if post_creational_filter:
- obj = post_creational_filter(obj)
- return obj
- else:
- def onerow(self):
- row = self._fetchone_impl()
- if row is None:
- return _NO_ROW
- else:
- row = make_row(row) if make_row else row
- if post_creational_filter:
- row = post_creational_filter(row)
- return row
- return onerow
- @HasMemoized.memoized_attribute
- def _manyrow_getter(self):
- make_row = self._row_getter
- post_creational_filter = self._post_creational_filter
- if self._unique_filter_state:
- uniques, strategy = self._unique_strategy
- def filterrows(make_row, rows, strategy, uniques):
- if make_row:
- rows = [make_row(row) for row in rows]
- if strategy:
- made_rows = (
- (made_row, strategy(made_row)) for made_row in rows
- )
- else:
- made_rows = ((made_row, made_row) for made_row in rows)
- return [
- made_row
- for made_row, sig_row in made_rows
- if sig_row not in uniques and not uniques.add(sig_row)
- ]
- def manyrows(self, num):
- collect = []
- _manyrows = self._fetchmany_impl
- if num is None:
- # if None is passed, we don't know the default
- # manyrows number, DBAPI has this as cursor.arraysize
- # different DBAPIs / fetch strategies may be different.
- # do a fetch to find what the number is. if there are
- # only fewer rows left, then it doesn't matter.
- real_result = (
- self._real_result if self._real_result else self
- )
- if real_result._yield_per:
- num_required = num = real_result._yield_per
- else:
- rows = _manyrows(num)
- num = len(rows)
- collect.extend(
- filterrows(make_row, rows, strategy, uniques)
- )
- num_required = num - len(collect)
- else:
- num_required = num
- while num_required:
- rows = _manyrows(num_required)
- if not rows:
- break
- collect.extend(
- filterrows(make_row, rows, strategy, uniques)
- )
- num_required = num - len(collect)
- if post_creational_filter:
- collect = [post_creational_filter(row) for row in collect]
- return collect
- else:
- def manyrows(self, num):
- if num is None:
- real_result = (
- self._real_result if self._real_result else self
- )
- num = real_result._yield_per
- rows = self._fetchmany_impl(num)
- if make_row:
- rows = [make_row(row) for row in rows]
- if post_creational_filter:
- rows = [post_creational_filter(row) for row in rows]
- return rows
- return manyrows
- def _only_one_row(
- self,
- raise_for_second_row,
- raise_for_none,
- scalar,
- ):
- onerow = self._fetchone_impl
- row = onerow(hard_close=True)
- if row is None:
- if raise_for_none:
- raise exc.NoResultFound(
- "No row was found when one was required"
- )
- else:
- return None
- if scalar and self._source_supports_scalars:
- self._generate_rows = False
- make_row = None
- else:
- make_row = self._row_getter
- try:
- row = make_row(row) if make_row else row
- except:
- self._soft_close(hard=True)
- raise
- if raise_for_second_row:
- if self._unique_filter_state:
- # for no second row but uniqueness, need to essentially
- # consume the entire result :(
- uniques, strategy = self._unique_strategy
- existing_row_hash = strategy(row) if strategy else row
- while True:
- next_row = onerow(hard_close=True)
- if next_row is None:
- next_row = _NO_ROW
- break
- try:
- next_row = make_row(next_row) if make_row else next_row
- if strategy:
- if existing_row_hash == strategy(next_row):
- continue
- elif row == next_row:
- continue
- # here, we have a row and it's different
- break
- except:
- self._soft_close(hard=True)
- raise
- else:
- next_row = onerow(hard_close=True)
- if next_row is None:
- next_row = _NO_ROW
- if next_row is not _NO_ROW:
- self._soft_close(hard=True)
- raise exc.MultipleResultsFound(
- "Multiple rows were found when exactly one was required"
- if raise_for_none
- else "Multiple rows were found when one or none "
- "was required"
- )
- else:
- next_row = _NO_ROW
- # if we checked for second row then that would have
- # closed us :)
- self._soft_close(hard=True)
- if not scalar:
- post_creational_filter = self._post_creational_filter
- if post_creational_filter:
- row = post_creational_filter(row)
- if scalar and make_row:
- return row[0]
- else:
- return row
- def _iter_impl(self):
- return self._iterator_getter(self)
- def _next_impl(self):
- row = self._onerow_getter(self)
- if row is _NO_ROW:
- raise StopIteration()
- else:
- return row
- @_generative
- def _column_slices(self, indexes):
- real_result = self._real_result if self._real_result else self
- if real_result._source_supports_scalars and len(indexes) == 1:
- self._generate_rows = False
- else:
- self._generate_rows = True
- self._metadata = self._metadata._reduce(indexes)
- @HasMemoized.memoized_attribute
- def _unique_strategy(self):
- uniques, strategy = self._unique_filter_state
- real_result = (
- self._real_result if self._real_result is not None else self
- )
- if not strategy and self._metadata._unique_filters:
- if (
- real_result._source_supports_scalars
- and not self._generate_rows
- ):
- strategy = self._metadata._unique_filters[0]
- else:
- filters = self._metadata._unique_filters
- if self._metadata._tuplefilter:
- filters = self._metadata._tuplefilter(filters)
- strategy = operator.methodcaller("_filter_on_values", filters)
- return uniques, strategy
- class _WithKeys(object):
- # used mainly to share documentation on the keys method.
- # py2k does not allow overriding the __doc__ attribute.
- def keys(self):
- """Return an iterable view which yields the string keys that would
- be represented by each :class:`.Row`.
- The keys can represent the labels of the columns returned by a core
- statement or the names of the orm classes returned by an orm
- execution.
- The view also can be tested for key containment using the Python
- ``in`` operator, which will test both for the string keys represented
- in the view, as well as for alternate keys such as column objects.
- .. versionchanged:: 1.4 a key view object is returned rather than a
- plain list.
- """
- return self._metadata.keys
- class Result(_WithKeys, ResultInternal):
- """Represent a set of database results.
- .. versionadded:: 1.4 The :class:`.Result` object provides a completely
- updated usage model and calling facade for SQLAlchemy Core and
- SQLAlchemy ORM. In Core, it forms the basis of the
- :class:`.CursorResult` object which replaces the previous
- :class:`.ResultProxy` interface. When using the ORM, a higher level
- object called :class:`.ChunkedIteratorResult` is normally used.
- .. note:: In SQLAlchemy 1.4 and above, this object is
- used for ORM results returned by :meth:`_orm.Session.execute`, which can
- yield instances of ORM mapped objects either individually or within
- tuple-like rows. Note that the :class:`_result.Result` object does not
- deduplicate instances or rows automatically as is the case with the
- legacy :class:`_orm.Query` object. For in-Python de-duplication of
- instances or rows, use the :meth:`_result.Result.unique` modifier
- method.
- .. seealso::
- :ref:`tutorial_fetching_rows` - in the :doc:`/tutorial/index`
- """
- _process_row = Row
- _row_logging_fn = None
- _source_supports_scalars = False
- _yield_per = None
- _attributes = util.immutabledict()
- def __init__(self, cursor_metadata):
- self._metadata = cursor_metadata
- def _soft_close(self, hard=False):
- raise NotImplementedError()
- def close(self):
- """close this :class:`_result.Result`.
- The behavior of this method is implementation specific, and is
- not implemented by default. The method should generally end
- the resources in use by the result object and also cause any
- subsequent iteration or row fetching to raise
- :class:`.ResourceClosedError`.
- .. versionadded:: 1.4.27 - ``.close()`` was previously not generally
- available for all :class:`_result.Result` classes, instead only
- being available on the :class:`_engine.CursorResult` returned for
- Core statement executions. As most other result objects, namely the
- ones used by the ORM, are proxying a :class:`_engine.CursorResult`
- in any case, this allows the underlying cursor result to be closed
- from the outside facade for the case when the ORM query is using
- the ``yield_per`` execution option where it does not immediately
- exhaust and autoclose the database cursor.
- """
- self._soft_close(hard=True)
- @_generative
- def yield_per(self, num):
- """Configure the row-fetching strategy to fetch num rows at a time.
- This impacts the underlying behavior of the result when iterating over
- the result object, or otherwise making use of methods such as
- :meth:`_engine.Result.fetchone` that return one row at a time. Data
- from the underlying cursor or other data source will be buffered up to
- this many rows in memory, and the buffered collection will then be
- yielded out one row at at time or as many rows are requested. Each time
- the buffer clears, it will be refreshed to this many rows or as many
- rows remain if fewer remain.
- The :meth:`_engine.Result.yield_per` method is generally used in
- conjunction with the
- :paramref:`_engine.Connection.execution_options.stream_results`
- execution option, which will allow the database dialect in use to make
- use of a server side cursor, if the DBAPI supports it.
- Most DBAPIs do not use server side cursors by default, which means all
- rows will be fetched upfront from the database regardless of the
- :meth:`_engine.Result.yield_per` setting. However,
- :meth:`_engine.Result.yield_per` may still be useful in that it batches
- the SQLAlchemy-side processing of the raw data from the database, and
- additionally when used for ORM scenarios will batch the conversion of
- database rows into ORM entity rows.
- .. versionadded:: 1.4
- :param num: number of rows to fetch each time the buffer is refilled.
- If set to a value below 1, fetches all rows for the next buffer.
- """
- self._yield_per = num
- @_generative
- def unique(self, strategy=None):
- """Apply unique filtering to the objects returned by this
- :class:`_engine.Result`.
- When this filter is applied with no arguments, the rows or objects
- returned will filtered such that each row is returned uniquely. The
- algorithm used to determine this uniqueness is by default the Python
- hashing identity of the whole tuple. In some cases a specialized
- per-entity hashing scheme may be used, such as when using the ORM, a
- scheme is applied which works against the primary key identity of
- returned objects.
- The unique filter is applied **after all other filters**, which means
- if the columns returned have been refined using a method such as the
- :meth:`_engine.Result.columns` or :meth:`_engine.Result.scalars`
- method, the uniquing is applied to **only the column or columns
- returned**. This occurs regardless of the order in which these
- methods have been called upon the :class:`_engine.Result` object.
- The unique filter also changes the calculus used for methods like
- :meth:`_engine.Result.fetchmany` and :meth:`_engine.Result.partitions`.
- When using :meth:`_engine.Result.unique`, these methods will continue
- to yield the number of rows or objects requested, after uniquing
- has been applied. However, this necessarily impacts the buffering
- behavior of the underlying cursor or datasource, such that multiple
- underlying calls to ``cursor.fetchmany()`` may be necessary in order
- to accumulate enough objects in order to provide a unique collection
- of the requested size.
- :param strategy: a callable that will be applied to rows or objects
- being iterated, which should return an object that represents the
- unique value of the row. A Python ``set()`` is used to store
- these identities. If not passed, a default uniqueness strategy
- is used which may have been assembled by the source of this
- :class:`_engine.Result` object.
- """
- self._unique_filter_state = (set(), strategy)
- def columns(self, *col_expressions):
- r"""Establish the columns that should be returned in each row.
- This method may be used to limit the columns returned as well
- as to reorder them. The given list of expressions are normally
- a series of integers or string key names. They may also be
- appropriate :class:`.ColumnElement` objects which correspond to
- a given statement construct.
- E.g.::
- statement = select(table.c.x, table.c.y, table.c.z)
- result = connection.execute(statement)
- for z, y in result.columns('z', 'y'):
- # ...
- Example of using the column objects from the statement itself::
- for z, y in result.columns(
- statement.selected_columns.c.z,
- statement.selected_columns.c.y
- ):
- # ...
- .. versionadded:: 1.4
- :param \*col_expressions: indicates columns to be returned. Elements
- may be integer row indexes, string column names, or appropriate
- :class:`.ColumnElement` objects corresponding to a select construct.
- :return: this :class:`_engine.Result` object with the modifications
- given.
- """
- return self._column_slices(col_expressions)
- def scalars(self, index=0):
- """Return a :class:`_result.ScalarResult` filtering object which
- will return single elements rather than :class:`_row.Row` objects.
- E.g.::
- >>> result = conn.execute(text("select int_id from table"))
- >>> result.scalars().all()
- [1, 2, 3]
- When results are fetched from the :class:`_result.ScalarResult`
- filtering object, the single column-row that would be returned by the
- :class:`_result.Result` is instead returned as the column's value.
- .. versionadded:: 1.4
- :param index: integer or row key indicating the column to be fetched
- from each row, defaults to ``0`` indicating the first column.
- :return: a new :class:`_result.ScalarResult` filtering object referring
- to this :class:`_result.Result` object.
- """
- return ScalarResult(self, index)
- def _getter(self, key, raiseerr=True):
- """return a callable that will retrieve the given key from a
- :class:`.Row`.
- """
- if self._source_supports_scalars:
- raise NotImplementedError(
- "can't use this function in 'only scalars' mode"
- )
- return self._metadata._getter(key, raiseerr)
- def _tuple_getter(self, keys):
- """return a callable that will retrieve the given keys from a
- :class:`.Row`.
- """
- if self._source_supports_scalars:
- raise NotImplementedError(
- "can't use this function in 'only scalars' mode"
- )
- return self._metadata._row_as_tuple_getter(keys)
- def mappings(self):
- """Apply a mappings filter to returned rows, returning an instance of
- :class:`_result.MappingResult`.
- When this filter is applied, fetching rows will return
- :class:`.RowMapping` objects instead of :class:`.Row` objects.
- .. versionadded:: 1.4
- :return: a new :class:`_result.MappingResult` filtering object
- referring to this :class:`_result.Result` object.
- """
- return MappingResult(self)
- def _raw_row_iterator(self):
- """Return a safe iterator that yields raw row data.
- This is used by the :meth:`._engine.Result.merge` method
- to merge multiple compatible results together.
- """
- raise NotImplementedError()
- def _fetchiter_impl(self):
- raise NotImplementedError()
- def _fetchone_impl(self, hard_close=False):
- raise NotImplementedError()
- def _fetchall_impl(self):
- raise NotImplementedError()
- def _fetchmany_impl(self, size=None):
- raise NotImplementedError()
- def __iter__(self):
- return self._iter_impl()
- def __next__(self):
- return self._next_impl()
- if py2k:
- def next(self): # noqa
- return self._next_impl()
- def partitions(self, size=None):
- """Iterate through sub-lists of rows of the size given.
- Each list will be of the size given, excluding the last list to
- be yielded, which may have a small number of rows. No empty
- lists will be yielded.
- The result object is automatically closed when the iterator
- is fully consumed.
- Note that the backend driver will usually buffer the entire result
- ahead of time unless the
- :paramref:`.Connection.execution_options.stream_results` execution
- option is used indicating that the driver should not pre-buffer
- results, if possible. Not all drivers support this option and
- the option is silently ignored for those who do not.
- .. versionadded:: 1.4
- :param size: indicate the maximum number of rows to be present
- in each list yielded. If None, makes use of the value set by
- :meth:`_engine.Result.yield_per`, if present, otherwise uses the
- :meth:`_engine.Result.fetchmany` default which may be backend
- specific.
- :return: iterator of lists
- """
- getter = self._manyrow_getter
- while True:
- partition = getter(self, size)
- if partition:
- yield partition
- else:
- break
- def fetchall(self):
- """A synonym for the :meth:`_engine.Result.all` method."""
- return self._allrows()
- def fetchone(self):
- """Fetch one row.
- When all rows are exhausted, returns None.
- This method is provided for backwards compatibility with
- SQLAlchemy 1.x.x.
- To fetch the first row of a result only, use the
- :meth:`_engine.Result.first` method. To iterate through all
- rows, iterate the :class:`_engine.Result` object directly.
- :return: a :class:`.Row` object if no filters are applied, or None
- if no rows remain.
- """
- row = self._onerow_getter(self)
- if row is _NO_ROW:
- return None
- else:
- return row
- def fetchmany(self, size=None):
- """Fetch many rows.
- When all rows are exhausted, returns an empty list.
- This method is provided for backwards compatibility with
- SQLAlchemy 1.x.x.
- To fetch rows in groups, use the :meth:`._result.Result.partitions`
- method.
- :return: a list of :class:`.Row` objects.
- """
- return self._manyrow_getter(self, size)
- def all(self):
- """Return all rows in a list.
- Closes the result set after invocation. Subsequent invocations
- will return an empty list.
- .. versionadded:: 1.4
- :return: a list of :class:`.Row` objects.
- """
- return self._allrows()
- def first(self):
- """Fetch the first row or None if no row is present.
- Closes the result set and discards remaining rows.
- .. note:: This method returns one **row**, e.g. tuple, by default.
- To return exactly one single scalar value, that is, the first
- column of the first row, use the :meth:`.Result.scalar` method,
- or combine :meth:`.Result.scalars` and :meth:`.Result.first`.
- Additionally, in contrast to the behavior of the legacy ORM
- :meth:`_orm.Query.first` method, **no limit is applied** to the
- SQL query which was invoked to produce this :class:`_engine.Result`;
- for a DBAPI driver that buffers results in memory before yielding
- rows, all rows will be sent to the Python process and all but
- the first row will be discarded.
- .. seealso::
- :ref:`migration_20_unify_select`
- :return: a :class:`.Row` object, or None
- if no rows remain.
- .. seealso::
- :meth:`_result.Result.scalar`
- :meth:`_result.Result.one`
- """
- return self._only_one_row(
- raise_for_second_row=False, raise_for_none=False, scalar=False
- )
- def one_or_none(self):
- """Return at most one result or raise an exception.
- Returns ``None`` if the result has no rows.
- Raises :class:`.MultipleResultsFound`
- if multiple rows are returned.
- .. versionadded:: 1.4
- :return: The first :class:`.Row` or None if no row is available.
- :raises: :class:`.MultipleResultsFound`
- .. seealso::
- :meth:`_result.Result.first`
- :meth:`_result.Result.one`
- """
- return self._only_one_row(
- raise_for_second_row=True, raise_for_none=False, scalar=False
- )
- def scalar_one(self):
- """Return exactly one scalar result or raise an exception.
- This is equivalent to calling :meth:`.Result.scalars` and then
- :meth:`.Result.one`.
- .. seealso::
- :meth:`.Result.one`
- :meth:`.Result.scalars`
- """
- return self._only_one_row(
- raise_for_second_row=True, raise_for_none=True, scalar=True
- )
- def scalar_one_or_none(self):
- """Return exactly one or no scalar result.
- This is equivalent to calling :meth:`.Result.scalars` and then
- :meth:`.Result.one_or_none`.
- .. seealso::
- :meth:`.Result.one_or_none`
- :meth:`.Result.scalars`
- """
- return self._only_one_row(
- raise_for_second_row=True, raise_for_none=False, scalar=True
- )
- def one(self):
- """Return exactly one row or raise an exception.
- Raises :class:`.NoResultFound` if the result returns no
- rows, or :class:`.MultipleResultsFound` if multiple rows
- would be returned.
- .. note:: This method returns one **row**, e.g. tuple, by default.
- To return exactly one single scalar value, that is, the first
- column of the first row, use the :meth:`.Result.scalar_one` method,
- or combine :meth:`.Result.scalars` and :meth:`.Result.one`.
- .. versionadded:: 1.4
- :return: The first :class:`.Row`.
- :raises: :class:`.MultipleResultsFound`, :class:`.NoResultFound`
- .. seealso::
- :meth:`_result.Result.first`
- :meth:`_result.Result.one_or_none`
- :meth:`_result.Result.scalar_one`
- """
- return self._only_one_row(
- raise_for_second_row=True, raise_for_none=True, scalar=False
- )
- def scalar(self):
- """Fetch the first column of the first row, and close the result set.
- Returns None if there are no rows to fetch.
- No validation is performed to test if additional rows remain.
- After calling this method, the object is fully closed,
- e.g. the :meth:`_engine.CursorResult.close`
- method will have been called.
- :return: a Python scalar value , or None if no rows remain.
- """
- return self._only_one_row(
- raise_for_second_row=False, raise_for_none=False, scalar=True
- )
- def freeze(self):
- """Return a callable object that will produce copies of this
- :class:`.Result` when invoked.
- The callable object returned is an instance of
- :class:`_engine.FrozenResult`.
- This is used for result set caching. The method must be called
- on the result when it has been unconsumed, and calling the method
- will consume the result fully. When the :class:`_engine.FrozenResult`
- is retrieved from a cache, it can be called any number of times where
- it will produce a new :class:`_engine.Result` object each time
- against its stored set of rows.
- .. seealso::
- :ref:`do_orm_execute_re_executing` - example usage within the
- ORM to implement a result-set cache.
- """
- return FrozenResult(self)
- def merge(self, *others):
- """Merge this :class:`.Result` with other compatible result
- objects.
- The object returned is an instance of :class:`_engine.MergedResult`,
- which will be composed of iterators from the given result
- objects.
- The new result will use the metadata from this result object.
- The subsequent result objects must be against an identical
- set of result / cursor metadata, otherwise the behavior is
- undefined.
- """
- return MergedResult(self._metadata, (self,) + others)
- class FilterResult(ResultInternal):
- """A wrapper for a :class:`_engine.Result` that returns objects other than
- :class:`_result.Row` objects, such as dictionaries or scalar objects.
- """
- _post_creational_filter = None
- def _soft_close(self, hard=False):
- self._real_result._soft_close(hard=hard)
- @property
- def _attributes(self):
- return self._real_result._attributes
- def _fetchiter_impl(self):
- return self._real_result._fetchiter_impl()
- def _fetchone_impl(self, hard_close=False):
- return self._real_result._fetchone_impl(hard_close=hard_close)
- def _fetchall_impl(self):
- return self._real_result._fetchall_impl()
- def _fetchmany_impl(self, size=None):
- return self._real_result._fetchmany_impl(size=size)
- class ScalarResult(FilterResult):
- """A wrapper for a :class:`_result.Result` that returns scalar values
- rather than :class:`_row.Row` values.
- The :class:`_result.ScalarResult` object is acquired by calling the
- :meth:`_result.Result.scalars` method.
- A special limitation of :class:`_result.ScalarResult` is that it has
- no ``fetchone()`` method; since the semantics of ``fetchone()`` are that
- the ``None`` value indicates no more results, this is not compatible
- with :class:`_result.ScalarResult` since there is no way to distinguish
- between ``None`` as a row value versus ``None`` as an indicator. Use
- ``next(result)`` to receive values individually.
- """
- _generate_rows = False
- def __init__(self, real_result, index):
- self._real_result = real_result
- if real_result._source_supports_scalars:
- self._metadata = real_result._metadata
- self._post_creational_filter = None
- else:
- self._metadata = real_result._metadata._reduce([index])
- self._post_creational_filter = operator.itemgetter(0)
- self._unique_filter_state = real_result._unique_filter_state
- def unique(self, strategy=None):
- """Apply unique filtering to the objects returned by this
- :class:`_engine.ScalarResult`.
- See :meth:`_engine.Result.unique` for usage details.
- """
- self._unique_filter_state = (set(), strategy)
- return self
- def partitions(self, size=None):
- """Iterate through sub-lists of elements of the size given.
- Equivalent to :meth:`_result.Result.partitions` except that
- scalar values, rather than :class:`_result.Row` objects,
- are returned.
- """
- getter = self._manyrow_getter
- while True:
- partition = getter(self, size)
- if partition:
- yield partition
- else:
- break
- def fetchall(self):
- """A synonym for the :meth:`_engine.ScalarResult.all` method."""
- return self._allrows()
- def fetchmany(self, size=None):
- """Fetch many objects.
- Equivalent to :meth:`_result.Result.fetchmany` except that
- scalar values, rather than :class:`_result.Row` objects,
- are returned.
- """
- return self._manyrow_getter(self, size)
- def all(self):
- """Return all scalar values in a list.
- Equivalent to :meth:`_result.Result.all` except that
- scalar values, rather than :class:`_result.Row` objects,
- are returned.
- """
- return self._allrows()
- def __iter__(self):
- return self._iter_impl()
- def __next__(self):
- return self._next_impl()
- if py2k:
- def next(self): # noqa
- return self._next_impl()
- def first(self):
- """Fetch the first object or None if no object is present.
- Equivalent to :meth:`_result.Result.first` except that
- scalar values, rather than :class:`_result.Row` objects,
- are returned.
- """
- return self._only_one_row(
- raise_for_second_row=False, raise_for_none=False, scalar=False
- )
- def one_or_none(self):
- """Return at most one object or raise an exception.
- Equivalent to :meth:`_result.Result.one_or_none` except that
- scalar values, rather than :class:`_result.Row` objects,
- are returned.
- """
- return self._only_one_row(
- raise_for_second_row=True, raise_for_none=False, scalar=False
- )
- def one(self):
- """Return exactly one object or raise an exception.
- Equivalent to :meth:`_result.Result.one` except that
- scalar values, rather than :class:`_result.Row` objects,
- are returned.
- """
- return self._only_one_row(
- raise_for_second_row=True, raise_for_none=True, scalar=False
- )
- class MappingResult(_WithKeys, FilterResult):
- """A wrapper for a :class:`_engine.Result` that returns dictionary values
- rather than :class:`_engine.Row` values.
- The :class:`_engine.MappingResult` object is acquired by calling the
- :meth:`_engine.Result.mappings` method.
- """
- _generate_rows = True
- _post_creational_filter = operator.attrgetter("_mapping")
- def __init__(self, result):
- self._real_result = result
- self._unique_filter_state = result._unique_filter_state
- self._metadata = result._metadata
- if result._source_supports_scalars:
- self._metadata = self._metadata._reduce([0])
- def unique(self, strategy=None):
- """Apply unique filtering to the objects returned by this
- :class:`_engine.MappingResult`.
- See :meth:`_engine.Result.unique` for usage details.
- """
- self._unique_filter_state = (set(), strategy)
- return self
- def columns(self, *col_expressions):
- r"""Establish the columns that should be returned in each row."""
- return self._column_slices(col_expressions)
- def partitions(self, size=None):
- """Iterate through sub-lists of elements of the size given.
- Equivalent to :meth:`_result.Result.partitions` except that
- mapping values, rather than :class:`_result.Row` objects,
- are returned.
- """
- getter = self._manyrow_getter
- while True:
- partition = getter(self, size)
- if partition:
- yield partition
- else:
- break
- def fetchall(self):
- """A synonym for the :meth:`_engine.MappingResult.all` method."""
- return self._allrows()
- def fetchone(self):
- """Fetch one object.
- Equivalent to :meth:`_result.Result.fetchone` except that
- mapping values, rather than :class:`_result.Row` objects,
- are returned.
- """
- row = self._onerow_getter(self)
- if row is _NO_ROW:
- return None
- else:
- return row
- def fetchmany(self, size=None):
- """Fetch many objects.
- Equivalent to :meth:`_result.Result.fetchmany` except that
- mapping values, rather than :class:`_result.Row` objects,
- are returned.
- """
- return self._manyrow_getter(self, size)
- def all(self):
- """Return all scalar values in a list.
- Equivalent to :meth:`_result.Result.all` except that
- mapping values, rather than :class:`_result.Row` objects,
- are returned.
- """
- return self._allrows()
- def __iter__(self):
- return self._iter_impl()
- def __next__(self):
- return self._next_impl()
- if py2k:
- def next(self): # noqa
- return self._next_impl()
- def first(self):
- """Fetch the first object or None if no object is present.
- Equivalent to :meth:`_result.Result.first` except that
- mapping values, rather than :class:`_result.Row` objects,
- are returned.
- """
- return self._only_one_row(
- raise_for_second_row=False, raise_for_none=False, scalar=False
- )
- def one_or_none(self):
- """Return at most one object or raise an exception.
- Equivalent to :meth:`_result.Result.one_or_none` except that
- mapping values, rather than :class:`_result.Row` objects,
- are returned.
- """
- return self._only_one_row(
- raise_for_second_row=True, raise_for_none=False, scalar=False
- )
- def one(self):
- """Return exactly one object or raise an exception.
- Equivalent to :meth:`_result.Result.one` except that
- mapping values, rather than :class:`_result.Row` objects,
- are returned.
- """
- return self._only_one_row(
- raise_for_second_row=True, raise_for_none=True, scalar=False
- )
- class FrozenResult(object):
- """Represents a :class:`.Result` object in a "frozen" state suitable
- for caching.
- The :class:`_engine.FrozenResult` object is returned from the
- :meth:`_engine.Result.freeze` method of any :class:`_engine.Result`
- object.
- A new iterable :class:`.Result` object is generated from a fixed
- set of data each time the :class:`.FrozenResult` is invoked as
- a callable::
- result = connection.execute(query)
- frozen = result.freeze()
- unfrozen_result_one = frozen()
- for row in unfrozen_result_one:
- print(row)
- unfrozen_result_two = frozen()
- rows = unfrozen_result_two.all()
- # ... etc
- .. versionadded:: 1.4
- .. seealso::
- :ref:`do_orm_execute_re_executing` - example usage within the
- ORM to implement a result-set cache.
- :func:`_orm.loading.merge_frozen_result` - ORM function to merge
- a frozen result back into a :class:`_orm.Session`.
- """
- def __init__(self, result):
- self.metadata = result._metadata._for_freeze()
- self._source_supports_scalars = result._source_supports_scalars
- self._attributes = result._attributes
- if self._source_supports_scalars:
- self.data = list(result._raw_row_iterator())
- else:
- self.data = result.fetchall()
- def rewrite_rows(self):
- if self._source_supports_scalars:
- return [[elem] for elem in self.data]
- else:
- return [list(row) for row in self.data]
- def with_new_rows(self, tuple_data):
- fr = FrozenResult.__new__(FrozenResult)
- fr.metadata = self.metadata
- fr._attributes = self._attributes
- fr._source_supports_scalars = self._source_supports_scalars
- if self._source_supports_scalars:
- fr.data = [d[0] for d in tuple_data]
- else:
- fr.data = tuple_data
- return fr
- def __call__(self):
- result = IteratorResult(self.metadata, iter(self.data))
- result._attributes = self._attributes
- result._source_supports_scalars = self._source_supports_scalars
- return result
- class IteratorResult(Result):
- """A :class:`.Result` that gets data from a Python iterator of
- :class:`.Row` objects.
- .. versionadded:: 1.4
- """
- _hard_closed = False
- def __init__(
- self,
- cursor_metadata,
- iterator,
- raw=None,
- _source_supports_scalars=False,
- ):
- self._metadata = cursor_metadata
- self.iterator = iterator
- self.raw = raw
- self._source_supports_scalars = _source_supports_scalars
- def _soft_close(self, hard=False, **kw):
- if hard:
- self._hard_closed = True
- if self.raw is not None:
- self.raw._soft_close(hard=hard, **kw)
- self.iterator = iter([])
- self._reset_memoizations()
- def _raise_hard_closed(self):
- raise exc.ResourceClosedError("This result object is closed.")
- def _raw_row_iterator(self):
- return self.iterator
- def _fetchiter_impl(self):
- if self._hard_closed:
- self._raise_hard_closed()
- return self.iterator
- def _fetchone_impl(self, hard_close=False):
- if self._hard_closed:
- self._raise_hard_closed()
- row = next(self.iterator, _NO_ROW)
- if row is _NO_ROW:
- self._soft_close(hard=hard_close)
- return None
- else:
- return row
- def _fetchall_impl(self):
- if self._hard_closed:
- self._raise_hard_closed()
- try:
- return list(self.iterator)
- finally:
- self._soft_close()
- def _fetchmany_impl(self, size=None):
- if self._hard_closed:
- self._raise_hard_closed()
- return list(itertools.islice(self.iterator, 0, size))
- def null_result():
- return IteratorResult(SimpleResultMetaData([]), iter([]))
- class ChunkedIteratorResult(IteratorResult):
- """An :class:`.IteratorResult` that works from an iterator-producing callable.
- The given ``chunks`` argument is a function that is given a number of rows
- to return in each chunk, or ``None`` for all rows. The function should
- then return an un-consumed iterator of lists, each list of the requested
- size.
- The function can be called at any time again, in which case it should
- continue from the same result set but adjust the chunk size as given.
- .. versionadded:: 1.4
- """
- def __init__(
- self,
- cursor_metadata,
- chunks,
- source_supports_scalars=False,
- raw=None,
- dynamic_yield_per=False,
- ):
- self._metadata = cursor_metadata
- self.chunks = chunks
- self._source_supports_scalars = source_supports_scalars
- self.raw = raw
- self.iterator = itertools.chain.from_iterable(self.chunks(None))
- self.dynamic_yield_per = dynamic_yield_per
- @_generative
- def yield_per(self, num):
- # TODO: this throws away the iterator which may be holding
- # onto a chunk. the yield_per cannot be changed once any
- # rows have been fetched. either find a way to enforce this,
- # or we can't use itertools.chain and will instead have to
- # keep track.
- self._yield_per = num
- self.iterator = itertools.chain.from_iterable(self.chunks(num))
- def _soft_close(self, **kw):
- super(ChunkedIteratorResult, self)._soft_close(**kw)
- self.chunks = lambda size: []
- def _fetchmany_impl(self, size=None):
- if self.dynamic_yield_per:
- self.iterator = itertools.chain.from_iterable(self.chunks(size))
- return super(ChunkedIteratorResult, self)._fetchmany_impl(size=size)
- class MergedResult(IteratorResult):
- """A :class:`_engine.Result` that is merged from any number of
- :class:`_engine.Result` objects.
- Returned by the :meth:`_engine.Result.merge` method.
- .. versionadded:: 1.4
- """
- closed = False
- def __init__(self, cursor_metadata, results):
- self._results = results
- super(MergedResult, self).__init__(
- cursor_metadata,
- itertools.chain.from_iterable(
- r._raw_row_iterator() for r in results
- ),
- )
- self._unique_filter_state = results[0]._unique_filter_state
- self._yield_per = results[0]._yield_per
- # going to try something w/ this in next rev
- self._source_supports_scalars = results[0]._source_supports_scalars
- self._attributes = self._attributes.merge_with(
- *[r._attributes for r in results]
- )
- def _soft_close(self, hard=False, **kw):
- for r in self._results:
- r._soft_close(hard=hard, **kw)
- if hard:
- self.closed = True
|