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

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.. seealso:: 

126 

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

128 the SQLite documentation 

129 

130.. _pysqlite_regexp: 

131 

132Regular Expression Support 

133--------------------------- 

134 

135.. versionadded:: 1.4 

136 

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

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

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

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

141 

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

143as follows:: 

144 

145 

146 def regexp(a, b): 

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

148 

149 

150 sqlite_connection.create_function( 

151 "regexp", 

152 2, 

153 regexp, 

154 ) 

155 

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

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

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

159details. 

160 

161.. seealso:: 

162 

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

164 

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

166 

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

168 

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

170 

171 

172 

173Compatibility with sqlite3 "native" date and datetime types 

174----------------------------------------------------------- 

175 

176The pysqlite driver includes the sqlite3.PARSE_DECLTYPES and 

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

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

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

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

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

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

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

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

185datetime/date types natively. Unfortunately, pysqlite 

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

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

188without expensive per-row type checks. 

189 

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

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

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

193 

194 engine = create_engine( 

195 "sqlite://", 

196 connect_args={ 

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

198 }, 

199 native_datetime=True, 

200 ) 

201 

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

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

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

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

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

207processing. 

208 

209.. _pysqlite_threading_pooling: 

210 

211Threading/Pooling Behavior 

212--------------------------- 

213 

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

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

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

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

218 

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

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

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

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

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

224:class:`.QueuePool`. 

225 

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

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

228 

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

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

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

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

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

234 defaults to ``True``. 

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

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

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

238 

239 .. versionchanged:: 2.0 

240 

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

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

243 may be used by specifying it via the 

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

245 

246Disabling Connection Pooling for File Databases 

247^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

248 

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

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

251parameter:: 

252 

253 from sqlalchemy import NullPool 

254 

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

256 

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

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

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

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

261issues with files being locked. 

262 

263Using a Memory Database in Multiple Threads 

264^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

265 

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

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

268only within the scope of that connection. The 

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

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

271as ``False``:: 

272 

273 from sqlalchemy.pool import StaticPool 

274 

275 engine = create_engine( 

276 "sqlite://", 

277 connect_args={"check_same_thread": False}, 

278 poolclass=StaticPool, 

279 ) 

280 

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

282version of SQLite. 

283 

284Using Temporary Tables with SQLite 

285^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

286 

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

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

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

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

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

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

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

294needed within multiple threads for this case:: 

295 

296 # maintain the same connection per thread 

297 from sqlalchemy.pool import SingletonThreadPool 

298 

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

300 

301 

302 # maintain the same connection across all threads 

303 from sqlalchemy.pool import StaticPool 

304 

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

306 

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

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

309closed out in a non deterministic way. 

310 

311 

312Dealing with Mixed String / Binary Columns 

313------------------------------------------------------ 

314 

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

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

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

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

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

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

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

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

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

324table will not be consistently readable because SQLAlchemy's 

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

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

327 

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

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

330 

331 from sqlalchemy import String 

332 from sqlalchemy import TypeDecorator 

333 

334 

335 class MixedBinary(TypeDecorator): 

336 impl = String 

337 cache_ok = True 

338 

339 def process_result_value(self, value, dialect): 

340 if isinstance(value, str): 

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

342 elif value is not None: 

343 value = bytes(value) 

344 

345 return value 

346 

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

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

349 

350.. _pysqlite_serializable: 

351 

352Serializable isolation / Savepoints / Transactional DDL 

353------------------------------------------------------- 

354 

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

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

357:ref:`sqlite_transactions`. 

358 

359 

360.. _pysqlite_udfs: 

361 

362User-Defined Functions 

363---------------------- 

364 

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

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

367These functions are registered with a specific DBAPI Connection. 

368 

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

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

371 

372 from sqlalchemy import create_engine 

373 from sqlalchemy import event 

374 from sqlalchemy import text 

375 

376 

377 def udf(): 

378 return "udf-ok" 

379 

380 

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

382 

383 

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

385 def connect(conn, rec): 

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

387 

388 

389 for i in range(5): 

390 with engine.connect() as conn: 

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

392 

