Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/pysqlite.py: 54%

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

177 statements  

1# dialects/sqlite/pysqlite.py 

2# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors 

3# <see AUTHORS file> 

4# 

5# This module is part of SQLAlchemy and is released under 

6# the MIT License: https://www.opensource.org/licenses/mit-license.php 

7# mypy: ignore-errors 

8 

9 

10r""" 

11.. dialect:: sqlite+pysqlite 

12 :name: pysqlite 

13 :dbapi: sqlite3 

14 :connectstring: sqlite+pysqlite:///file_path 

15 :url: https://docs.python.org/library/sqlite3.html 

16 

17 Note that ``pysqlite`` is the same driver as the ``sqlite3`` 

18 module included with the Python distribution. 

19 

20Driver 

21------ 

22 

23The ``sqlite3`` Python DBAPI is standard on all modern Python versions; 

24for cPython and Pypy, no additional installation is necessary. 

25 

26 

27Connect Strings 

28--------------- 

29 

30The file specification for the SQLite database is taken as the "database" 

31portion of the URL. Note that the format of a SQLAlchemy url is: 

32 

33.. sourcecode:: text 

34 

35 driver://user:pass@host/database 

36 

37This means that the actual filename to be used starts with the characters to 

38the **right** of the third slash. So connecting to a relative filepath 

39looks like:: 

40 

41 # relative path 

42 e = create_engine("sqlite:///path/to/database.db") 

43 

44An absolute path, which is denoted by starting with a slash, means you 

45need **four** slashes:: 

46 

47 # absolute path 

48 e = create_engine("sqlite:////path/to/database.db") 

49 

50To use a Windows path, regular drive specifications and backslashes can be 

51used. Double backslashes are probably needed:: 

52 

53 # absolute path on Windows 

54 e = create_engine("sqlite:///C:\\path\\to\\database.db") 

55 

56To use sqlite ``:memory:`` database specify it as the filename using 

57``sqlite:///:memory:``. It's also the default if no filepath is 

58present, specifying only ``sqlite://`` and nothing else:: 

59 

60 # in-memory database (note three slashes) 

61 e = create_engine("sqlite:///:memory:") 

62 # also in-memory database 

63 e2 = create_engine("sqlite://") 

64 

65.. _pysqlite_uri_connections: 

66 

67URI Connections 

68^^^^^^^^^^^^^^^ 

69 

70Modern versions of SQLite support an alternative system of connecting using a 

71`driver level URI <https://www.sqlite.org/uri.html>`_, which has the advantage 

72that additional driver-level arguments can be passed including options such as 

73"read only". The Python sqlite3 driver supports this mode under modern Python 

743 versions. The SQLAlchemy pysqlite driver supports this mode of use by 

75specifying "uri=true" in the URL query string. The SQLite-level "URI" is kept 

76as the "database" portion of the SQLAlchemy url (that is, following a slash):: 

77 

78 e = create_engine("sqlite:///file:path/to/database?mode=ro&uri=true") 

79 

80.. note:: The "uri=true" parameter must appear in the **query string** 

81 of the URL. It will not currently work as expected if it is only 

82 present in the :paramref:`_sa.create_engine.connect_args` 

83 parameter dictionary. 

84 

85The logic reconciles the simultaneous presence of SQLAlchemy's query string and 

86SQLite's query string by separating out the parameters that belong to the 

87Python sqlite3 driver vs. those that belong to the SQLite URI. This is 

88achieved through the use of a fixed list of parameters known to be accepted by 

89the Python side of the driver. For example, to include a URL that indicates 

90the Python sqlite3 "timeout" and "check_same_thread" parameters, along with the 

91SQLite "mode" and "nolock" parameters, they can all be passed together on the 

92query string:: 

93 

94 e = create_engine( 

95 "sqlite:///file:path/to/database?" 

96 "check_same_thread=true&timeout=10&mode=ro&nolock=1&uri=true" 

97 ) 

98 

99Above, the pysqlite / sqlite3 DBAPI would be passed arguments as:: 

100 

101 sqlite3.connect( 

102 "file:path/to/database?mode=ro&nolock=1", 

103 check_same_thread=True, 

104 timeout=10, 

105 uri=True, 

106 ) 

107 

108Regarding future parameters added to either the Python or native drivers. new 

109parameter names added to the SQLite URI scheme should be automatically 

110accommodated by this scheme. New parameter names added to the Python driver 

111side can be accommodated by specifying them in the 

112:paramref:`_sa.create_engine.connect_args` dictionary, 

113until dialect support is 

114added by SQLAlchemy. For the less likely case that the native SQLite driver 

115adds a new parameter name that overlaps with one of the existing, known Python 

116driver parameters (such as "timeout" perhaps), SQLAlchemy's dialect would 

117require adjustment for the URL scheme to continue to support this. 

118 

119As is always the case for all SQLAlchemy dialects, the entire "URL" process 

120can be bypassed in :func:`_sa.create_engine` through the use of the 

121:paramref:`_sa.create_engine.creator` 

122parameter which allows for a custom callable 

123that creates a Python sqlite3 driver level connection directly. 

124 

125.. versionadded:: 1.3.9 

126 

127.. seealso:: 

128 

129 `Uniform Resource Identifiers <https://www.sqlite.org/uri.html>`_ - in 

130 the SQLite documentation 

131 

132.. _pysqlite_regexp: 

133 

134Regular Expression Support 

135--------------------------- 

136 

137.. versionadded:: 1.4 

138 

139Support for the :meth:`_sql.ColumnOperators.regexp_match` operator is provided 

140using Python's re.search_ function. SQLite itself does not include a working 

141regular expression operator; instead, it includes a non-implemented placeholder 

142operator ``REGEXP`` that calls a user-defined function that must be provided. 

143 

144SQLAlchemy's implementation makes use of the pysqlite create_function_ hook 

145as follows:: 

146 

147 

148 def regexp(a, b): 

149 return re.search(a, b) is not None 

150 

151 

152 sqlite_connection.create_function( 

153 "regexp", 

154 2, 

155 regexp, 

156 ) 

157 

158There is currently no support for regular expression flags as a separate 

159argument, as these are not supported by SQLite's REGEXP operator, however these 

160may be included inline within the regular expression string. See `Python regular expressions`_ for 

161details. 

162 

163.. seealso:: 

164 

165 `Python regular expressions`_: Documentation for Python's regular expression syntax. 

166 

167.. _create_function: https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function 

168 

169.. _re.search: https://docs.python.org/3/library/re.html#re.search 

170 

171.. _Python regular expressions: https://docs.python.org/3/library/re.html#re.search 

172 

173 

174 

175Compatibility with sqlite3 "native" date and datetime types 

176----------------------------------------------------------- 

177 

178The pysqlite driver includes the sqlite3.PARSE_DECLTYPES and 

179sqlite3.PARSE_COLNAMES options, which have the effect of any column 

180or expression explicitly cast as "date" or "timestamp" will be converted 

181to a Python date or datetime object. The date and datetime types provided 

182with the pysqlite dialect are not currently compatible with these options, 

183since they render the ISO date/datetime including microseconds, which 

184pysqlite's driver does not. Additionally, SQLAlchemy does not at 

185this time automatically render the "cast" syntax required for the 

186freestanding functions "current_timestamp" and "current_date" to return 

187datetime/date types natively. Unfortunately, pysqlite 

188does not provide the standard DBAPI types in ``cursor.description``, 

189leaving SQLAlchemy with no way to detect these types on the fly 

190without expensive per-row type checks. 

191 

192Keeping in mind that pysqlite's parsing option is not recommended, 

193nor should be necessary, for use with SQLAlchemy, usage of PARSE_DECLTYPES 

194can be forced if one configures "native_datetime=True" on create_engine():: 

195 

196 engine = create_engine( 

197 "sqlite://", 

198 connect_args={ 

199 "detect_types": sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES 

200 }, 

201 native_datetime=True, 

202 ) 

203 

204With this flag enabled, the DATE and TIMESTAMP types (but note - not the 

205DATETIME or TIME types...confused yet ?) will not perform any bind parameter 

206or result processing. Execution of "func.current_date()" will return a string. 

207"func.current_timestamp()" is registered as returning a DATETIME type in 

208SQLAlchemy, so this function still receives SQLAlchemy-level result 

209processing. 

210 

211.. _pysqlite_threading_pooling: 

212 

213Threading/Pooling Behavior 

214--------------------------- 

215 

216The ``sqlite3`` DBAPI by default prohibits the use of a particular connection 

217in a thread which is not the one in which it was created. As SQLite has 

218matured, it's behavior under multiple threads has improved, and even includes 

219options for memory only databases to be used in multiple threads. 

220 

221The thread prohibition is known as "check same thread" and may be controlled 

222using the ``sqlite3`` parameter ``check_same_thread``, which will disable or 

223enable this check. SQLAlchemy's default behavior here is to set 

224``check_same_thread`` to ``False`` automatically whenever a file-based database 

225is in use, to establish compatibility with the default pool class 

226:class:`.QueuePool`. 

227 

228The SQLAlchemy ``pysqlite`` DBAPI establishes the connection pool differently 

229based on the kind of SQLite database that's requested: 

230 

231* When a ``:memory:`` SQLite database is specified, the dialect by default 

232 will use :class:`.SingletonThreadPool`. This pool maintains a single 

233 connection per thread, so that all access to the engine within the current 

234 thread use the same ``:memory:`` database - other threads would access a 

235 different ``:memory:`` database. The ``check_same_thread`` parameter 

236 defaults to ``True``. 

237* When a file-based database is specified, the dialect will use 

238 :class:`.QueuePool` as the source of connections. at the same time, 

239 the ``check_same_thread`` flag is set to False by default unless overridden. 

240 

241 .. versionchanged:: 2.0 

242 

243 SQLite file database engines now use :class:`.QueuePool` by default. 

244 Previously, :class:`.NullPool` were used. The :class:`.NullPool` class 

245 may be used by specifying it via the 

246 :paramref:`_sa.create_engine.poolclass` parameter. 

247 

248Disabling Connection Pooling for File Databases 

249^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

250 

251Pooling may be disabled for a file based database by specifying the 

252:class:`.NullPool` implementation for the :func:`_sa.create_engine.poolclass` 

253parameter:: 

254 

255 from sqlalchemy import NullPool 

256 

257 engine = create_engine("sqlite:///myfile.db", poolclass=NullPool) 

258 

259It's been observed that the :class:`.NullPool` implementation incurs an 

260extremely small performance overhead for repeated checkouts due to the lack of 

261connection re-use implemented by :class:`.QueuePool`. However, it still 

262may be beneficial to use this class if the application is experiencing 

263issues with files being locked. 

264 

265Using a Memory Database in Multiple Threads 

266^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

267 

268To use a ``:memory:`` database in a multithreaded scenario, the same 

269connection object must be shared among threads, since the database exists 

270only within the scope of that connection. The 

271:class:`.StaticPool` implementation will maintain a single connection 

272globally, and the ``check_same_thread`` flag can be passed to Pysqlite 

273as ``False``:: 

274 

275 from sqlalchemy.pool import StaticPool 

276 

277 engine = create_engine( 

278 "sqlite://", 

279 connect_args={"check_same_thread": False}, 

280 poolclass=StaticPool, 

281 ) 

282 

283Note that using a ``:memory:`` database in multiple threads requires a recent 

284version of SQLite. 

285 

286Using Temporary Tables with SQLite 

287^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

288 

289Due to the way SQLite deals with temporary tables, if you wish to use a 

290temporary table in a file-based SQLite database across multiple checkouts 

291from the connection pool, such as when using an ORM :class:`.Session` where 

292the temporary table should continue to remain after :meth:`.Session.commit` or 

293:meth:`.Session.rollback` is called, a pool which maintains a single 

294connection must be used. Use :class:`.SingletonThreadPool` if the scope is 

295only needed within the current thread, or :class:`.StaticPool` is scope is 

296needed within multiple threads for this case:: 

297 

298 # maintain the same connection per thread 

299 from sqlalchemy.pool import SingletonThreadPool 

300 

301 engine = create_engine("sqlite:///mydb.db", poolclass=SingletonThreadPool) 

302 

303 

304 # maintain the same connection across all threads 

305 from sqlalchemy.pool import StaticPool 

306 

307 engine = create_engine("sqlite:///mydb.db", poolclass=StaticPool) 

308 

309Note that :class:`.SingletonThreadPool` should be configured for the number 

310of threads that are to be used; beyond that number, connections will be 

311closed out in a non deterministic way. 

312 

313 

314Dealing with Mixed String / Binary Columns 

315------------------------------------------------------ 

316 

317The SQLite database is weakly typed, and as such it is possible when using 

318binary values, which in Python are represented as ``b'some string'``, that a 

319particular SQLite database can have data values within different rows where 

320some of them will be returned as a ``b''`` value by the Pysqlite driver, and 

321others will be returned as Python strings, e.g. ``''`` values. This situation 

322is not known to occur if the SQLAlchemy :class:`.LargeBinary` datatype is used 

323consistently, however if a particular SQLite database has data that was 

324inserted using the Pysqlite driver directly, or when using the SQLAlchemy 

325:class:`.String` type which was later changed to :class:`.LargeBinary`, the 

326table will not be consistently readable because SQLAlchemy's 

327:class:`.LargeBinary` datatype does not handle strings so it has no way of 

328"encoding" a value that is in string format. 

329 

330To deal with a SQLite table that has mixed string / binary data in the 

331same column, use a custom type that will check each row individually:: 

332 

333 from sqlalchemy import String 

334 from sqlalchemy import TypeDecorator 

335 

336 

337 class MixedBinary(TypeDecorator): 

338 impl = String 

339 cache_ok = True 

340 

341 def process_result_value(self, value, dialect): 

342 if isinstance(value, str): 

343 value = bytes(value, "utf-8") 

344 elif value is not None: 

345 value = bytes(value) 

346 

347 return value 

348 

349Then use the above ``MixedBinary`` datatype in the place where 

350:class:`.LargeBinary` would normally be used. 

351 

352.. _pysqlite_serializable: 

353 

354Serializable isolation / Savepoints / Transactional DDL 

355------------------------------------------------------- 

356 

357A newly revised version of this important section is now available 

358at the top level of the SQLAlchemy SQLite documentation, in the section 

359:ref:`sqlite_transactions`. 

360 

361 

362.. _pysqlite_udfs: 

363 

364User-Defined Functions 

365---------------------- 

366 

367pysqlite supports a `create_function() <https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function>`_ 

368method that allows us to create our own user-defined functions (UDFs) in Python and use them directly in SQLite queries. 

369These functions are registered with a specific DBAPI Connection. 

370 

371SQLAlchemy uses connection pooling with file-based SQLite databases, so we need to ensure that the UDF is attached to the 

372connection when it is created. That is accomplished with an event listener:: 

373 

374 from sqlalchemy import create_engine 

375 from sqlalchemy import event 

376 from sqlalchemy import text 

377 

378 

379 def udf(): 

380 return "udf-ok" 

381 

382 

383 engine = create_engine("sqlite:///./db_file") 

384 

385 

386 @event.listens_for(engine, "connect") 

387 def connect(conn, rec): 

388 conn.create_function("udf", 0, udf) 

389 

390 

391 for i in range(5): 

392 with engine.connect() as conn: 

393 print(conn.scalar(text("SELECT UDF()"))) 

394 

395""" # noqa 

