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

188 statements  

1# dialects/sqlite/pysqlite.py 

2# Copyright (C) 2005-2026 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 

8 

9r""" 

10.. dialect:: sqlite+pysqlite 

11 :name: pysqlite 

12 :dbapi: sqlite3 

13 :connectstring: sqlite+pysqlite:///file_path 

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

15 

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

17 module included with the Python distribution. 

18 

19Driver 

20------ 

21 

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

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

24 

25 

26Connect Strings 

27--------------- 

28 

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

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

31 

32.. sourcecode:: text 

33 

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

35 

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

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

38looks like:: 

39 

40 # relative path 

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

42 

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

44need **four** slashes:: 

45 

46 # absolute path 

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

48 

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

50used. Double backslashes are probably needed:: 

51 

52 # absolute path on Windows 

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

54 

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

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

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

58 

59 # in-memory database (note three slashes) 

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

61 # also in-memory database 

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

63 

64.. _pysqlite_uri_connections: 

65 

66URI Connections 

67^^^^^^^^^^^^^^^ 

68 

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

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

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

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

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

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

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

76 

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

78 

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

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

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

82 parameter dictionary. 

83 

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

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

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

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

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

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

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

91query string:: 

92 

93 e = create_engine( 

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

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

96 ) 

97 

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

99 

100 sqlite3.connect( 

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

102 check_same_thread=True, 

103 timeout=10, 

104 uri=True, 

105 ) 

106 

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

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

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

110side can be accommodated by specifying them in the 

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

112until dialect support is 

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

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

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

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

117 

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

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

120:paramref:`_sa.create_engine.creator` 

121parameter which allows for a custom callable 

122that creates a Python sqlite3 driver level connection directly. 

123 

124.. versionadded:: 1.3.9 

125 

126.. seealso:: 

127 

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

129 the SQLite documentation 

130 

131.. _pysqlite_regexp: 

132 

133Regular Expression Support 

134--------------------------- 

135 

136.. versionadded:: 1.4 

137 

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

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

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

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

142 

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

144as follows:: 

145 

146 

147 def regexp(a, b): 

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

149 

150 

151 sqlite_connection.create_function( 

152 "regexp", 

153 2, 

154 regexp, 

155 ) 

156 

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

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

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

160details. 

161 

162.. seealso:: 

163 

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

165 

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

167 

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

169 

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

171 

172 

173 

174Compatibility with sqlite3 "native" date and datetime types 

175----------------------------------------------------------- 

176 

177The pysqlite driver includes the sqlite3.PARSE_DECLTYPES and 

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

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

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

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

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

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

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

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

186datetime/date types natively. Unfortunately, pysqlite 

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

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

189without expensive per-row type checks. 

190 

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

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

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

194 

195 engine = create_engine( 

196 "sqlite://", 

197 connect_args={ 

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

199 }, 

200 native_datetime=True, 

201 ) 

202 

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

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

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

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

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

208processing. 

209 

210.. _pysqlite_threading_pooling: 

211 

212Concurrency/Threading/Pooling Behavior 

213-------------------------------------- 

214 

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

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

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

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

219 

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

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

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

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

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

225:class:`.QueuePool`. 

226 

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

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

229 

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

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

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

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

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

235 defaults to ``True``. 

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

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

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

239 

240 .. versionchanged:: 2.0 

241 

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

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

244 may be used by specifying it via the 

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

246 

247Disabling Connection Pooling for File Databases 

248^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

249 

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

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

252parameter:: 

253 

254 from sqlalchemy import NullPool 

255 

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

257 

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

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

260connection reuse implemented by :class:`.QueuePool`. However, it still 

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

262issues with files being locked. 

263 

264Using a Memory Database in Multiple Threads or Coroutines 

265^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

266 

267A ``:memory:`` SQLite database exists only within the scope of a single 

268DBAPI connection. It is not possible for two separate ``sqlite3`` 

269connection objects to access the same ``:memory:`` database unless SQLite's 

270shared-cache feature is enabled. Without shared cache, each connection 

271creates its own independent in-memory database. 

272 

273This means a ``:memory:`` database is **not suitable** for use with 

274multiple concurrent threads or coroutines unless either: 

275 

276* All workers are fully serialized (mutexed) against each other 

277 such that only one is using the database at a time, or 

278* SQLite's shared-cache URI feature is used to allow multiple 

279 independent connections to access the same in-memory database. 

280 

281The same considerations apply when using the :ref:`aiosqlite <aiosqlite>` 

282dialect, which wraps ``pysqlite`` connections in an async interface — 

283without shared cache, each connection still creates its own independent 

284in-memory database. 

285 

286The recommended approach for multithreaded or async in-memory 

287use is the shared-cache URI feature, described below. 

288 

289.. _pysqlite_uri_shared_cache: 

290 

291Using a Shared-Cache Memory Database 

292~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

293 

294SQLite's 

295`URI shared-cache <https://www.sqlite.org/sharedcache.html>`_ 

296mode allows multiple independent DBAPI connections, each with 

297their own transaction state, to access the same in-memory 

298database. This is enabled by using a ``file:`` URI with 

299``cache=shared`` and passing ``uri=true`` in the query 

300string:: 

301 

302 engine = create_engine("sqlite:///file::memory:?cache=shared&uri=true") 

303 

304For async use with aiosqlite, use 

305:func:`_asyncio.create_async_engine`:: 

306 

307 engine = create_async_engine( 

308 "sqlite+aiosqlite:///file::memory:?cache=shared&uri=true" 

309 ) 

310 

311Because this URL form is treated as a file-based database by the 

312dialect, :class:`.QueuePool` is used automatically and 

313``check_same_thread`` defaults to ``False``, so no additional pool 

314or connect_args configuration is needed. Each checkout from the 

315pool is a distinct DBAPI connection with its own transaction state, 

316and the in-memory database persists as long as at least one 

317connection remains open. 

318 

319The shared-cache database is scoped by the filename component of 

320the URI. ``file::memory:`` (empty name) is process-global — all 

321engines in the process that use this URI share the same database. 

322To maintain multiple independent in-memory databases within the 

323same process, supply a distinct name for each:: 

324 

325 engine_a = create_engine( 

326 "sqlite:///file:db_a?mode=memory&cache=shared&uri=true" 

327 ) 

328 engine_b = create_engine( 

329 "sqlite:///file:db_b?mode=memory&cache=shared&uri=true" 

330 ) 

331 

332Using StaticPool for Single-Connection Memory Databases 

333~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

334 

335An older approach for sharing a ``:memory:`` database among threads 

336is to force all checkouts to return the same DBAPI connection using 

337:class:`.StaticPool`. **This approach does not support any form of 

338concurrency** and is only useful when all access to the engine is 

339fully serialized, such as in single-threaded test suites:: 

340 

341 from sqlalchemy.pool import StaticPool 

342 

343 engine = create_engine( 

344 "sqlite://", 

345 connect_args={"check_same_thread": False}, 

346 poolclass=StaticPool, 

347 ) 

348 

349.. warning:: 

350 

351 Because :class:`.StaticPool` maintains a single DBAPI connection, 

352 all :class:`.Session` or :class:`.Connection` objects that use 

353 this engine share that one underlying connection and its single 

354 SQLite transaction state. A ``ROLLBACK`` issued by one session 

355 (e.g. during error handling) will also roll back uncommitted work 

356 from any other session, and concurrent ``COMMIT`` / ``ROLLBACK`` 

357 calls can interfere with each other unpredictably. This approach 

358 is only appropriate when access to the engine is fully serialized, 

359 such as in single-threaded test suites. For concurrent workloads, 

360 use the shared-cache URI approach described above. 

361 

362Using Temporary Tables with SQLite 

363^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

364 

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

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

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

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

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

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

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

372needed within multiple threads for this case:: 

373 

374 # maintain the same connection per thread 

375 from sqlalchemy.pool import SingletonThreadPool 

376 

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

378 

379 

380 # maintain the same connection across all threads 

381 from sqlalchemy.pool import StaticPool 

382 

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

384 

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

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

387closed out in a non deterministic way. 

388 

389 

390Dealing with Mixed String / Binary Columns 

391------------------------------------------------------ 

392 

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

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

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

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

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

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

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

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

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

402table will not be consistently readable because SQLAlchemy's 

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

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

405 

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

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

408 

409 from sqlalchemy import String 

410 from sqlalchemy import TypeDecorator 

411 

412 

413 class MixedBinary(TypeDecorator): 

414 impl = String 

415 cache_ok = True 

416 

417 def process_result_value(self, value, dialect): 

418 if isinstance(value, str): 

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

420 elif value is not None: 

421 value = bytes(value) 

422 

423 return value 

424 

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

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

427 

428.. _pysqlite_serializable: 

429 

430Serializable isolation / Savepoints / Transactional DDL 

431------------------------------------------------------- 

432 

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

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

435:ref:`sqlite_transactions`. 

436 

437 

438.. _pysqlite_udfs: 

439 

440User-Defined Functions 

441---------------------- 

442 

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

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

445These functions are registered with a specific DBAPI Connection. 

446 

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

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

449 

450 from sqlalchemy import create_engine 

451 from sqlalchemy import event 

452 from sqlalchemy import text 

453 

454 

455 def udf(): 

456 return "udf-ok" 

457 

458 

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

460 

461 

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

463 def connect(conn, rec): 

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

465 

466 

467 for i in range(5): 

468 with engine.connect() as conn: 

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

470 

471""" # noqa 

