1# engine/result.py 
    2# Copyright (C) 2005-2021 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: http://www.opensource.org/licenses/mit-license.php 
    7 
    8"""Define result set constructs including :class:`_engine.ResultProxy` 
    9and :class:`.RowProxy`.""" 
    10 
    11 
    12import collections 
    13import operator 
    14 
    15from .. import exc 
    16from .. import util 
    17from ..sql import expression 
    18from ..sql import sqltypes 
    19from ..sql import util as sql_util 
    20 
    21 
    22# This reconstructor is necessary so that pickles with the C extension or 
    23# without use the same Binary format. 
    24try: 
    25    # We need a different reconstructor on the C extension so that we can 
    26    # add extra checks that fields have correctly been initialized by 
    27    # __setstate__. 
    28    from sqlalchemy.cresultproxy import safe_rowproxy_reconstructor 
    29 
    30    # The extra function embedding is needed so that the 
    31    # reconstructor function has the same signature whether or not 
    32    # the extension is present. 
    33    def rowproxy_reconstructor(cls, state): 
    34        return safe_rowproxy_reconstructor(cls, state) 
    35 
    36 
    37except ImportError: 
    38 
    39    def rowproxy_reconstructor(cls, state): 
    40        obj = cls.__new__(cls) 
    41        obj.__setstate__(state) 
    42        return obj 
    43 
    44 
    45try: 
    46    from sqlalchemy.cresultproxy import BaseRowProxy 
    47 
    48    _baserowproxy_usecext = True 
    49except ImportError: 
    50    _baserowproxy_usecext = False 
    51 
    52    class BaseRowProxy(object): 
    53        __slots__ = ("_parent", "_row", "_processors", "_keymap") 
    54 
    55        def __init__(self, parent, row, processors, keymap): 
    56            """RowProxy objects are constructed by ResultProxy objects.""" 
    57 
    58            self._parent = parent 
    59            self._row = row 
    60            self._processors = processors 
    61            self._keymap = keymap 
    62 
    63        def __reduce__(self): 
    64            return ( 
    65                rowproxy_reconstructor, 
    66                (self.__class__, self.__getstate__()), 
    67            ) 
    68 
    69        def values(self): 
    70            """Return the values represented by this RowProxy as a list.""" 
    71            return list(self) 
    72 
    73        def __iter__(self): 
    74            for processor, value in zip(self._processors, self._row): 
    75                if processor is None: 
    76                    yield value 
    77                else: 
    78                    yield processor(value) 
    79 
    80        def __len__(self): 
    81            return len(self._row) 
    82 
    83        def __getitem__(self, key): 
    84            try: 
    85                processor, obj, index = self._keymap[key] 
    86            except KeyError as err: 
    87                processor, obj, index = self._parent._key_fallback(key, err) 
    88            except TypeError: 
    89                if isinstance(key, slice): 
    90                    l = [] 
    91                    for processor, value in zip( 
    92                        self._processors[key], self._row[key] 
    93                    ): 
    94                        if processor is None: 
    95                            l.append(value) 
    96                        else: 
    97                            l.append(processor(value)) 
    98                    return tuple(l) 
    99                else: 
    100                    raise 
    101            if index is None: 
    102                raise exc.InvalidRequestError( 
    103                    "Ambiguous column name '%s' in " 
    104                    "result set column descriptions" % obj 
    105                ) 
    106            if processor is not None: 
    107                return processor(self._row[index]) 
    108            else: 
    109                return self._row[index] 
    110 
    111        def __getattr__(self, name): 
    112            try: 
    113                return self[name] 
    114            except KeyError as e: 
    115                util.raise_(AttributeError(e.args[0]), replace_context=e) 
    116 
    117 
    118class RowProxy(BaseRowProxy): 
    119    """Represent a single result row. 
    120 
    121    The :class:`.RowProxy` object is retrieved from a database result, from the 
    122    :class:`_engine.ResultProxy` object using methods like 
    123    :meth:`_engine.ResultProxy.fetchall`. 
    124 
    125    The :class:`.RowProxy` object seeks to act mostly like a Python named 
    126    tuple, but also provides some Python dictionary behaviors at the same time. 
    127 
    128    .. seealso:: 
    129 
    130        :ref:`coretutorial_selecting` - includes examples of selecting 
    131        rows from SELECT statements. 
    132 
    133    """ 
    134 
    135    __slots__ = () 
    136 
    137    def __contains__(self, key): 
    138        return self._parent._has_key(key) 
    139 
    140    def __getstate__(self): 
    141        return {"_parent": self._parent, "_row": tuple(self)} 
    142 
    143    def __setstate__(self, state): 
    144        self._parent = parent = state["_parent"] 
    145        self._row = state["_row"] 
    146        self._processors = parent._processors 
    147        self._keymap = parent._keymap 
    148 
    149    __hash__ = None 
    150 
    151    def _op(self, other, op): 
    152        return ( 
    153            op(tuple(self), tuple(other)) 
    154            if isinstance(other, RowProxy) 
    155            else op(tuple(self), other) 
    156        ) 
    157 
    158    def __lt__(self, other): 
    159        return self._op(other, operator.lt) 
    160 
    161    def __le__(self, other): 
    162        return self._op(other, operator.le) 
    163 
    164    def __ge__(self, other): 
    165        return self._op(other, operator.ge) 
    166 
    167    def __gt__(self, other): 
    168        return self._op(other, operator.gt) 
    169 
    170    def __eq__(self, other): 
    171        return self._op(other, operator.eq) 
    172 
    173    def __ne__(self, other): 
    174        return self._op(other, operator.ne) 
    175 
    176    def __repr__(self): 
    177        return repr(sql_util._repr_row(self)) 
    178 
    179    def has_key(self, key): 
    180        """Return True if this :class:`.RowProxy` contains the given key. 
    181 
    182        Through the SQLAlchemy 1.x series, the ``__contains__()`` method 
    183        of :class:`.RowProxy` also links to :meth:`.RowProxy.has_key`, in that 
    184        an expression such as :: 
    185 
    186            "some_col" in row 
    187 
    188        Will return True if the row contains a column named ``"some_col"``, 
    189        in the way that a Python mapping works. 
    190 
    191        However, it is planned that the 2.0 series of SQLAlchemy will reverse 
    192        this behavior so that ``__contains__()`` will refer to a value being 
    193        present in the row, in the way that a Python tuple works. 
    194 
    195        """ 
    196 
    197        return self._parent._has_key(key) 
    198 
    199    def items(self): 
    200        """Return a list of tuples, each tuple containing a key/value pair. 
    201 
    202        This method is analogous to the Python dictionary ``.items()`` method, 
    203        except that it returns a list, not an iterator. 
    204 
    205        """ 
    206 
    207        return [(key, self[key]) for key in self.keys()] 
    208 
    209    def keys(self): 
    210        """Return the list of keys as strings represented by this 
    211        :class:`.RowProxy`. 
    212 
    213        This method is analogous to the Python dictionary ``.keys()`` method, 
    214        except that it returns a list, not an iterator. 
    215 
    216        """ 
    217 
    218        return self._parent.keys 
    219 
    220    def iterkeys(self): 
    221        """Return a an iterator against the :meth:`.RowProxy.keys` method. 
    222 
    223        This method is analogous to the Python-2-only dictionary 
    224        ``.iterkeys()`` method. 
    225 
    226        """ 
    227        return iter(self._parent.keys) 
    228 
    229    def itervalues(self): 
    230        """Return a an iterator against the :meth:`.RowProxy.values` method. 
    231 
    232        This method is analogous to the Python-2-only dictionary 
    233        ``.itervalues()`` method. 
    234 
    235        """ 
    236        return iter(self) 
    237 
    238    def values(self): 
    239        """Return the values represented by this :class:`.RowProxy` as a list. 
    240 
    241        This method is analogous to the Python dictionary ``.values()`` method, 
    242        except that it returns a list, not an iterator. 
    243 
    244        """ 
    245        return super(RowProxy, self).values() 
    246 
    247 
    248try: 
    249    # Register RowProxy with Sequence, 
    250    # so sequence protocol is implemented 
    251    util.collections_abc.Sequence.register(RowProxy) 
    252except ImportError: 
    253    pass 
    254 
    255 
    256class ResultMetaData(object): 
    257    """Handle cursor.description, applying additional info from an execution 
    258    context.""" 
    259 
    260    __slots__ = ( 
    261        "_keymap", 
    262        "case_sensitive", 
    263        "matched_on_name", 
    264        "_processors", 
    265        "keys", 
    266        "_orig_processors", 
    267    ) 
    268 
    269    def __init__(self, parent, cursor_description): 
    270        context = parent.context 
    271        dialect = context.dialect 
    272        self.case_sensitive = dialect.case_sensitive 
    273        self.matched_on_name = False 
    274        self._orig_processors = None 
    275 
    276        if context.result_column_struct: 
    277            ( 
    278                result_columns, 
    279                cols_are_ordered, 
    280                textual_ordered, 
    281            ) = context.result_column_struct 
    282            num_ctx_cols = len(result_columns) 
    283        else: 
    284            result_columns = ( 
    285                cols_are_ordered 
    286            ) = num_ctx_cols = textual_ordered = False 
    287 
    288        # merge cursor.description with the column info 
    289        # present in the compiled structure, if any 
    290        raw = self._merge_cursor_description( 
    291            context, 
    292            cursor_description, 
    293            result_columns, 
    294            num_ctx_cols, 
    295            cols_are_ordered, 
    296            textual_ordered, 
    297        ) 
    298 
    299        self._keymap = {} 
    300        if not _baserowproxy_usecext: 
    301            # keymap indexes by integer index: this is only used 
    302            # in the pure Python BaseRowProxy.__getitem__ 
    303            # implementation to avoid an expensive 
    304            # isinstance(key, util.int_types) in the most common 
    305            # case path 
    306 
    307            len_raw = len(raw) 
    308 
    309            self._keymap.update( 
    310                [(elem[0], (elem[3], elem[4], elem[0])) for elem in raw] 
    311                + [ 
    312                    (elem[0] - len_raw, (elem[3], elem[4], elem[0])) 
    313                    for elem in raw 
    314                ] 
    315            ) 
    316 
    317        # processors in key order for certain per-row 
    318        # views like __iter__ and slices 
    319        self._processors = [elem[3] for elem in raw] 
    320 
    321        # keymap by primary string... 
    322        by_key = dict([(elem[2], (elem[3], elem[4], elem[0])) for elem in raw]) 
    323 
    324        # for compiled SQL constructs, copy additional lookup keys into 
    325        # the key lookup map, such as Column objects, labels, 
    326        # column keys and other names 
    327        if num_ctx_cols: 
    328 
    329            # if by-primary-string dictionary smaller (or bigger?!) than 
    330            # number of columns, assume we have dupes, rewrite 
    331            # dupe records with "None" for index which results in 
    332            # ambiguous column exception when accessed. 
    333            if len(by_key) != num_ctx_cols: 
    334                seen = set() 
    335                for rec in raw: 
    336                    key = rec[1] 
    337                    if key in seen: 
    338                        # this is an "ambiguous" element, replacing 
    339                        # the full record in the map 
    340                        key = key.lower() if not self.case_sensitive else key 
    341                        by_key[key] = (None, key, None) 
    342                    seen.add(key) 
    343 
    344                # copy secondary elements from compiled columns 
    345                # into self._keymap, write in the potentially "ambiguous" 
    346                # element 
    347                self._keymap.update( 
    348                    [ 
    349                        (obj_elem, by_key[elem[2]]) 
    350                        for elem in raw 
    351                        if elem[4] 
    352                        for obj_elem in elem[4] 
    353                    ] 
    354                ) 
    355 
    356                # if we did a pure positional match, then reset the 
    357                # original "expression element" back to the "unambiguous" 
    358                # entry.  This is a new behavior in 1.1 which impacts 
    359                # TextAsFrom but also straight compiled SQL constructs. 
    360                if not self.matched_on_name: 
    361                    self._keymap.update( 
    362                        [ 
    363                            (elem[4][0], (elem[3], elem[4], elem[0])) 
    364                            for elem in raw 
    365                            if elem[4] 
    366                        ] 
    367                    ) 
    368            else: 
    369                # no dupes - copy secondary elements from compiled 
    370                # columns into self._keymap 
    371                self._keymap.update( 
    372                    [ 
    373                        (obj_elem, (elem[3], elem[4], elem[0])) 
    374                        for elem in raw 
    375                        if elem[4] 
    376                        for obj_elem in elem[4] 
    377                    ] 
    378                ) 
    379 
    380        # update keymap with primary string names taking 
    381        # precedence 
    382        self._keymap.update(by_key) 
    383 
    384        # update keymap with "translated" names (sqlite-only thing) 
    385        if not num_ctx_cols and context._translate_colname: 
    386            self._keymap.update( 
    387                [(elem[5], self._keymap[elem[2]]) for elem in raw if elem[5]] 
    388            ) 
    389 
    390    def _merge_cursor_description( 
    391        self, 
    392        context, 
    393        cursor_description, 
    394        result_columns, 
    395        num_ctx_cols, 
    396        cols_are_ordered, 
    397        textual_ordered, 
    398    ): 
    399        """Merge a cursor.description with compiled result column information. 
    400 
    401        There are at least four separate strategies used here, selected 
    402        depending on the type of SQL construct used to start with. 
    403 
    404        The most common case is that of the compiled SQL expression construct, 
    405        which generated the column names present in the raw SQL string and 
    406        which has the identical number of columns as were reported by 
    407        cursor.description.  In this case, we assume a 1-1 positional mapping 
    408        between the entries in cursor.description and the compiled object. 
    409        This is also the most performant case as we disregard extracting / 
    410        decoding the column names present in cursor.description since we 
    411        already have the desired name we generated in the compiled SQL 
    412        construct. 
    413 
    414        The next common case is that of the completely raw string SQL, 
    415        such as passed to connection.execute().  In this case we have no 
    416        compiled construct to work with, so we extract and decode the 
    417        names from cursor.description and index those as the primary 
    418        result row target keys. 
    419 
    420        The remaining fairly common case is that of the textual SQL 
    421        that includes at least partial column information; this is when 
    422        we use a :class:`.TextAsFrom` construct.   This construct may have 
    423        unordered or ordered column information.  In the ordered case, we 
    424        merge the cursor.description and the compiled construct's information 
    425        positionally, and warn if there are additional description names 
    426        present, however we still decode the names in cursor.description 
    427        as we don't have a guarantee that the names in the columns match 
    428        on these.   In the unordered case, we match names in cursor.description 
    429        to that of the compiled construct based on name matching. 
    430        In both of these cases, the cursor.description names and the column 
    431        expression objects and names are indexed as result row target keys. 
    432 
    433        The final case is much less common, where we have a compiled 
    434        non-textual SQL expression construct, but the number of columns 
    435        in cursor.description doesn't match what's in the compiled 
    436        construct.  We make the guess here that there might be textual 
    437        column expressions in the compiled construct that themselves include 
    438        a comma in them causing them to split.  We do the same name-matching 
    439        as with textual non-ordered columns. 
    440 
    441        The name-matched system of merging is the same as that used by 
    442        SQLAlchemy for all cases up through te 0.9 series.   Positional 
    443        matching for compiled SQL expressions was introduced in 1.0 as a 
    444        major performance feature, and positional matching for textual 
    445        :class:`.TextAsFrom` objects in 1.1.  As name matching is no longer 
    446        a common case, it was acceptable to factor it into smaller generator- 
    447        oriented methods that are easier to understand, but incur slightly 
    448        more performance overhead. 
    449 
    450        """ 
    451 
    452        case_sensitive = context.dialect.case_sensitive 
    453 
    454        if ( 
    455            num_ctx_cols 
    456            and cols_are_ordered 
    457            and not textual_ordered 
    458            and num_ctx_cols == len(cursor_description) 
    459        ): 
    460            self.keys = [elem[0] for elem in result_columns] 
    461            # pure positional 1-1 case; doesn't need to read 
    462            # the names from cursor.description 
    463            return [ 
    464                ( 
    465                    idx, 
    466                    key, 
    467                    name.lower() if not case_sensitive else name, 
    468                    context.get_result_processor( 
    469                        type_, key, cursor_description[idx][1] 
    470                    ), 
    471                    obj, 
    472                    None, 
    473                ) 
    474                for idx, (key, name, obj, type_) in enumerate(result_columns) 
    475            ] 
    476        else: 
    477            # name-based or text-positional cases, where we need 
    478            # to read cursor.description names 
    479            if textual_ordered: 
    480                # textual positional case 
    481                raw_iterator = self._merge_textual_cols_by_position( 
    482                    context, cursor_description, result_columns 
    483                ) 
    484            elif num_ctx_cols: 
    485                # compiled SQL with a mismatch of description cols 
    486                # vs. compiled cols, or textual w/ unordered columns 
    487                raw_iterator = self._merge_cols_by_name( 
    488                    context, cursor_description, result_columns 
    489                ) 
    490            else: 
    491                # no compiled SQL, just a raw string 
    492                raw_iterator = self._merge_cols_by_none( 
    493                    context, cursor_description 
    494                ) 
    495 
    496            return [ 
    497                ( 
    498                    idx, 
    499                    colname, 
    500                    colname, 
    501                    context.get_result_processor( 
    502                        mapped_type, colname, coltype 
    503                    ), 
    504                    obj, 
    505                    untranslated, 
    506                ) 
    507                for ( 
    508                    idx, 
    509                    colname, 
    510                    mapped_type, 
    511                    coltype, 
    512                    obj, 
    513                    untranslated, 
    514                ) in raw_iterator 
    515            ] 
    516 
    517    def _colnames_from_description(self, context, cursor_description): 
    518        """Extract column names and data types from a cursor.description. 
    519 
    520        Applies unicode decoding, column translation, "normalization", 
    521        and case sensitivity rules to the names based on the dialect. 
    522 
    523        """ 
    524 
    525        dialect = context.dialect 
    526        case_sensitive = dialect.case_sensitive 
    527        translate_colname = context._translate_colname 
    528        description_decoder = ( 
    529            dialect._description_decoder 
    530            if dialect.description_encoding 
    531            else None 
    532        ) 
    533        normalize_name = ( 
    534            dialect.normalize_name if dialect.requires_name_normalize else None 
    535        ) 
    536        untranslated = None 
    537 
    538        self.keys = [] 
    539 
    540        for idx, rec in enumerate(cursor_description): 
    541            colname = rec[0] 
    542            coltype = rec[1] 
    543 
    544            if description_decoder: 
    545                colname = description_decoder(colname) 
    546 
    547            if translate_colname: 
    548                colname, untranslated = translate_colname(colname) 
    549 
    550            if normalize_name: 
    551                colname = normalize_name(colname) 
    552 
    553            self.keys.append(colname) 
    554            if not case_sensitive: 
    555                colname = colname.lower() 
    556 
    557            yield idx, colname, untranslated, coltype 
    558 
    559    def _merge_textual_cols_by_position( 
    560        self, context, cursor_description, result_columns 
    561    ): 
    562        num_ctx_cols = len(result_columns) if result_columns else None 
    563 
    564        if num_ctx_cols > len(cursor_description): 
    565            util.warn( 
    566                "Number of columns in textual SQL (%d) is " 
    567                "smaller than number of columns requested (%d)" 
    568                % (num_ctx_cols, len(cursor_description)) 
    569            ) 
    570        seen = set() 
    571        for ( 
    572            idx, 
    573            colname, 
    574            untranslated, 
    575            coltype, 
    576        ) in self._colnames_from_description(context, cursor_description): 
    577            if idx < num_ctx_cols: 
    578                ctx_rec = result_columns[idx] 
    579                obj = ctx_rec[2] 
    580                mapped_type = ctx_rec[3] 
    581                if obj[0] in seen: 
    582                    raise exc.InvalidRequestError( 
    583                        "Duplicate column expression requested " 
    584                        "in textual SQL: %r" % obj[0] 
    585                    ) 
    586                seen.add(obj[0]) 
    587            else: 
    588                mapped_type = sqltypes.NULLTYPE 
    589                obj = None 
    590 
    591            yield idx, colname, mapped_type, coltype, obj, untranslated 
    592 
    593    def _merge_cols_by_name(self, context, cursor_description, result_columns): 
    594        dialect = context.dialect 
    595        case_sensitive = dialect.case_sensitive 
    596        result_map = self._create_result_map(result_columns, case_sensitive) 
    597 
    598        self.matched_on_name = True 
    599        for ( 
    600            idx, 
    601            colname, 
    602            untranslated, 
    603            coltype, 
    604        ) in self._colnames_from_description(context, cursor_description): 
    605            try: 
    606                ctx_rec = result_map[colname] 
    607            except KeyError: 
    608                mapped_type = sqltypes.NULLTYPE 
    609                obj = None 
    610            else: 
    611                obj = ctx_rec[1] 
    612                mapped_type = ctx_rec[2] 
    613            yield idx, colname, mapped_type, coltype, obj, untranslated 
    614 
    615    def _merge_cols_by_none(self, context, cursor_description): 
    616        for ( 
    617            idx, 
    618            colname, 
    619            untranslated, 
    620            coltype, 
    621        ) in self._colnames_from_description(context, cursor_description): 
    622            yield idx, colname, sqltypes.NULLTYPE, coltype, None, untranslated 
    623 
    624    @classmethod 
    625    def _create_result_map(cls, result_columns, case_sensitive=True): 
    626        d = {} 
    627        for elem in result_columns: 
    628            key, rec = elem[0], elem[1:] 
    629            if not case_sensitive: 
    630                key = key.lower() 
    631            if key in d: 
    632                # conflicting keyname, just double up the list 
    633                # of objects.  this will cause an "ambiguous name" 
    634                # error if an attempt is made by the result set to 
    635                # access. 
    636                e_name, e_obj, e_type = d[key] 
    637                d[key] = e_name, e_obj + rec[1], e_type 
    638            else: 
    639                d[key] = rec 
    640        return d 
    641 
    642    def _key_fallback(self, key, err, raiseerr=True): 
    643        map_ = self._keymap 
    644        result = None 
    645        if isinstance(key, util.string_types): 
    646            result = map_.get(key if self.case_sensitive else key.lower()) 
    647        # fallback for targeting a ColumnElement to a textual expression 
    648        # this is a rare use case which only occurs when matching text() 
    649        # or colummn('name') constructs to ColumnElements, or after a 
    650        # pickle/unpickle roundtrip 
    651        elif isinstance(key, expression.ColumnElement): 
    652            if ( 
    653                key._label 
    654                and (key._label if self.case_sensitive else key._label.lower()) 
    655                in map_ 
    656            ): 
    657                result = map_[ 
    658                    key._label if self.case_sensitive else key._label.lower() 
    659                ] 
    660            elif ( 
    661                hasattr(key, "name") 
    662                and (key.name if self.case_sensitive else key.name.lower()) 
    663                in map_ 
    664            ): 
    665                # match is only on name. 
    666                result = map_[ 
    667                    key.name if self.case_sensitive else key.name.lower() 
    668                ] 
    669            # search extra hard to make sure this 
    670            # isn't a column/label name overlap. 
    671            # this check isn't currently available if the row 
    672            # was unpickled. 
    673            if result is not None and result[1] is not None: 
    674                for obj in result[1]: 
    675                    if key._compare_name_for_result(obj): 
    676                        break 
    677                else: 
    678                    result = None 
    679        if result is None: 
    680            if raiseerr: 
    681                util.raise_( 
    682                    exc.NoSuchColumnError( 
    683                        "Could not locate column in row for column '%s'" 
    684                        % expression._string_or_unprintable(key) 
    685                    ), 
    686                    replace_context=err, 
    687                ) 
    688            else: 
    689                return None 
    690        else: 
    691            map_[key] = result 
    692        return result 
    693 
    694    def _has_key(self, key): 
    695        if key in self._keymap: 
    696            return True 
    697        else: 
    698            return self._key_fallback(key, None, False) is not None 
    699 
    700    def _getter(self, key, raiseerr=True): 
    701        if key in self._keymap: 
    702            processor, obj, index = self._keymap[key] 
    703        else: 
    704            ret = self._key_fallback(key, None, raiseerr) 
    705            if ret is None: 
    706                return None 
    707            processor, obj, index = ret 
    708 
    709        if index is None: 
    710            util.raise_( 
    711                exc.InvalidRequestError( 
    712                    "Ambiguous column name '%s' in " 
    713                    "result set column descriptions" % obj 
    714                ), 
    715                from_=None, 
    716            ) 
    717 
    718        return operator.itemgetter(index) 
    719 
    720    def __getstate__(self): 
    721        return { 
    722            "_pickled_keymap": dict( 
    723                (key, index) 
    724                for key, (processor, obj, index) in self._keymap.items() 
    725                if isinstance(key, util.string_types + util.int_types) 
    726            ), 
    727            "keys": self.keys, 
    728            "case_sensitive": self.case_sensitive, 
    729            "matched_on_name": self.matched_on_name, 
    730        } 
    731 
    732    def __setstate__(self, state): 
    733        # the row has been processed at pickling time so we don't need any 
    734        # processor anymore 
    735        self._processors = [None for _ in range(len(state["keys"]))] 
    736        self._keymap = keymap = {} 
    737        for key, index in state["_pickled_keymap"].items(): 
    738            # not preserving "obj" here, unfortunately our 
    739            # proxy comparison fails with the unpickle 
    740            keymap[key] = (None, None, index) 
    741        self.keys = state["keys"] 
    742        self.case_sensitive = state["case_sensitive"] 
    743        self.matched_on_name = state["matched_on_name"] 
    744 
    745 
    746class ResultProxy(object): 
    747    """A facade around a DBAPI cursor object. 
    748 
    749    Returns database rows via the :class:`.RowProxy` class, which provides 
    750    additional API features and behaviors on top of the raw data returned 
    751    by the DBAPI. 
    752 
    753    .. seealso:: 
    754 
    755        :ref:`coretutorial_selecting` - introductory material for accessing 
    756        :class:`_engine.ResultProxy` and :class:`.RowProxy` objects. 
    757 
    758    """ 
    759 
    760    _process_row = RowProxy 
    761    out_parameters = None 
    762    _autoclose_connection = False 
    763    _metadata = None 
    764    _soft_closed = False 
    765    closed = False 
    766 
    767    def __init__(self, context): 
    768        self.context = context 
    769        self.dialect = context.dialect 
    770        self.cursor = self._saved_cursor = context.cursor 
    771        self.connection = context.root_connection 
    772        self._echo = ( 
    773            self.connection._echo and context.engine._should_log_debug() 
    774        ) 
    775        self._init_metadata() 
    776 
    777    def _getter(self, key, raiseerr=True): 
    778        try: 
    779            getter = self._metadata._getter 
    780        except AttributeError as err: 
    781            return self._non_result(None, err) 
    782        else: 
    783            return getter(key, raiseerr) 
    784 
    785    def _has_key(self, key): 
    786        try: 
    787            has_key = self._metadata._has_key 
    788        except AttributeError as err: 
    789            return self._non_result(None, err) 
    790        else: 
    791            return has_key(key) 
    792 
    793    def _init_metadata(self): 
    794        cursor_description = self._cursor_description() 
    795        if cursor_description is not None: 
    796            if ( 
    797                self.context.compiled 
    798                and "compiled_cache" in self.context.execution_options 
    799            ): 
    800                if self.context.compiled._cached_metadata: 
    801                    self._metadata = self.context.compiled._cached_metadata 
    802                else: 
    803                    self._metadata = ( 
    804                        self.context.compiled._cached_metadata 
    805                    ) = ResultMetaData(self, cursor_description) 
    806            else: 
    807                self._metadata = ResultMetaData(self, cursor_description) 
    808            if self._echo: 
    809                self.context.engine.logger.debug( 
    810                    "Col %r", tuple(x[0] for x in cursor_description) 
    811                ) 
    812 
    813    def keys(self): 
    814        """Return the list of string keys that would represented by each 
    815        :class:`.RowProxy`.""" 
    816 
    817        if self._metadata: 
    818            return self._metadata.keys 
    819        else: 
    820            return [] 
    821 
    822    @util.memoized_property 
    823    def rowcount(self): 
    824        """Return the 'rowcount' for this result. 
    825 
    826        The 'rowcount' reports the number of rows *matched* 
    827        by the WHERE criterion of an UPDATE or DELETE statement. 
    828 
    829        .. note:: 
    830 
    831           Notes regarding :attr:`_engine.ResultProxy.rowcount`: 
    832 
    833 
    834           * This attribute returns the number of rows *matched*, 
    835             which is not necessarily the same as the number of rows 
    836             that were actually *modified* - an UPDATE statement, for example, 
    837             may have no net change on a given row if the SET values 
    838             given are the same as those present in the row already. 
    839             Such a row would be matched but not modified. 
    840             On backends that feature both styles, such as MySQL, 
    841             rowcount is configured by default to return the match 
    842             count in all cases. 
    843 
    844           * :attr:`_engine.ResultProxy.rowcount` 
    845             is *only* useful in conjunction 
    846             with an UPDATE or DELETE statement.  Contrary to what the Python 
    847             DBAPI says, it does *not* return the 
    848             number of rows available from the results of a SELECT statement 
    849             as DBAPIs cannot support this functionality when rows are 
    850             unbuffered. 
    851 
    852           * :attr:`_engine.ResultProxy.rowcount` 
    853             may not be fully implemented by 
    854             all dialects.  In particular, most DBAPIs do not support an 
    855             aggregate rowcount result from an executemany call. 
    856             The :meth:`_engine.ResultProxy.supports_sane_rowcount` and 
    857             :meth:`_engine.ResultProxy.supports_sane_multi_rowcount` methods 
    858             will report from the dialect if each usage is known to be 
    859             supported. 
    860 
    861           * Statements that use RETURNING may not return a correct 
    862             rowcount. 
    863 
    864        """ 
    865        try: 
    866            return self.context.rowcount 
    867        except BaseException as e: 
    868            self.connection._handle_dbapi_exception( 
    869                e, None, None, self.cursor, self.context 
    870            ) 
    871 
    872    @property 
    873    def lastrowid(self): 
    874        """Return the 'lastrowid' accessor on the DBAPI cursor. 
    875 
    876        This is a DBAPI specific method and is only functional 
    877        for those backends which support it, for statements 
    878        where it is appropriate.  It's behavior is not 
    879        consistent across backends. 
    880 
    881        Usage of this method is normally unnecessary when 
    882        using insert() expression constructs; the 
    883        :attr:`~ResultProxy.inserted_primary_key` attribute provides a 
    884        tuple of primary key values for a newly inserted row, 
    885        regardless of database backend. 
    886 
    887        """ 
    888        try: 
    889            return self._saved_cursor.lastrowid 
    890        except BaseException as e: 
    891            self.connection._handle_dbapi_exception( 
    892                e, None, None, self._saved_cursor, self.context 
    893            ) 
    894 
    895    @property 
    896    def returns_rows(self): 
    897        """True if this :class:`_engine.ResultProxy` returns rows. 
    898 
    899        I.e. if it is legal to call the methods 
    900        :meth:`_engine.ResultProxy.fetchone`, 
    901        :meth:`_engine.ResultProxy.fetchmany` 
    902        :meth:`_engine.ResultProxy.fetchall`. 
    903 
    904        """ 
    905        return self._metadata is not None 
    906 
    907    @property 
    908    def is_insert(self): 
    909        """True if this :class:`_engine.ResultProxy` is the result 
    910        of a executing an expression language compiled 
    911        :func:`_expression.insert` construct. 
    912 
    913        When True, this implies that the 
    914        :attr:`inserted_primary_key` attribute is accessible, 
    915        assuming the statement did not include 
    916        a user defined "returning" construct. 
    917 
    918        """ 
    919        return self.context.isinsert 
    920 
    921    def _cursor_description(self): 
    922        """May be overridden by subclasses.""" 
    923 
    924        return self._saved_cursor.description 
    925 
    926    def _soft_close(self): 
    927        """Soft close this :class:`_engine.ResultProxy`. 
    928 
    929        This releases all DBAPI cursor resources, but leaves the 
    930        ResultProxy "open" from a semantic perspective, meaning the 
    931        fetchXXX() methods will continue to return empty results. 
    932 
    933        This method is called automatically when: 
    934 
    935        * all result rows are exhausted using the fetchXXX() methods. 
    936        * cursor.description is None. 
    937 
    938        This method is **not public**, but is documented in order to clarify 
    939        the "autoclose" process used. 
    940 
    941        .. versionadded:: 1.0.0 
    942 
    943        .. seealso:: 
    944 
    945            :meth:`_engine.ResultProxy.close` 
    946 
    947 
    948        """ 
    949        if self._soft_closed: 
    950            return 
    951        self._soft_closed = True 
    952        cursor = self.cursor 
    953        self.connection._safe_close_cursor(cursor) 
    954        if self._autoclose_connection: 
    955            self.connection.close() 
    956        self.cursor = None 
    957 
    958    def close(self): 
    959        """Close this ResultProxy. 
    960 
    961        This closes out the underlying DBAPI cursor corresponding 
    962        to the statement execution, if one is still present.  Note that the 
    963        DBAPI cursor is automatically released when the 
    964        :class:`_engine.ResultProxy` 
    965        exhausts all available rows.  :meth:`_engine.ResultProxy.close` 
    966        is generally 
    967        an optional method except in the case when discarding a 
    968        :class:`_engine.ResultProxy` 
    969        that still has additional rows pending for fetch. 
    970 
    971        In the case of a result that is the product of 
    972        :ref:`connectionless execution <dbengine_implicit>`, 
    973        the underlying :class:`_engine.Connection` object is also closed, 
    974        which 
    975        :term:`releases` DBAPI connection resources. 
    976 
    977        After this method is called, it is no longer valid to call upon 
    978        the fetch methods, which will raise a :class:`.ResourceClosedError` 
    979        on subsequent use. 
    980 
    981        .. versionchanged:: 1.0.0 - the :meth:`_engine.ResultProxy.close` 
    982           method 
    983           has been separated out from the process that releases the underlying 
    984           DBAPI cursor resource.   The "auto close" feature of the 
    985           :class:`_engine.Connection` now performs a so-called "soft close", 
    986           which 
    987           releases the underlying DBAPI cursor, but allows the 
    988           :class:`_engine.ResultProxy` 
    989           to still behave as an open-but-exhausted 
    990           result set; the actual :meth:`_engine.ResultProxy.close` 
    991           method is never 
    992           called.    It is still safe to discard a 
    993           :class:`_engine.ResultProxy` 
    994           that has been fully exhausted without calling this method. 
    995 
    996        .. seealso:: 
    997 
    998            :ref:`connections_toplevel` 
    999 
    1000        """ 
    1001 
    1002        if not self.closed: 
    1003            self._soft_close() 
    1004            self.closed = True 
    1005 
    1006    def __iter__(self): 
    1007        """Implement iteration protocol.""" 
    1008 
    1009        while True: 
    1010            row = self.fetchone() 
    1011            if row is None: 
    1012                return 
    1013            else: 
    1014                yield row 
    1015 
    1016    def __next__(self): 
    1017        """Implement the Python next() protocol. 
    1018 
    1019        This method, mirrored as both ``.next()`` and  ``.__next__()``, is part 
    1020        of Python's API for producing iterator-like behavior. 
    1021 
    1022        .. versionadded:: 1.2 
    1023 
    1024        """ 
    1025        row = self.fetchone() 
    1026        if row is None: 
    1027            raise StopIteration() 
    1028        else: 
    1029            return row 
    1030 
    1031    next = __next__ 
    1032 
    1033    @util.memoized_property 
    1034    def inserted_primary_key(self): 
    1035        """Return the primary key for the row just inserted. 
    1036 
    1037        The return value is a list of scalar values 
    1038        corresponding to the list of primary key columns 
    1039        in the target table. 
    1040 
    1041        This only applies to single row :func:`_expression.insert` 
    1042        constructs which did not explicitly specify 
    1043        :meth:`_expression.Insert.returning`. 
    1044 
    1045        Note that primary key columns which specify a 
    1046        server_default clause, 
    1047        or otherwise do not qualify as "autoincrement" 
    1048        columns (see the notes at :class:`_schema.Column`), and were 
    1049        generated using the database-side default, will 
    1050        appear in this list as ``None`` unless the backend 
    1051        supports "returning" and the insert statement executed 
    1052        with the "implicit returning" enabled. 
    1053 
    1054        Raises :class:`~sqlalchemy.exc.InvalidRequestError` if the executed 
    1055        statement is not a compiled expression construct 
    1056        or is not an insert() construct. 
    1057 
    1058        """ 
    1059 
    1060        if not self.context.compiled: 
    1061            raise exc.InvalidRequestError( 
    1062                "Statement is not a compiled " "expression construct." 
    1063            ) 
    1064        elif not self.context.isinsert: 
    1065            raise exc.InvalidRequestError( 
    1066                "Statement is not an insert() " "expression construct." 
    1067            ) 
    1068        elif self.context._is_explicit_returning: 
    1069            raise exc.InvalidRequestError( 
    1070                "Can't call inserted_primary_key " 
    1071                "when returning() " 
    1072                "is used." 
    1073            ) 
    1074 
    1075        return self.context.inserted_primary_key 
    1076 
    1077    def last_updated_params(self): 
    1078        """Return the collection of updated parameters from this 
    1079        execution. 
    1080 
    1081        Raises :class:`~sqlalchemy.exc.InvalidRequestError` if the executed 
    1082        statement is not a compiled expression construct 
    1083        or is not an update() construct. 
    1084 
    1085        """ 
    1086        if not self.context.compiled: 
    1087            raise exc.InvalidRequestError( 
    1088                "Statement is not a compiled " "expression construct." 
    1089            ) 
    1090        elif not self.context.isupdate: 
    1091            raise exc.InvalidRequestError( 
    1092                "Statement is not an update() " "expression construct." 
    1093            ) 
    1094        elif self.context.executemany: 
    1095            return self.context.compiled_parameters 
    1096        else: 
    1097            return self.context.compiled_parameters[0] 
    1098 
    1099    def last_inserted_params(self): 
    1100        """Return the collection of inserted parameters from this 
    1101        execution. 
    1102 
    1103        Raises :class:`~sqlalchemy.exc.InvalidRequestError` if the executed 
    1104        statement is not a compiled expression construct 
    1105        or is not an insert() construct. 
    1106 
    1107        """ 
    1108        if not self.context.compiled: 
    1109            raise exc.InvalidRequestError( 
    1110                "Statement is not a compiled " "expression construct." 
    1111            ) 
    1112        elif not self.context.isinsert: 
    1113            raise exc.InvalidRequestError( 
    1114                "Statement is not an insert() " "expression construct." 
    1115            ) 
    1116        elif self.context.executemany: 
    1117            return self.context.compiled_parameters 
    1118        else: 
    1119            return self.context.compiled_parameters[0] 
    1120 
    1121    @property 
    1122    def returned_defaults(self): 
    1123        """Return the values of default columns that were fetched using 
    1124        the :meth:`.ValuesBase.return_defaults` feature. 
    1125 
    1126        The value is an instance of :class:`.RowProxy`, or ``None`` 
    1127        if :meth:`.ValuesBase.return_defaults` was not used or if the 
    1128        backend does not support RETURNING. 
    1129 
    1130        .. versionadded:: 0.9.0 
    1131 
    1132        .. seealso:: 
    1133 
    1134            :meth:`.ValuesBase.return_defaults` 
    1135 
    1136        """ 
    1137        return self.context.returned_defaults 
    1138 
    1139    def lastrow_has_defaults(self): 
    1140        """Return ``lastrow_has_defaults()`` from the underlying 
    1141        :class:`.ExecutionContext`. 
    1142 
    1143        See :class:`.ExecutionContext` for details. 
    1144 
    1145        """ 
    1146 
    1147        return self.context.lastrow_has_defaults() 
    1148 
    1149    def postfetch_cols(self): 
    1150        """Return ``postfetch_cols()`` from the underlying 
    1151        :class:`.ExecutionContext`. 
    1152 
    1153        See :class:`.ExecutionContext` for details. 
    1154 
    1155        Raises :class:`~sqlalchemy.exc.InvalidRequestError` if the executed 
    1156        statement is not a compiled expression construct 
    1157        or is not an insert() or update() construct. 
    1158 
    1159        """ 
    1160 
    1161        if not self.context.compiled: 
    1162            raise exc.InvalidRequestError( 
    1163                "Statement is not a compiled " "expression construct." 
    1164            ) 
    1165        elif not self.context.isinsert and not self.context.isupdate: 
    1166            raise exc.InvalidRequestError( 
    1167                "Statement is not an insert() or update() " 
    1168                "expression construct." 
    1169            ) 
    1170        return self.context.postfetch_cols 
    1171 
    1172    def prefetch_cols(self): 
    1173        """Return ``prefetch_cols()`` from the underlying 
    1174        :class:`.ExecutionContext`. 
    1175 
    1176        See :class:`.ExecutionContext` for details. 
    1177 
    1178        Raises :class:`~sqlalchemy.exc.InvalidRequestError` if the executed 
    1179        statement is not a compiled expression construct 
    1180        or is not an insert() or update() construct. 
    1181 
    1182        """ 
    1183 
    1184        if not self.context.compiled: 
    1185            raise exc.InvalidRequestError( 
    1186                "Statement is not a compiled " "expression construct." 
    1187            ) 
    1188        elif not self.context.isinsert and not self.context.isupdate: 
    1189            raise exc.InvalidRequestError( 
    1190                "Statement is not an insert() or update() " 
    1191                "expression construct." 
    1192            ) 
    1193        return self.context.prefetch_cols 
    1194 
    1195    def supports_sane_rowcount(self): 
    1196        """Return ``supports_sane_rowcount`` from the dialect. 
    1197 
    1198        See :attr:`_engine.ResultProxy.rowcount` for background. 
    1199 
    1200        """ 
    1201 
    1202        return self.dialect.supports_sane_rowcount 
    1203 
    1204    def supports_sane_multi_rowcount(self): 
    1205        """Return ``supports_sane_multi_rowcount`` from the dialect. 
    1206 
    1207        See :attr:`_engine.ResultProxy.rowcount` for background. 
    1208 
    1209        """ 
    1210 
    1211        return self.dialect.supports_sane_multi_rowcount 
    1212 
    1213    def _fetchone_impl(self): 
    1214        try: 
    1215            return self.cursor.fetchone() 
    1216        except AttributeError as err: 
    1217            return self._non_result(None, err) 
    1218 
    1219    def _fetchmany_impl(self, size=None): 
    1220        try: 
    1221            if size is None: 
    1222                return self.cursor.fetchmany() 
    1223            else: 
    1224                return self.cursor.fetchmany(size) 
    1225        except AttributeError as err: 
    1226            return self._non_result([], err) 
    1227 
    1228    def _fetchall_impl(self): 
    1229        try: 
    1230            return self.cursor.fetchall() 
    1231        except AttributeError as err: 
    1232            return self._non_result([], err) 
    1233 
    1234    def _non_result(self, default, err=None): 
    1235        if self._metadata is None: 
    1236            util.raise_( 
    1237                exc.ResourceClosedError( 
    1238                    "This result object does not return rows. " 
    1239                    "It has been closed automatically." 
    1240                ), 
    1241                replace_context=err, 
    1242            ) 
    1243        elif self.closed: 
    1244            util.raise_( 
    1245                exc.ResourceClosedError("This result object is closed."), 
    1246                replace_context=err, 
    1247            ) 
    1248        else: 
    1249            return default 
    1250 
    1251    def process_rows(self, rows): 
    1252        process_row = self._process_row 
    1253        metadata = self._metadata 
    1254        keymap = metadata._keymap 
    1255        processors = metadata._processors 
    1256        if self._echo: 
    1257            log = self.context.engine.logger.debug 
    1258            l = [] 
    1259            for row in rows: 
    1260                log("Row %r", sql_util._repr_row(row)) 
    1261                l.append(process_row(metadata, row, processors, keymap)) 
    1262            return l 
    1263        else: 
    1264            return [ 
    1265                process_row(metadata, row, processors, keymap) for row in rows 
    1266            ] 
    1267 
    1268    def fetchall(self): 
    1269        """Fetch all rows, just like DB-API ``cursor.fetchall()``. 
    1270 
    1271        After all rows have been exhausted, the underlying DBAPI 
    1272        cursor resource is released, and the object may be safely 
    1273        discarded. 
    1274 
    1275        Subsequent calls to :meth:`_engine.ResultProxy.fetchall` will return 
    1276        an empty list.   After the :meth:`_engine.ResultProxy.close` method is 
    1277        called, the method will raise :class:`.ResourceClosedError`. 
    1278 
    1279        :return: a list of :class:`.RowProxy` objects 
    1280 
    1281        """ 
    1282 
    1283        try: 
    1284            l = self.process_rows(self._fetchall_impl()) 
    1285            self._soft_close() 
    1286            return l 
    1287        except BaseException as e: 
    1288            self.connection._handle_dbapi_exception( 
    1289                e, None, None, self.cursor, self.context 
    1290            ) 
    1291 
    1292    def fetchmany(self, size=None): 
    1293        """Fetch many rows, just like DB-API 
    1294        ``cursor.fetchmany(size=cursor.arraysize)``. 
    1295 
    1296        After all rows have been exhausted, the underlying DBAPI 
    1297        cursor resource is released, and the object may be safely 
    1298        discarded. 
    1299 
    1300        Calls to :meth:`_engine.ResultProxy.fetchmany` 
    1301        after all rows have been 
    1302        exhausted will return 
    1303        an empty list.   After the :meth:`_engine.ResultProxy.close` method is 
    1304        called, the method will raise :class:`.ResourceClosedError`. 
    1305 
    1306        :return: a list of :class:`.RowProxy` objects 
    1307 
    1308        """ 
    1309 
    1310        try: 
    1311            l = self.process_rows(self._fetchmany_impl(size)) 
    1312            if len(l) == 0: 
    1313                self._soft_close() 
    1314            return l 
    1315        except BaseException as e: 
    1316            self.connection._handle_dbapi_exception( 
    1317                e, None, None, self.cursor, self.context 
    1318            ) 
    1319 
    1320    def fetchone(self): 
    1321        """Fetch one row, just like DB-API ``cursor.fetchone()``. 
    1322 
    1323        After all rows have been exhausted, the underlying DBAPI 
    1324        cursor resource is released, and the object may be safely 
    1325        discarded. 
    1326 
    1327        Calls to :meth:`_engine.ResultProxy.fetchone` after all rows have 
    1328        been exhausted will return ``None``. 
    1329        After the :meth:`_engine.ResultProxy.close` method is 
    1330        called, the method will raise :class:`.ResourceClosedError`. 
    1331 
    1332        :return: a :class:`.RowProxy` object, or None if no rows remain 
    1333 
    1334        """ 
    1335        try: 
    1336            row = self._fetchone_impl() 
    1337            if row is not None: 
    1338                return self.process_rows([row])[0] 
    1339            else: 
    1340                self._soft_close() 
    1341                return None 
    1342        except BaseException as e: 
    1343            self.connection._handle_dbapi_exception( 
    1344                e, None, None, self.cursor, self.context 
    1345            ) 
    1346 
    1347    def first(self): 
    1348        """Fetch the first row and then close the result set unconditionally. 
    1349 
    1350        After calling this method, the object is fully closed, 
    1351        e.g. the :meth:`_engine.ResultProxy.close` 
    1352        method will have been called. 
    1353 
    1354        :return: a :class:`.RowProxy` object, or None if no rows remain 
    1355 
    1356        """ 
    1357        if self._metadata is None: 
    1358            return self._non_result(None) 
    1359 
    1360        try: 
    1361            row = self._fetchone_impl() 
    1362        except BaseException as e: 
    1363            self.connection._handle_dbapi_exception( 
    1364                e, None, None, self.cursor, self.context 
    1365            ) 
    1366 
    1367        try: 
    1368            if row is not None: 
    1369                return self.process_rows([row])[0] 
    1370            else: 
    1371                return None 
    1372        finally: 
    1373            self.close() 
    1374 
    1375    def scalar(self): 
    1376        """Fetch the first column of the first row, and close the result set. 
    1377 
    1378        After calling this method, the object is fully closed, 
    1379        e.g. the :meth:`_engine.ResultProxy.close` 
    1380        method will have been called. 
    1381 
    1382        :return: a Python scalar value , or None if no rows remain 
    1383 
    1384        """ 
    1385        row = self.first() 
    1386        if row is not None: 
    1387            return row[0] 
    1388        else: 
    1389            return None 
    1390 
    1391 
    1392class BufferedRowResultProxy(ResultProxy): 
    1393    """A ResultProxy with row buffering behavior. 
    1394 
    1395    ``ResultProxy`` that buffers the contents of a selection of rows 
    1396    before ``fetchone()`` is called.  This is to allow the results of 
    1397    ``cursor.description`` to be available immediately, when 
    1398    interfacing with a DB-API that requires rows to be consumed before 
    1399    this information is available (currently psycopg2, when used with 
    1400    server-side cursors). 
    1401 
    1402    The pre-fetching behavior fetches only one row initially, and then 
    1403    grows its buffer size by a fixed amount with each successive need 
    1404    for additional rows up to a size of 1000. 
    1405 
    1406    The size argument is configurable using the ``max_row_buffer`` 
    1407    execution option:: 
    1408 
    1409        with psycopg2_engine.connect() as conn: 
    1410 
    1411            result = conn.execution_options( 
    1412                stream_results=True, max_row_buffer=50 
    1413                ).execute("select * from table") 
    1414 
    1415    .. versionadded:: 1.0.6 Added the ``max_row_buffer`` option. 
    1416 
    1417    .. seealso:: 
    1418 
    1419        :ref:`psycopg2_execution_options` 
    1420    """ 
    1421 
    1422    def _init_metadata(self): 
    1423        self._max_row_buffer = self.context.execution_options.get( 
    1424            "max_row_buffer", None 
    1425        ) 
    1426        self.__buffer_rows() 
    1427        super(BufferedRowResultProxy, self)._init_metadata() 
    1428 
    1429    # this is a "growth chart" for the buffering of rows. 
    1430    # each successive __buffer_rows call will use the next 
    1431    # value in the list for the buffer size until the max 
    1432    # is reached 
    1433    size_growth = { 
    1434        1: 5, 
    1435        5: 10, 
    1436        10: 20, 
    1437        20: 50, 
    1438        50: 100, 
    1439        100: 250, 
    1440        250: 500, 
    1441        500: 1000, 
    1442    } 
    1443 
    1444    def __buffer_rows(self): 
    1445        if self.cursor is None: 
    1446            return 
    1447        size = getattr(self, "_bufsize", 1) 
    1448        self.__rowbuffer = collections.deque(self.cursor.fetchmany(size)) 
    1449        self._bufsize = self.size_growth.get(size, size) 
    1450        if self._max_row_buffer is not None: 
    1451            self._bufsize = min(self._max_row_buffer, self._bufsize) 
    1452 
    1453    def _soft_close(self, **kw): 
    1454        self.__rowbuffer.clear() 
    1455        super(BufferedRowResultProxy, self)._soft_close(**kw) 
    1456 
    1457    def _fetchone_impl(self): 
    1458        if self.cursor is None: 
    1459            return self._non_result(None) 
    1460        if not self.__rowbuffer: 
    1461            self.__buffer_rows() 
    1462            if not self.__rowbuffer: 
    1463                return None 
    1464        return self.__rowbuffer.popleft() 
    1465 
    1466    def _fetchmany_impl(self, size=None): 
    1467        if size is None: 
    1468            return self._fetchall_impl() 
    1469        result = [] 
    1470        for x in range(0, size): 
    1471            row = self._fetchone_impl() 
    1472            if row is None: 
    1473                break 
    1474            result.append(row) 
    1475        return result 
    1476 
    1477    def _fetchall_impl(self): 
    1478        if self.cursor is None: 
    1479            return self._non_result([]) 
    1480        self.__rowbuffer.extend(self.cursor.fetchall()) 
    1481        ret = self.__rowbuffer 
    1482        self.__rowbuffer = collections.deque() 
    1483        return ret 
    1484 
    1485 
    1486class FullyBufferedResultProxy(ResultProxy): 
    1487    """A result proxy that buffers rows fully upon creation. 
    1488 
    1489    Used for operations where a result is to be delivered 
    1490    after the database conversation can not be continued, 
    1491    such as MSSQL INSERT...OUTPUT after an autocommit. 
    1492 
    1493    """ 
    1494 
    1495    def _init_metadata(self): 
    1496        super(FullyBufferedResultProxy, self)._init_metadata() 
    1497        self.__rowbuffer = self._buffer_rows() 
    1498 
    1499    def _buffer_rows(self): 
    1500        return collections.deque(self.cursor.fetchall()) 
    1501 
    1502    def _soft_close(self, **kw): 
    1503        self.__rowbuffer.clear() 
    1504        super(FullyBufferedResultProxy, self)._soft_close(**kw) 
    1505 
    1506    def _fetchone_impl(self): 
    1507        if self.__rowbuffer: 
    1508            return self.__rowbuffer.popleft() 
    1509        else: 
    1510            return self._non_result(None) 
    1511 
    1512    def _fetchmany_impl(self, size=None): 
    1513        if size is None: 
    1514            return self._fetchall_impl() 
    1515        result = [] 
    1516        for x in range(0, size): 
    1517            row = self._fetchone_impl() 
    1518            if row is None: 
    1519                break 
    1520            result.append(row) 
    1521        return result 
    1522 
    1523    def _fetchall_impl(self): 
    1524        if not self.cursor: 
    1525            return self._non_result([]) 
    1526        ret = self.__rowbuffer 
    1527        self.__rowbuffer = collections.deque() 
    1528        return ret 
    1529 
    1530 
    1531class BufferedColumnRow(RowProxy): 
    1532    def __init__(self, parent, row, processors, keymap): 
    1533        # preprocess row 
    1534        row = list(row) 
    1535        # this is a tad faster than using enumerate 
    1536        index = 0 
    1537        for processor in parent._orig_processors: 
    1538            if processor is not None: 
    1539                row[index] = processor(row[index]) 
    1540            index += 1 
    1541        row = tuple(row) 
    1542        super(BufferedColumnRow, self).__init__( 
    1543            parent, row, processors, keymap 
    1544        ) 
    1545 
    1546 
    1547class BufferedColumnResultProxy(ResultProxy): 
    1548    """A ResultProxy with column buffering behavior. 
    1549 
    1550    ``ResultProxy`` that loads all columns into memory each time 
    1551    fetchone() is called.  If fetchmany() or fetchall() are called, 
    1552    the full grid of results is fetched.  This is to operate with 
    1553    databases where result rows contain "live" results that fall out 
    1554    of scope unless explicitly fetched. 
    1555 
    1556    .. versionchanged:: 1.2  This :class:`_engine.ResultProxy` is not used by 
    1557       any SQLAlchemy-included dialects. 
    1558 
    1559    """ 
    1560 
    1561    _process_row = BufferedColumnRow 
    1562 
    1563    def _init_metadata(self): 
    1564        super(BufferedColumnResultProxy, self)._init_metadata() 
    1565 
    1566        metadata = self._metadata 
    1567 
    1568        # don't double-replace the processors, in the case 
    1569        # of a cached ResultMetaData 
    1570        if metadata._orig_processors is None: 
    1571            # orig_processors will be used to preprocess each row when 
    1572            # they are constructed. 
    1573            metadata._orig_processors = metadata._processors 
    1574            # replace the all type processors by None processors. 
    1575            metadata._processors = [None for _ in range(len(metadata.keys))] 
    1576            keymap = {} 
    1577            for k, (func, obj, index) in metadata._keymap.items(): 
    1578                keymap[k] = (None, obj, index) 
    1579            metadata._keymap = keymap 
    1580 
    1581    def fetchall(self): 
    1582        # can't call cursor.fetchall(), since rows must be 
    1583        # fully processed before requesting more from the DBAPI. 
    1584        l = [] 
    1585        while True: 
    1586            row = self.fetchone() 
    1587            if row is None: 
    1588                break 
    1589            l.append(row) 
    1590        return l 
    1591 
    1592    def fetchmany(self, size=None): 
    1593        # can't call cursor.fetchmany(), since rows must be 
    1594        # fully processed before requesting more from the DBAPI. 
    1595        if size is None: 
    1596            return self.fetchall() 
    1597        l = [] 
    1598        for i in range(size): 
    1599            row = self.fetchone() 
    1600            if row is None: 
    1601                break 
    1602            l.append(row) 
    1603        return l