396from __future__ import annotations 

397 

398import math 

399import os 

400import re 

401from typing import cast 

402from typing import Optional 

403from typing import TYPE_CHECKING 

404from typing import Union 

405 

406from .base import DATE 

407from .base import DATETIME 

408from .base import SQLiteDialect 

409from ... import exc 

410from ... import pool 

411from ... import types as sqltypes 

412from ... import util 

413 

414if TYPE_CHECKING: 

415 from ...engine.interfaces import DBAPIConnection 

416 from ...engine.interfaces import DBAPICursor 

417 from ...engine.interfaces import DBAPIModule 

418 from ...engine.url import URL 

419 from ...pool.base import PoolProxiedConnection 

420 

421 

422class _SQLite_pysqliteTimeStamp(DATETIME): 

423 def bind_processor(self, dialect): 

424 if dialect.native_datetime: 

425 return None 

426 else: 

427 return DATETIME.bind_processor(self, dialect) 

428 

429 def result_processor(self, dialect, coltype): 

430 if dialect.native_datetime: 

431 return None 

432 else: 

433 return DATETIME.result_processor(self, dialect, coltype) 

434 

435 

436class _SQLite_pysqliteDate(DATE): 

437 def bind_processor(self, dialect): 

438 if dialect.native_datetime: 