472 

473from __future__ import annotations 

474 

475import math 

476import os 

477import re 

478from typing import Any 

479from typing import Callable 

480from typing import cast 

481from typing import Optional 

482from typing import Pattern 

483from typing import TYPE_CHECKING 

484from typing import TypeVar 

485from typing import Union 

486 

487from .base import DATE 

488from .base import DATETIME 

489from .base import SQLiteDialect 

490from ... import exc 

491from ... import pool 

492from ... import types as sqltypes 

493from ... import util 

494from ...util.typing import Self 

495 

496if TYPE_CHECKING: 

497 from ...engine.interfaces import ConnectArgsType 

498 from ...engine.interfaces import DBAPIConnection 

499 from ...engine.interfaces import DBAPICursor 

500 from ...engine.interfaces import DBAPIModule 

501 from ...engine.interfaces import IsolationLevel 

502 from ...engine.interfaces import VersionInfoType 

503 from ...engine.url import URL 

504 from ...pool.base import PoolProxiedConnection 

505 from ...sql.type_api import _BindProcessorType 

506 from ...sql.type_api import _ResultProcessorType 

507 

508 

509class _SQLite_pysqliteTimeStamp(DATETIME): 

510 def bind_processor( # type: ignore[override] 

511 self, dialect: SQLiteDialect 

512 ) -> Optional[_BindProcessorType[Any]]: 