393""" # noqa 

394from __future__ import annotations 

395 

396import math 

397import os 

398import re 

399from typing import cast 

400from typing import Optional 

401from typing import TYPE_CHECKING 

402from typing import Union 

403 

404from .base import DATE 

405from .base import DATETIME 

406from .base import SQLiteDialect 

407from ... import exc 

408from ... import pool 

409from ... import types as sqltypes 

410from ... import util 

411 

412if TYPE_CHECKING: 

413 from ...engine.interfaces import DBAPIConnection 

414 from ...engine.interfaces import DBAPICursor 

415 from ...engine.interfaces import DBAPIModule 

416 from ...engine.url import URL 

417 from ...pool.base import PoolProxiedConnection 

418 

419 

420class _SQLite_pysqliteTimeStamp(DATETIME): 

421 def bind_processor(self, dialect): 

422 if dialect.native_datetime: 

423 return None 

424 else: 

425 return DATETIME.bind_processor(self, dialect) 

426 

427 def result_processor(self, dialect, coltype): 

428 if dialect.native_datetime: 

429 return None 

430 else: 

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

432 

433 

434class _SQLite_pysqliteDate(DATE): 

435 def bind_processor(self, dialect): 

436 if dialect.native_datetime: 

437 return None 

438 else: 

439 return DATE.bind_processor(self, dialect) 

440 

441 def result_processor(self, dialect, coltype): 

442 if dialect.native_datetime: 

443 return None 

444 else: 

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

446 

447 

448class SQLiteDialect_pysqlite(SQLiteDialect): 

449 default_paramstyle = "qmark" 

450 supports_statement_cache = True 

451 returns_native_bytes = True 

452 

453 colspecs = util.update_copy( 

454 SQLiteDialect.colspecs, 

455 { 

456 sqltypes.Date: _SQLite_pysqliteDate, 

457 sqltypes.TIMESTAMP: _SQLite_pysqliteTimeStamp, 

458 }, 

459 ) 

460 

461 description_encoding = None 

462 

463 driver = "pysqlite" 

464 

465 @classmethod 

466 def import_dbapi(cls): 

467 from sqlite3 import dbapi2 as sqlite 

468 

469 return sqlite 

470 

471 @classmethod 

472 def _is_url_file_db(cls, url: URL): 

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

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

475 ): 

476 return True 

477 else: 

478 return False 

479 

480 @classmethod 

481 def get_pool_class(cls, url): 

482 if cls._is_url_file_db(url): 

483 return pool.QueuePool 

484 else: 

485 return pool.SingletonThreadPool 

486 

487 def _get_server_version_info(self, connection): 

488 return self.dbapi.sqlite_version_info 

489 

490 _isolation_lookup = SQLiteDialect._isolation_lookup.union( 

491 { 

492 "AUTOCOMMIT": None, 

493 } 

494 ) 

495 

496 def set_isolation_level(self, dbapi_connection, level): 

497 if level == "AUTOCOMMIT": 

498 dbapi_connection.isolation_level = None 

499 else: 

500 dbapi_connection.isolation_level = "" 

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

502 

503 def detect_autocommit_setting(self, dbapi_connection): 

504 return dbapi_connection.isolation_level is None 

505 

506 def on_connect(self): 

507 def regexp(a, b): 

508 if b is None: 

509 return None 

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

511 

512 if self._get_server_version_info(None) >= (3, 9): 

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

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

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

516 # with following 3.8 sqlite versions 

517 create_func_kw = {"deterministic": True} 

518 else: 

519 create_func_kw = {} 

520 

521 def set_regexp(dbapi_connection): 

522 dbapi_connection.create_function( 

523 "regexp", 2, regexp, **create_func_kw 

524 ) 

525 

526 def floor_func(dbapi_connection): 

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

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

529 # for now. 

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

531 dbapi_connection.create_function( 

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

533 ) 

534 

535 fns = [set_regexp, floor_func] 

536 

537 def connect(conn): 

538 for fn in fns: 

539 fn(conn) 

540 

541 return connect 

542 

543 def create_connect_args(self, url): 

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

545 raise exc.ArgumentError( 

546 "Invalid SQLite URL: %s\n" 

547 "Valid SQLite URL forms are:\n" 

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

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

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

551 ) 

552 

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

554 # parameter names accepted by sqlite3/pysqlite, using 

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

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

557 # parameters passed to connect_args will always go to the 

558 # sqlite3/pysqlite driver. 

559 pysqlite_args = [ 

560 ("uri", bool), 

561 ("timeout", float), 

562 ("isolation_level", str), 

563 ("detect_types", int), 

564 ("check_same_thread", bool), 

565 ("cached_statements", int), 

566 ] 

567 opts = url.query 

568 pysqlite_opts = {} 

569 for key, type_ in pysqlite_args: 

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

571 

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

573 uri_opts = dict(opts) 

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

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

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

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

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

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

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

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

582 # to adjust for that here. 

583 for key, type_ in pysqlite_args: 

584 uri_opts.pop(key, None) 

585 filename = url.database 

586 if uri_opts: 

587 # sorting of keys is for unit test support 

588 filename += "?" + ( 

589 "&".join( 

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

591 for key in sorted(uri_opts) 

592 ) 

593 ) 

594 else: 

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

596 if filename != ":memory:": 

597 filename = os.path.abspath(filename) 

598 

599 pysqlite_opts.setdefault( 

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

601 ) 

602 

603 return ([filename], pysqlite_opts) 

604 

605 def is_disconnect( 

606 self, 

607 e: DBAPIModule.Error, 

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

609 cursor: Optional[DBAPICursor], 

610 ) -> bool: 

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

612 return isinstance( 

613 e, self.dbapi.ProgrammingError 

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

615 

616 

617dialect = SQLiteDialect_pysqlite 

618 

619 

620class _SQLiteDialect_pysqlite_numeric(SQLiteDialect_pysqlite): 

621 """numeric dialect for testing only 