439 return None 

440 else: 

441 return DATE.bind_processor(self, dialect) 

442 

443 def result_processor(self, dialect, coltype): 

444 if dialect.native_datetime: 

445 return None 

446 else: 

447 return DATE.result_processor(self, dialect, coltype) 

448 

449 

450class SQLiteDialect_pysqlite(SQLiteDialect): 

451 default_paramstyle = "qmark" 

452 supports_statement_cache = True 

453 returns_native_bytes = True 

454 

455 colspecs = util.update_copy( 

456 SQLiteDialect.colspecs, 

457 { 

458 sqltypes.Date: _SQLite_pysqliteDate, 

459 sqltypes.TIMESTAMP: _SQLite_pysqliteTimeStamp, 

460 }, 

461 ) 

462 

463 description_encoding = None 

464 

465 driver = "pysqlite" 

466 

467 @classmethod 

468 def import_dbapi(cls): 

469 from sqlite3 import dbapi2 as sqlite 

470 

471 return sqlite 

472 

473 @classmethod 

474 def _is_url_file_db(cls, url: URL): 

475 if (url.database and url.database != ":memory:") and ( 

476 url.query.get("mode", None) != "memory" 

477 ): 

478 return True 

479 else: 

480 return False 

481 

482 @classmethod 

483 def get_pool_class(cls, url): 