513 if dialect.native_datetime: 

514 return None 

515 else: 

516 return DATETIME.bind_processor(self, dialect) 

517 

518 def result_processor( # type: ignore[override] 

519 self, dialect: SQLiteDialect, coltype: object 

520 ) -> Optional[_ResultProcessorType[Any]]: 

521 if dialect.native_datetime: 

522 return None 

523 else: 

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

525 

526 

527class _SQLite_pysqliteDate(DATE): 

528 def bind_processor( # type: ignore[override] 

529 self, dialect: SQLiteDialect 

530 ) -> Optional[_BindProcessorType[Any]]: 

531 if dialect.native_datetime: 

532 return None 

533 else: 

534 return DATE.bind_processor(self, dialect) 

535 

536 def result_processor( # type: ignore[override] 

537 self, dialect: SQLiteDialect, coltype: object 

538 ) -> Optional[_ResultProcessorType[Any]]: 

539 if dialect.native_datetime: 

540 return None 

541 else: 

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

543 

544 

545class SQLiteDialect_pysqlite(SQLiteDialect): 

546 default_paramstyle = "qmark" 

547 supports_statement_cache = True 

548 returns_native_bytes = True 

549 

550 colspecs = util.update_copy( 

551 SQLiteDialect.colspecs, 

552 { 

553 sqltypes.Date: _SQLite_pysqliteDate, 

554 sqltypes.TIMESTAMP: _SQLite_pysqliteTimeStamp, 

555 }, 

556 ) 

