pysqlite.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  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. r"""
  8. .. dialect:: sqlite+pysqlite
  9. :name: pysqlite
  10. :dbapi: sqlite3
  11. :connectstring: sqlite+pysqlite:///file_path
  12. :url: https://docs.python.org/library/sqlite3.html
  13. Note that ``pysqlite`` is the same driver as the ``sqlite3``
  14. module included with the Python distribution.
  15. Driver
  16. ------
  17. The ``sqlite3`` Python DBAPI is standard on all modern Python versions;
  18. for cPython and Pypy, no additional installation is necessary.
  19. Connect Strings
  20. ---------------
  21. The file specification for the SQLite database is taken as the "database"
  22. portion of the URL. Note that the format of a SQLAlchemy url is:
  23. .. sourcecode:: text
  24. driver://user:pass@host/database
  25. This means that the actual filename to be used starts with the characters to
  26. the **right** of the third slash. So connecting to a relative filepath
  27. looks like::
  28. # relative path
  29. e = create_engine("sqlite:///path/to/database.db")
  30. An absolute path, which is denoted by starting with a slash, means you
  31. need **four** slashes::
  32. # absolute path
  33. e = create_engine("sqlite:////path/to/database.db")
  34. To use a Windows path, regular drive specifications and backslashes can be
  35. used. Double backslashes are probably needed::
  36. # absolute path on Windows
  37. e = create_engine("sqlite:///C:\\path\\to\\database.db")
  38. To use sqlite ``:memory:`` database specify it as the filename using
  39. ``sqlite:///:memory:``. It's also the default if no filepath is
  40. present, specifying only ``sqlite://`` and nothing else::
  41. # in-memory database (note three slashes)
  42. e = create_engine("sqlite:///:memory:")
  43. # also in-memory database
  44. e2 = create_engine("sqlite://")
  45. .. _pysqlite_uri_connections:
  46. URI Connections
  47. ^^^^^^^^^^^^^^^
  48. Modern versions of SQLite support an alternative system of connecting using a
  49. `driver level URI <https://www.sqlite.org/uri.html>`_, which has the advantage
  50. that additional driver-level arguments can be passed including options such as
  51. "read only". The Python sqlite3 driver supports this mode under modern Python
  52. 3 versions. The SQLAlchemy pysqlite driver supports this mode of use by
  53. specifying "uri=true" in the URL query string. The SQLite-level "URI" is kept
  54. as the "database" portion of the SQLAlchemy url (that is, following a slash)::
  55. e = create_engine("sqlite:///file:path/to/database?mode=ro&uri=true")
  56. .. note:: The "uri=true" parameter must appear in the **query string**
  57. of the URL. It will not currently work as expected if it is only
  58. present in the :paramref:`_sa.create_engine.connect_args`
  59. parameter dictionary.
  60. The logic reconciles the simultaneous presence of SQLAlchemy's query string and
  61. SQLite's query string by separating out the parameters that belong to the
  62. Python sqlite3 driver vs. those that belong to the SQLite URI. This is
  63. achieved through the use of a fixed list of parameters known to be accepted by
  64. the Python side of the driver. For example, to include a URL that indicates
  65. the Python sqlite3 "timeout" and "check_same_thread" parameters, along with the
  66. SQLite "mode" and "nolock" parameters, they can all be passed together on the
  67. query string::
  68. e = create_engine(
  69. "sqlite:///file:path/to/database?"
  70. "check_same_thread=true&timeout=10&mode=ro&nolock=1&uri=true"
  71. )
  72. Above, the pysqlite / sqlite3 DBAPI would be passed arguments as::
  73. sqlite3.connect(
  74. "file:path/to/database?mode=ro&nolock=1",
  75. check_same_thread=True,
  76. timeout=10,
  77. uri=True,
  78. )
  79. Regarding future parameters added to either the Python or native drivers. new
  80. parameter names added to the SQLite URI scheme should be automatically
  81. accommodated by this scheme. New parameter names added to the Python driver
  82. side can be accommodated by specifying them in the
  83. :paramref:`_sa.create_engine.connect_args` dictionary,
  84. until dialect support is
  85. added by SQLAlchemy. For the less likely case that the native SQLite driver
  86. adds a new parameter name that overlaps with one of the existing, known Python
  87. driver parameters (such as "timeout" perhaps), SQLAlchemy's dialect would
  88. require adjustment for the URL scheme to continue to support this.
  89. As is always the case for all SQLAlchemy dialects, the entire "URL" process
  90. can be bypassed in :func:`_sa.create_engine` through the use of the
  91. :paramref:`_sa.create_engine.creator`
  92. parameter which allows for a custom callable
  93. that creates a Python sqlite3 driver level connection directly.
  94. .. versionadded:: 1.3.9
  95. .. seealso::
  96. `Uniform Resource Identifiers <https://www.sqlite.org/uri.html>`_ - in
  97. the SQLite documentation
  98. .. _pysqlite_regexp:
  99. Regular Expression Support
  100. ---------------------------
  101. .. versionadded:: 1.4
  102. Support for the :meth:`_sql.ColumnOperators.regexp_match` operator is provided
  103. using Python's re.search_ function. SQLite itself does not include a working
  104. regular expression operator; instead, it includes a non-implemented placeholder
  105. operator ``REGEXP`` that calls a user-defined function that must be provided.
  106. SQLAlchemy's implementation makes use of the pysqlite create_function_ hook
  107. as follows::
  108. def regexp(a, b):
  109. return re.search(a, b) is not None
  110. sqlite_connection.create_function(
  111. "regexp",
  112. 2,
  113. regexp,
  114. )
  115. There is currently no support for regular expression flags as a separate
  116. argument, as these are not supported by SQLite's REGEXP operator, however these
  117. may be included inline within the regular expression string. See `Python regular expressions`_ for
  118. details.
  119. .. seealso::
  120. `Python regular expressions`_: Documentation for Python's regular expression syntax.
  121. .. _create_function: https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function
  122. .. _re.search: https://docs.python.org/3/library/re.html#re.search
  123. .. _Python regular expressions: https://docs.python.org/3/library/re.html#re.search
  124. Compatibility with sqlite3 "native" date and datetime types
  125. -----------------------------------------------------------
  126. The pysqlite driver includes the sqlite3.PARSE_DECLTYPES and
  127. sqlite3.PARSE_COLNAMES options, which have the effect of any column
  128. or expression explicitly cast as "date" or "timestamp" will be converted
  129. to a Python date or datetime object. The date and datetime types provided
  130. with the pysqlite dialect are not currently compatible with these options,
  131. since they render the ISO date/datetime including microseconds, which
  132. pysqlite's driver does not. Additionally, SQLAlchemy does not at
  133. this time automatically render the "cast" syntax required for the
  134. freestanding functions "current_timestamp" and "current_date" to return
  135. datetime/date types natively. Unfortunately, pysqlite
  136. does not provide the standard DBAPI types in ``cursor.description``,
  137. leaving SQLAlchemy with no way to detect these types on the fly
  138. without expensive per-row type checks.
  139. Keeping in mind that pysqlite's parsing option is not recommended,
  140. nor should be necessary, for use with SQLAlchemy, usage of PARSE_DECLTYPES
  141. can be forced if one configures "native_datetime=True" on create_engine()::
  142. engine = create_engine(
  143. "sqlite://",
  144. connect_args={
  145. "detect_types": sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES
  146. },
  147. native_datetime=True,
  148. )
  149. With this flag enabled, the DATE and TIMESTAMP types (but note - not the
  150. DATETIME or TIME types...confused yet ?) will not perform any bind parameter
  151. or result processing. Execution of "func.current_date()" will return a string.
  152. "func.current_timestamp()" is registered as returning a DATETIME type in
  153. SQLAlchemy, so this function still receives SQLAlchemy-level result
  154. processing.
  155. .. _pysqlite_threading_pooling:
  156. Threading/Pooling Behavior
  157. ---------------------------
  158. The ``sqlite3`` DBAPI by default prohibits the use of a particular connection
  159. in a thread which is not the one in which it was created. As SQLite has
  160. matured, it's behavior under multiple threads has improved, and even includes
  161. options for memory only databases to be used in multiple threads.
  162. The thread prohibition is known as "check same thread" and may be controlled
  163. using the ``sqlite3`` parameter ``check_same_thread``, which will disable or
  164. enable this check. SQLAlchemy's default behavior here is to set
  165. ``check_same_thread`` to ``False`` automatically whenever a file-based database
  166. is in use, to establish compatibility with the default pool class
  167. :class:`.QueuePool`.
  168. The SQLAlchemy ``pysqlite`` DBAPI establishes the connection pool differently
  169. based on the kind of SQLite database that's requested:
  170. * When a ``:memory:`` SQLite database is specified, the dialect by default
  171. will use :class:`.SingletonThreadPool`. This pool maintains a single
  172. connection per thread, so that all access to the engine within the current
  173. thread use the same ``:memory:`` database - other threads would access a
  174. different ``:memory:`` database. The ``check_same_thread`` parameter
  175. defaults to ``True``.
  176. * When a file-based database is specified, the dialect will use
  177. :class:`.QueuePool` as the source of connections. at the same time,
  178. the ``check_same_thread`` flag is set to False by default unless overridden.
  179. .. versionchanged:: 2.0
  180. SQLite file database engines now use :class:`.QueuePool` by default.
  181. Previously, :class:`.NullPool` were used. The :class:`.NullPool` class
  182. may be used by specifying it via the
  183. :paramref:`_sa.create_engine.poolclass` parameter.
  184. Disabling Connection Pooling for File Databases
  185. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  186. Pooling may be disabled for a file based database by specifying the
  187. :class:`.NullPool` implementation for the :func:`_sa.create_engine.poolclass`
  188. parameter::
  189. from sqlalchemy import NullPool
  190. engine = create_engine("sqlite:///myfile.db", poolclass=NullPool)
  191. It's been observed that the :class:`.NullPool` implementation incurs an
  192. extremely small performance overhead for repeated checkouts due to the lack of
  193. connection reuse implemented by :class:`.QueuePool`. However, it still
  194. may be beneficial to use this class if the application is experiencing
  195. issues with files being locked.
  196. Using a Memory Database in Multiple Threads
  197. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  198. To use a ``:memory:`` database in a multithreaded scenario, the same
  199. connection object must be shared among threads, since the database exists
  200. only within the scope of that connection. The
  201. :class:`.StaticPool` implementation will maintain a single connection
  202. globally, and the ``check_same_thread`` flag can be passed to Pysqlite
  203. as ``False``::
  204. from sqlalchemy.pool import StaticPool
  205. engine = create_engine(
  206. "sqlite://",
  207. connect_args={"check_same_thread": False},
  208. poolclass=StaticPool,
  209. )
  210. Note that using a ``:memory:`` database in multiple threads requires a recent
  211. version of SQLite.
  212. Using Temporary Tables with SQLite
  213. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  214. Due to the way SQLite deals with temporary tables, if you wish to use a
  215. temporary table in a file-based SQLite database across multiple checkouts
  216. from the connection pool, such as when using an ORM :class:`.Session` where
  217. the temporary table should continue to remain after :meth:`.Session.commit` or
  218. :meth:`.Session.rollback` is called, a pool which maintains a single
  219. connection must be used. Use :class:`.SingletonThreadPool` if the scope is
  220. only needed within the current thread, or :class:`.StaticPool` is scope is
  221. needed within multiple threads for this case::
  222. # maintain the same connection per thread
  223. from sqlalchemy.pool import SingletonThreadPool
  224. engine = create_engine("sqlite:///mydb.db", poolclass=SingletonThreadPool)
  225. # maintain the same connection across all threads
  226. from sqlalchemy.pool import StaticPool
  227. engine = create_engine("sqlite:///mydb.db", poolclass=StaticPool)
  228. Note that :class:`.SingletonThreadPool` should be configured for the number
  229. of threads that are to be used; beyond that number, connections will be
  230. closed out in a non deterministic way.
  231. Dealing with Mixed String / Binary Columns
  232. ------------------------------------------------------
  233. The SQLite database is weakly typed, and as such it is possible when using
  234. binary values, which in Python are represented as ``b'some string'``, that a
  235. particular SQLite database can have data values within different rows where
  236. some of them will be returned as a ``b''`` value by the Pysqlite driver, and
  237. others will be returned as Python strings, e.g. ``''`` values. This situation
  238. is not known to occur if the SQLAlchemy :class:`.LargeBinary` datatype is used
  239. consistently, however if a particular SQLite database has data that was
  240. inserted using the Pysqlite driver directly, or when using the SQLAlchemy
  241. :class:`.String` type which was later changed to :class:`.LargeBinary`, the
  242. table will not be consistently readable because SQLAlchemy's
  243. :class:`.LargeBinary` datatype does not handle strings so it has no way of
  244. "encoding" a value that is in string format.
  245. To deal with a SQLite table that has mixed string / binary data in the
  246. same column, use a custom type that will check each row individually::
  247. from sqlalchemy import String
  248. from sqlalchemy import TypeDecorator
  249. class MixedBinary(TypeDecorator):
  250. impl = String
  251. cache_ok = True
  252. def process_result_value(self, value, dialect):
  253. if isinstance(value, str):
  254. value = bytes(value, "utf-8")
  255. elif value is not None:
  256. value = bytes(value)
  257. return value
  258. Then use the above ``MixedBinary`` datatype in the place where
  259. :class:`.LargeBinary` would normally be used.
  260. .. _pysqlite_serializable:
  261. Serializable isolation / Savepoints / Transactional DDL
  262. -------------------------------------------------------
  263. A newly revised version of this important section is now available
  264. at the top level of the SQLAlchemy SQLite documentation, in the section
  265. :ref:`sqlite_transactions`.
  266. .. _pysqlite_udfs:
  267. User-Defined Functions
  268. ----------------------
  269. pysqlite supports a `create_function() <https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function>`_
  270. method that allows us to create our own user-defined functions (UDFs) in Python and use them directly in SQLite queries.
  271. These functions are registered with a specific DBAPI Connection.
  272. SQLAlchemy uses connection pooling with file-based SQLite databases, so we need to ensure that the UDF is attached to the
  273. connection when it is created. That is accomplished with an event listener::
  274. from sqlalchemy import create_engine
  275. from sqlalchemy import event
  276. from sqlalchemy import text
  277. def udf():
  278. return "udf-ok"
  279. engine = create_engine("sqlite:///./db_file")
  280. @event.listens_for(engine, "connect")
  281. def connect(conn, rec):
  282. conn.create_function("udf", 0, udf)
  283. for i in range(5):
  284. with engine.connect() as conn:
  285. print(conn.scalar(text("SELECT UDF()")))
  286. """ # noqa
  287. from __future__ import annotations
  288. import math
  289. import os
  290. import re
  291. from typing import Any
  292. from typing import Callable
  293. from typing import cast
  294. from typing import Optional
  295. from typing import Pattern
  296. from typing import TYPE_CHECKING
  297. from typing import TypeVar
  298. from typing import Union
  299. from .base import DATE
  300. from .base import DATETIME
  301. from .base import SQLiteDialect
  302. from ... import exc
  303. from ... import pool
  304. from ... import types as sqltypes
  305. from ... import util
  306. from ...util.typing import Self
  307. if TYPE_CHECKING:
  308. from ...engine.interfaces import ConnectArgsType
  309. from ...engine.interfaces import DBAPIConnection
  310. from ...engine.interfaces import DBAPICursor
  311. from ...engine.interfaces import DBAPIModule
  312. from ...engine.interfaces import IsolationLevel
  313. from ...engine.interfaces import VersionInfoType
  314. from ...engine.url import URL
  315. from ...pool.base import PoolProxiedConnection
  316. from ...sql.type_api import _BindProcessorType
  317. from ...sql.type_api import _ResultProcessorType
  318. class _SQLite_pysqliteTimeStamp(DATETIME):
  319. def bind_processor( # type: ignore[override]
  320. self, dialect: SQLiteDialect
  321. ) -> Optional[_BindProcessorType[Any]]:
  322. if dialect.native_datetime:
  323. return None
  324. else:
  325. return DATETIME.bind_processor(self, dialect)
  326. def result_processor( # type: ignore[override]
  327. self, dialect: SQLiteDialect, coltype: object
  328. ) -> Optional[_ResultProcessorType[Any]]:
  329. if dialect.native_datetime:
  330. return None
  331. else:
  332. return DATETIME.result_processor(self, dialect, coltype)
  333. class _SQLite_pysqliteDate(DATE):
  334. def bind_processor( # type: ignore[override]
  335. self, dialect: SQLiteDialect
  336. ) -> Optional[_BindProcessorType[Any]]:
  337. if dialect.native_datetime:
  338. return None
  339. else:
  340. return DATE.bind_processor(self, dialect)
  341. def result_processor( # type: ignore[override]
  342. self, dialect: SQLiteDialect, coltype: object
  343. ) -> Optional[_ResultProcessorType[Any]]:
  344. if dialect.native_datetime:
  345. return None
  346. else:
  347. return DATE.result_processor(self, dialect, coltype)
  348. class SQLiteDialect_pysqlite(SQLiteDialect):
  349. default_paramstyle = "qmark"
  350. supports_statement_cache = True
  351. returns_native_bytes = True
  352. colspecs = util.update_copy(
  353. SQLiteDialect.colspecs,
  354. {
  355. sqltypes.Date: _SQLite_pysqliteDate,
  356. sqltypes.TIMESTAMP: _SQLite_pysqliteTimeStamp,
  357. },
  358. )
  359. description_encoding = None
  360. driver = "pysqlite"
  361. @classmethod
  362. def import_dbapi(cls) -> DBAPIModule:
  363. from sqlite3 import dbapi2 as sqlite
  364. return cast("DBAPIModule", sqlite)
  365. @classmethod
  366. def _is_url_file_db(cls, url: URL) -> bool:
  367. if (url.database and url.database != ":memory:") and (
  368. url.query.get("mode", None) != "memory"
  369. ):
  370. return True
  371. else:
  372. return False
  373. @classmethod
  374. def get_pool_class(cls, url: URL) -> type[pool.Pool]:
  375. if cls._is_url_file_db(url):
  376. return pool.QueuePool
  377. else:
  378. return pool.SingletonThreadPool
  379. def _get_server_version_info(self, connection: Any) -> VersionInfoType:
  380. return self.dbapi.sqlite_version_info # type: ignore
  381. _isolation_lookup = SQLiteDialect._isolation_lookup.union(
  382. {
  383. "AUTOCOMMIT": None, # type: ignore[dict-item]
  384. }
  385. )
  386. def set_isolation_level(
  387. self, dbapi_connection: DBAPIConnection, level: IsolationLevel
  388. ) -> None:
  389. if level == "AUTOCOMMIT":
  390. dbapi_connection.isolation_level = None
  391. else:
  392. dbapi_connection.isolation_level = ""
  393. return super().set_isolation_level(dbapi_connection, level)
  394. def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool:
  395. return dbapi_conn.isolation_level is None
  396. def on_connect(self) -> Callable[[DBAPIConnection], None]:
  397. def regexp(a: str, b: Optional[str]) -> Optional[bool]:
  398. if b is None:
  399. return None
  400. return re.search(a, b) is not None
  401. if util.py38 and self._get_server_version_info(None) >= (3, 9):
  402. # sqlite must be greater than 3.8.3 for deterministic=True
  403. # https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function
  404. # the check is more conservative since there were still issues
  405. # with following 3.8 sqlite versions
  406. create_func_kw = {"deterministic": True}
  407. else:
  408. create_func_kw = {}
  409. def set_regexp(dbapi_connection: DBAPIConnection) -> None:
  410. dbapi_connection.create_function(
  411. "regexp", 2, regexp, **create_func_kw
  412. )
  413. def floor_func(dbapi_connection: DBAPIConnection) -> None:
  414. # NOTE: floor is optionally present in sqlite 3.35+ , however
  415. # as it is normally non-present we deliver floor() unconditionally
  416. # for now.
  417. # https://www.sqlite.org/lang_mathfunc.html
  418. dbapi_connection.create_function(
  419. "floor", 1, math.floor, **create_func_kw
  420. )
  421. fns = [set_regexp, floor_func]
  422. def connect(conn: DBAPIConnection) -> None:
  423. for fn in fns:
  424. fn(conn)
  425. return connect
  426. def create_connect_args(self, url: URL) -> ConnectArgsType:
  427. if url.username or url.password or url.host or url.port:
  428. raise exc.ArgumentError(
  429. "Invalid SQLite URL: %s\n"
  430. "Valid SQLite URL forms are:\n"
  431. " sqlite:///:memory: (or, sqlite://)\n"
  432. " sqlite:///relative/path/to/file.db\n"
  433. " sqlite:////absolute/path/to/file.db" % (url,)
  434. )
  435. # theoretically, this list can be augmented, at least as far as
  436. # parameter names accepted by sqlite3/pysqlite, using
  437. # inspect.getfullargspec(). for the moment this seems like overkill
  438. # as these parameters don't change very often, and as always,
  439. # parameters passed to connect_args will always go to the
  440. # sqlite3/pysqlite driver.
  441. pysqlite_args = [
  442. ("uri", bool),
  443. ("timeout", float),
  444. ("isolation_level", str),
  445. ("detect_types", int),
  446. ("check_same_thread", bool),
  447. ("cached_statements", int),
  448. ]
  449. opts = url.query
  450. pysqlite_opts: dict[str, Any] = {}
  451. for key, type_ in pysqlite_args:
  452. util.coerce_kw_type(opts, key, type_, dest=pysqlite_opts)
  453. if pysqlite_opts.get("uri", False):
  454. uri_opts = dict(opts)
  455. # here, we are actually separating the parameters that go to
  456. # sqlite3/pysqlite vs. those that go the SQLite URI. What if
  457. # two names conflict? again, this seems to be not the case right
  458. # now, and in the case that new names are added to
  459. # either side which overlap, again the sqlite3/pysqlite parameters
  460. # can be passed through connect_args instead of in the URL.
  461. # If SQLite native URIs add a parameter like "timeout" that
  462. # we already have listed here for the python driver, then we need
  463. # to adjust for that here.
  464. for key, type_ in pysqlite_args:
  465. uri_opts.pop(key, None)
  466. filename: str = url.database # type: ignore[assignment]
  467. if uri_opts:
  468. # sorting of keys is for unit test support
  469. filename += "?" + (
  470. "&".join(
  471. "%s=%s" % (key, uri_opts[key])
  472. for key in sorted(uri_opts)
  473. )
  474. )
  475. else:
  476. filename = url.database or ":memory:"
  477. if filename != ":memory:":
  478. filename = os.path.abspath(filename)
  479. pysqlite_opts.setdefault(
  480. "check_same_thread", not self._is_url_file_db(url)
  481. )
  482. return ([filename], pysqlite_opts)
  483. def is_disconnect(
  484. self,
  485. e: DBAPIModule.Error,
  486. connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
  487. cursor: Optional[DBAPICursor],
  488. ) -> bool:
  489. self.dbapi = cast("DBAPIModule", self.dbapi)
  490. return isinstance(
  491. e, self.dbapi.ProgrammingError
  492. ) and "Cannot operate on a closed database." in str(e)
  493. dialect = SQLiteDialect_pysqlite
  494. class _SQLiteDialect_pysqlite_numeric(SQLiteDialect_pysqlite):
  495. """numeric dialect for testing only
  496. internal use only. This dialect is **NOT** supported by SQLAlchemy
  497. and may change at any time.
  498. """
  499. supports_statement_cache = True
  500. default_paramstyle = "numeric"
  501. driver = "pysqlite_numeric"
  502. _first_bind = ":1"
  503. _not_in_statement_regexp: Optional[Pattern[str]] = None
  504. def __init__(self, *arg: Any, **kw: Any) -> None:
  505. kw.setdefault("paramstyle", "numeric")
  506. super().__init__(*arg, **kw)
  507. def create_connect_args(self, url: URL) -> ConnectArgsType:
  508. arg, opts = super().create_connect_args(url)
  509. opts["factory"] = self._fix_sqlite_issue_99953()
  510. return arg, opts
  511. def _fix_sqlite_issue_99953(self) -> Any:
  512. import sqlite3
  513. first_bind = self._first_bind
  514. if self._not_in_statement_regexp:
  515. nis = self._not_in_statement_regexp
  516. def _test_sql(sql: str) -> None:
  517. m = nis.search(sql)
  518. assert not m, f"Found {nis.pattern!r} in {sql!r}"
  519. else:
  520. def _test_sql(sql: str) -> None:
  521. pass
  522. def _numeric_param_as_dict(
  523. parameters: Any,
  524. ) -> Union[dict[str, Any], tuple[Any, ...]]:
  525. if parameters:
  526. assert isinstance(parameters, tuple)
  527. return {
  528. str(idx): value for idx, value in enumerate(parameters, 1)
  529. }
  530. else:
  531. return ()
  532. class SQLiteFix99953Cursor(sqlite3.Cursor):
  533. def execute(self, sql: str, parameters: Any = ()) -> Self:
  534. _test_sql(sql)
  535. if first_bind in sql:
  536. parameters = _numeric_param_as_dict(parameters)
  537. return super().execute(sql, parameters)
  538. def executemany(self, sql: str, parameters: Any) -> Self:
  539. _test_sql(sql)
  540. if first_bind in sql:
  541. parameters = [
  542. _numeric_param_as_dict(p) for p in parameters
  543. ]
  544. return super().executemany(sql, parameters)
  545. class SQLiteFix99953Connection(sqlite3.Connection):
  546. _CursorT = TypeVar("_CursorT", bound=sqlite3.Cursor)
  547. def cursor(
  548. self,
  549. factory: Optional[
  550. Callable[[sqlite3.Connection], _CursorT]
  551. ] = None,
  552. ) -> _CursorT:
  553. if factory is None:
  554. factory = SQLiteFix99953Cursor # type: ignore[assignment]
  555. return super().cursor(factory=factory) # type: ignore[return-value] # noqa[E501]
  556. def execute(
  557. self, sql: str, parameters: Any = ()
  558. ) -> sqlite3.Cursor:
  559. _test_sql(sql)
  560. if first_bind in sql:
  561. parameters = _numeric_param_as_dict(parameters)
  562. return super().execute(sql, parameters)
  563. def executemany(self, sql: str, parameters: Any) -> sqlite3.Cursor:
  564. _test_sql(sql)
  565. if first_bind in sql:
  566. parameters = [
  567. _numeric_param_as_dict(p) for p in parameters
  568. ]
  569. return super().executemany(sql, parameters)
  570. return SQLiteFix99953Connection
  571. class _SQLiteDialect_pysqlite_dollar(_SQLiteDialect_pysqlite_numeric):
  572. """numeric dialect that uses $ for testing only
  573. internal use only. This dialect is **NOT** supported by SQLAlchemy
  574. and may change at any time.
  575. """
  576. supports_statement_cache = True
  577. default_paramstyle = "numeric_dollar"
  578. driver = "pysqlite_dollar"
  579. _first_bind = "$1"
  580. _not_in_statement_regexp = re.compile(r"[^\d]:\d+")
  581. def __init__(self, *arg: Any, **kw: Any) -> None:
  582. kw.setdefault("paramstyle", "numeric_dollar")
  583. super().__init__(*arg, **kw)