484 if cls._is_url_file_db(url): 

485 return pool.QueuePool 

486 else: 

487 return pool.SingletonThreadPool 

488 

489 def _get_server_version_info(self, connection): 

490 return self.dbapi.sqlite_version_info 

491 

492 _isolation_lookup = SQLiteDialect._isolation_lookup.union( 

493 { 

494 "AUTOCOMMIT": None, 

495 } 

496 ) 

497 

498 def set_isolation_level(self, dbapi_connection, level): 

499 if level == "AUTOCOMMIT": 

500 dbapi_connection.isolation_level = None 

501 else: 

502 dbapi_connection.isolation_level = "" 

503 return super().set_isolation_level(dbapi_connection, level) 

504 

505 def detect_autocommit_setting(self, dbapi_connection): 

506 return dbapi_connection.isolation_level is None 

507 

508 def on_connect(self): 

509 def regexp(a, b): 

510 if b is None: 

511 return None 

512 return re.search(a, b) is not None 

513 

514 if util.py38 and self._get_server_version_info(None) >= (3, 9): 

515 # sqlite must be greater than 3.8.3 for deterministic=True 

516 # https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function 

517 # the check is more conservative since there were still issues 

518 # with following 3.8 sqlite versions 

519 create_func_kw = {"deterministic": True} 