557 

558 description_encoding = None 

559 

560 driver = "pysqlite" 

561 

562 @classmethod 

563 def import_dbapi(cls) -> DBAPIModule: 

564 from sqlite3 import dbapi2 as sqlite 

565 

566 return cast("DBAPIModule", sqlite) 

567 

568 @classmethod 

569 def _is_url_file_db(cls, url: URL) -> bool: 

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

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

572 ): 

573 return True 

574 else: 

575 return False 

576 

577 @classmethod 

578 def get_pool_class(cls, url: URL) -> type[pool.Pool]: 

579 if cls._is_url_file_db(url): 

580 return pool.QueuePool 

581 else: 

582 return pool.SingletonThreadPool 

583 

584 def _get_server_version_info(self, connection: Any) -> VersionInfoType: 

585 return self.dbapi.sqlite_version_info # type: ignore 

586 

587 _isolation_lookup = SQLiteDialect._isolation_lookup.union( 

588 { 

589 "AUTOCOMMIT": None, # type: ignore[dict-item] 

590 } 

591 ) 

592 

593 def set_isolation_level( 

594 self, dbapi_connection: DBAPIConnection, level: IsolationLevel 

595 ) -> None: 

596 if level == "AUTOCOMMIT": 

597 dbapi_connection.isolation_level = None 

598 else: 

599 dbapi_connection.isolation_level = "" 

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

601 

602 def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool: 

603 return dbapi_conn.isolation_level is None 

604 

605 def on_connect(self) -> Callable[[DBAPIConnection], None]: 

606 def regexp(a: str, b: Optional[str]) -> Optional[bool]: 

607 if b is None: 

608 return None 

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

610 

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

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

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

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

615 # with following 3.8 sqlite versions 

616 create_func_kw = {"deterministic": True} 

617 else: 

618 create_func_kw = {} 

619 

620 def set_regexp(dbapi_connection: DBAPIConnection) -> None: 

621 dbapi_connection.create_function( 

622 "regexp", 2, regexp, **create_func_kw 

623 ) 

624 

625 def floor_func(dbapi_connection: DBAPIConnection) -> None: 

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

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

628 # for now. 

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

630 dbapi_connection.create_function( 

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

632 ) 

633 

634 fns = [set_regexp, floor_func] 

635 

636 def connect(conn: DBAPIConnection) -> None: 

637 for fn in fns: 

638 fn(conn) 

639 

640 return connect 

641 

642 def create_connect_args(self, url: URL) -> ConnectArgsType: 

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

644 raise exc.ArgumentError( 

645 "Invalid SQLite URL: %s\n" 

646 "Valid SQLite URL forms are:\n" 

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

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

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

650 ) 

651 

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

653 # parameter names accepted by sqlite3/pysqlite, using 

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

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

656 # parameters passed to connect_args will always go to the 

657 # sqlite3/pysqlite driver. 

658 pysqlite_args = [ 

659 ("uri", bool), 

660 ("timeout", float), 

661 ("isolation_level", str), 

662 ("detect_types", int), 

663 ("check_same_thread", bool), 

664 ("cached_statements", int), 

665 ] 

666 opts = url.query 

667 pysqlite_opts: dict[str, Any] = {} 

668 for key, type_ in pysqlite_args: 

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

670 

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

672 uri_opts = dict(opts) 

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

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

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

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

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

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

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

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

681 # to adjust for that here. 

682 for key, type_ in pysqlite_args: 

683 uri_opts.pop(key, None) 

684 filename: str = url.database # type: ignore[assignment] 

685 if uri_opts: 

686 # sorting of keys is for unit test support 

687 filename += "?" + ( 

688 "&".join( 

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

690 for key in sorted(uri_opts) 

691 ) 

692 ) 

693 else: 

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

