Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/SQLAlchemy-1.3.25.dev0-py3.11-linux-x86_64.egg/sqlalchemy/engine/result.py: 33%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

585 statements  

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