520 else: 

521 create_func_kw = {} 

522 

523 def set_regexp(dbapi_connection): 

524 dbapi_connection.create_function( 

525 "regexp", 2, regexp, **create_func_kw 

526 ) 

527 

528 def floor_func(dbapi_connection): 

529 # NOTE: floor is optionally present in sqlite 3.35+ , however 

530 # as it is normally non-present we deliver floor() unconditionally 

531 # for now. 

532 # https://www.sqlite.org/lang_mathfunc.html 

533 dbapi_connection.create_function( 

534 "floor", 1, math.floor, **create_func_kw 

535 ) 

536 

537 fns = [set_regexp, floor_func] 

538 

539 def connect(conn): 

540 for fn in fns: 

541 fn(conn) 

542 

543 return connect 

544 

545 def create_connect_args(self, url): 

546 if url.username or url.password or url.host or url.port: 

547 raise exc.ArgumentError( 

548 "Invalid SQLite URL: %s\n" 

549 "Valid SQLite URL forms are:\n" 

550 " sqlite:///:memory: (or, sqlite://)\n" 

551 " sqlite:///relative/path/to/file.db\n" 

552 " sqlite:////absolute/path/to/file.db" % (url,) 

553 ) 

554 

555 # theoretically, this list can be augmented, at least as far as 