695 if filename != ":memory:": 

696 filename = os.path.abspath(filename) 

697 

698 pysqlite_opts.setdefault( 

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

700 ) 

701 

702 return ([filename], pysqlite_opts) 

703 

704 def is_disconnect( 

705 self, 

706 e: DBAPIModule.Error, 

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

708 cursor: Optional[DBAPICursor], 

709 ) -> bool: 

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

711 return isinstance( 

712 e, self.dbapi.ProgrammingError 

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

714 

715 

716dialect = SQLiteDialect_pysqlite 

717 

718 

719class _SQLiteDialect_pysqlite_numeric(SQLiteDialect_pysqlite): 

720 """numeric dialect for testing only 

721 

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

723 and may change at any time. 

724 

725 """ 

726 

727 supports_statement_cache = True 

728 default_paramstyle = "numeric" 

729 driver = "pysqlite_numeric" 

730 

731 _first_bind = ":1" 

732 _not_in_statement_regexp: Optional[Pattern[str]] = None 

733 

734 def __init__(self, *arg: Any, **kw: Any) -> None: 

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

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

737 

738 def create_connect_args(self, url: URL) -> ConnectArgsType: 

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

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

741 return arg, opts 

742 

743 def _fix_sqlite_issue_99953(self) -> Any: 

744 import sqlite3 

745 

746 first_bind = self._first_bind 

747 if self._not_in_statement_regexp: 

748 nis = self._not_in_statement_regexp 

749 

750 def _test_sql(sql: str) -> None: 

751 m = nis.search(sql) 

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

753 

754 else: 

755 

756 def _test_sql(sql: str) -> None: 

757 pass 

758 

759 def _numeric_param_as_dict( 

760 parameters: Any, 

761 ) -> Union[dict[str, Any], tuple[Any, ...]]: 

762 if parameters: 

763 assert isinstance(parameters, tuple) 

764 return { 

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

766 } 

767 else: 

768 return () 

769 

770 class SQLiteFix99953Cursor(sqlite3.Cursor): 

771 def execute(self, sql: str, parameters: Any = ()) -> Self: 

772 _test_sql(sql) 

773 if first_bind in sql: 

774 parameters = _numeric_param_as_dict(parameters) 

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

776 

777 def executemany(self, sql: str, parameters: Any) -> Self: 

778 _test_sql(sql) 

779 if first_bind in sql: 

780 parameters = [ 

781 _numeric_param_as_dict(p) for p in parameters 

782 ] 

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

784 

785 class SQLiteFix99953Connection(sqlite3.Connection): 

786 _CursorT = TypeVar("_CursorT", bound=sqlite3.Cursor) 

787 

788 def cursor( 

789 self, 

790 factory: Optional[ 

791 Callable[[sqlite3.Connection], _CursorT] 

792 ] = None, 

793 ) -> _CursorT: 

794 if factory is None: 

795 factory = SQLiteFix99953Cursor # type: ignore[assignment] 

796 return super().cursor(factory=factory) # type: ignore[return-value] # noqa[E501] 

797 

798 def execute( 

799 self, sql: str, parameters: Any = () 

800 ) -> sqlite3.Cursor: 

801 _test_sql(sql) 

802 if first_bind in sql: 

803 parameters = _numeric_param_as_dict(parameters) 

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

805 

806 def executemany(self, sql: str, parameters: Any) -> sqlite3.Cursor: 

807 _test_sql(sql) 

808 if first_bind in sql: 

809 parameters = [ 

810 _numeric_param_as_dict(p) for p in parameters 

811 ] 

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

813 

814 return SQLiteFix99953Connection 

815 

816 

817class _SQLiteDialect_pysqlite_dollar(_SQLiteDialect_pysqlite_numeric): 

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

819 

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

821 and may change at any time. 

822 

823 """ 

824 

825 supports_statement_cache = True 

826 default_paramstyle = "numeric_dollar" 

827 driver = "pysqlite_dollar" 

828 

829 _first_bind = "$1" 

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

831 

832 def __init__(self, *arg: Any, **kw: Any) -> None: 

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

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