622 

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

624 and may change at any time. 

625 

626 """ 

627 

628 supports_statement_cache = True 

629 default_paramstyle = "numeric" 

630 driver = "pysqlite_numeric" 

631 

632 _first_bind = ":1" 

633 _not_in_statement_regexp = None 

634 

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

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

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

638 

639 def create_connect_args(self, url): 

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

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

642 return arg, opts 

643 

644 def _fix_sqlite_issue_99953(self): 

645 import sqlite3 

646 

647 first_bind = self._first_bind 

648 if self._not_in_statement_regexp: 

649 nis = self._not_in_statement_regexp 

650 

651 def _test_sql(sql): 

652 m = nis.search(sql) 

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

654 

655 else: 

656 

657 def _test_sql(sql): 

658 pass 

659 

660 def _numeric_param_as_dict(parameters): 

661 if parameters: 

662 assert isinstance(parameters, tuple) 

663 return { 

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

665 } 

666 else: 

667 return () 

668 

669 class SQLiteFix99953Cursor(sqlite3.Cursor): 

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

671 _test_sql(sql) 

672 if first_bind in sql: 

673 parameters = _numeric_param_as_dict(parameters) 

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

675 

676 def executemany(self, sql, parameters): 

677 _test_sql(sql) 

678 if first_bind in sql: 

679 parameters = [ 

680 _numeric_param_as_dict(p) for p in parameters 

681 ] 

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

683 

684 class SQLiteFix99953Connection(sqlite3.Connection): 

685 def cursor(self, factory=None): 

686 if factory is None: 

687 factory = SQLiteFix99953Cursor 

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

689 

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

691 _test_sql(sql) 

692 if first_bind in sql: 

693 parameters = _numeric_param_as_dict(parameters) 

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

695 

696 def executemany(self, sql, parameters): 

697 _test_sql(sql) 

698 if first_bind in sql: 

699 parameters = [ 

700 _numeric_param_as_dict(p) for p in parameters 

701 ] 

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

703 

704 return SQLiteFix99953Connection 

705 

706 

707class _SQLiteDialect_pysqlite_dollar(_SQLiteDialect_pysqlite_numeric): 

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

709 

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

711 and may change at any time. 

712 

713 """ 

714 

715 supports_statement_cache = True 

716 default_paramstyle = "numeric_dollar" 

717 driver = "pysqlite_dollar" 

718 

719 _first_bind = "$1" 

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

721 

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

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

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