556 # parameter names accepted by sqlite3/pysqlite, using 

557 # inspect.getfullargspec(). for the moment this seems like overkill 

558 # as these parameters don't change very often, and as always, 

559 # parameters passed to connect_args will always go to the 

560 # sqlite3/pysqlite driver. 

561 pysqlite_args = [ 

562 ("uri", bool), 

563 ("timeout", float), 

564 ("isolation_level", str), 

565 ("detect_types", int), 

566 ("check_same_thread", bool), 

567 ("cached_statements", int), 

568 ] 

569 opts = url.query 

570 pysqlite_opts = {} 

571 for key, type_ in pysqlite_args: 

572 util.coerce_kw_type(opts, key, type_, dest=pysqlite_opts) 

573 

574 if pysqlite_opts.get("uri", False): 

575 uri_opts = dict(opts) 

576 # here, we are actually separating the parameters that go to 

577 # sqlite3/pysqlite vs. those that go the SQLite URI. What if 

578 # two names conflict? again, this seems to be not the case right 

579 # now, and in the case that new names are added to 

580 # either side which overlap, again the sqlite3/pysqlite parameters 

581 # can be passed through connect_args instead of in the URL. 

582 # If SQLite native URIs add a parameter like "timeout" that 

583 # we already have listed here for the python driver, then we need 

584 # to adjust for that here. 

585 for key, type_ in pysqlite_args: 

586 uri_opts.pop(key, None) 

587 filename = url.database 

588 if uri_opts: 

589 # sorting of keys is for unit test support 

590 filename += "?" + ( 

591 "&".join( 

592 "%s=%s" % (key, uri_opts[key]) 

593 for key in sorted(uri_opts) 

594 ) 

595 ) 

596 else: 

597 filename = url.database or ":memory:" 

598 if filename != ":memory:": 

599 filename = os.path.abspath(filename) 

600 

601 pysqlite_opts.setdefault( 

602 "check_same_thread", not self._is_url_file_db(url) 

603 ) 

604 

605 return ([filename], pysqlite_opts) 

606 

607 def is_disconnect( 

608 self, 

609 e: DBAPIModule.Error, 

610 connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]], 

611 cursor: Optional[DBAPICursor], 

612 ) -> bool: 

613 self.dbapi = cast("DBAPIModule", self.dbapi) 

614 return isinstance( 

615 e, self.dbapi.ProgrammingError 

616 ) and "Cannot operate on a closed database." in str(e) 

617 

618 

619dialect = SQLiteDialect_pysqlite 

620 

621 

622class _SQLiteDialect_pysqlite_numeric(SQLiteDialect_pysqlite): 

623 """numeric dialect for testing only 

624 

625 internal use only. This dialect is **NOT** supported by SQLAlchemy 

626 and may change at any time. 

627 

628 """ 

629 

630 supports_statement_cache = True 

631 default_paramstyle = "numeric" 

632 driver = "pysqlite_numeric" 

633 

634 _first_bind = ":1" 

635 _not_in_statement_regexp = None 

636 

637 def __init__(self, *arg, **kw): 

638 kw.setdefault("paramstyle", "numeric") 

639 super().__init__(*arg, **kw) 

640 

641 def create_connect_args(self, url): 

642 arg, opts = super().create_connect_args(url) 

643 opts["factory"] = self._fix_sqlite_issue_99953() 

644 return arg, opts 

645 

646 def _fix_sqlite_issue_99953(self): 

647 import sqlite3 

648 

649 first_bind = self._first_bind 

650 if self._not_in_statement_regexp: 

651 nis = self._not_in_statement_regexp 

652 

653 def _test_sql(sql): 

654 m = nis.search(sql) 

655 assert not m, f"Found {nis.pattern!r} in {sql!r}" 

656 

657 else: 

658 

659 def _test_sql(sql): 

660 pass 

661 

662 def _numeric_param_as_dict(parameters): 

663 if parameters: 

664 assert isinstance(parameters, tuple) 

665 return { 

666 str(idx): value for idx, value in enumerate(parameters, 1) 

667 } 

668 else: 

669 return () 

670 

671 class SQLiteFix99953Cursor(sqlite3.Cursor): 

672 def execute(self, sql, parameters=()): 

673 _test_sql(sql) 

674 if first_bind in sql: 

675 parameters = _numeric_param_as_dict(parameters) 

676 return super().execute(sql, parameters) 

677 

678 def executemany(self, sql, parameters): 

679 _test_sql(sql) 

680 if first_bind in sql: 

681 parameters = [ 

682 _numeric_param_as_dict(p) for p in parameters 

683 ] 

684 return super().executemany(sql, parameters) 

685 

686 class SQLiteFix99953Connection(sqlite3.Connection): 

687 def cursor(self, factory=None): 

688 if factory is None: 

689 factory = SQLiteFix99953Cursor 

690 return super().cursor(factory=factory) 

691 

692 def execute(self, sql, parameters=()): 

693 _test_sql(sql) 

694 if first_bind in sql: 

695 parameters = _numeric_param_as_dict(parameters) 

696 return super().execute(sql, parameters) 

697 

698 def executemany(self, sql, parameters): 

699 _test_sql(sql) 

700 if first_bind in sql: 

701 parameters = [ 

702 _numeric_param_as_dict(p) for p in parameters 

703 ] 

704 return super().executemany(sql, parameters) 

705 

706 return SQLiteFix99953Connection 

707 

708 

709class _SQLiteDialect_pysqlite_dollar(_SQLiteDialect_pysqlite_numeric): 

710 """numeric dialect that uses $ for testing only 

711 

712 internal use only. This dialect is **NOT** supported by SQLAlchemy 

713 and may change at any time. 

714 

715 """ 

716 

717 supports_statement_cache = True 

718 default_paramstyle = "numeric_dollar" 

719 driver = "pysqlite_dollar" 

720 

721 _first_bind = "$1" 

722 _not_in_statement_regexp = re.compile(r"[^\d]:\d+") 

723 

724 def __init__(self, *arg, **kw): 

725 kw.setdefault("paramstyle", "numeric_dollar") 

726 super().__init__(*arg, **kw)