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.. seealso::
125
126 `Uniform Resource Identifiers <https://www.sqlite.org/uri.html>`_ - in
127 the SQLite documentation
128
129.. _pysqlite_regexp:
130
131Regular Expression Support
132---------------------------
133
134.. versionadded:: 1.4
135
136Support for the :meth:`_sql.ColumnOperators.regexp_match` operator is provided
137using Python's re.search_ function. SQLite itself does not include a working
138regular expression operator; instead, it includes a non-implemented placeholder
139operator ``REGEXP`` that calls a user-defined function that must be provided.
140
141SQLAlchemy's implementation makes use of the pysqlite create_function_ hook
142as follows::
143
144
145 def regexp(a, b):
146 return re.search(a, b) is not None
147
148
149 sqlite_connection.create_function(
150 "regexp",
151 2,
152 regexp,
153 )
154
155There is currently no support for regular expression flags as a separate
156argument, as these are not supported by SQLite's REGEXP operator, however these
157may be included inline within the regular expression string. See `Python regular expressions`_ for
158details.
159
160.. seealso::
161
162 `Python regular expressions`_: Documentation for Python's regular expression syntax.
163
164.. _create_function: https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function
165
166.. _re.search: https://docs.python.org/3/library/re.html#re.search
167
168.. _Python regular expressions: https://docs.python.org/3/library/re.html#re.search
169
170
171
172Compatibility with sqlite3 "native" date and datetime types
173-----------------------------------------------------------
174
175The pysqlite driver includes the sqlite3.PARSE_DECLTYPES and
176sqlite3.PARSE_COLNAMES options, which have the effect of any column
177or expression explicitly cast as "date" or "timestamp" will be converted
178to a Python date or datetime object. The date and datetime types provided
179with the pysqlite dialect are not currently compatible with these options,
180since they render the ISO date/datetime including microseconds, which
181pysqlite's driver does not. Additionally, SQLAlchemy does not at
182this time automatically render the "cast" syntax required for the
183freestanding functions "current_timestamp" and "current_date" to return
184datetime/date types natively. Unfortunately, pysqlite
185does not provide the standard DBAPI types in ``cursor.description``,
186leaving SQLAlchemy with no way to detect these types on the fly
187without expensive per-row type checks.
188
189Keeping in mind that pysqlite's parsing option is not recommended,
190nor should be necessary, for use with SQLAlchemy, usage of PARSE_DECLTYPES
191can be forced if one configures "native_datetime=True" on create_engine()::
192
193 engine = create_engine(
194 "sqlite://",
195 connect_args={
196 "detect_types": sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES
197 },
198 native_datetime=True,
199 )
200
201With this flag enabled, the DATE and TIMESTAMP types (but note - not the
202DATETIME or TIME types...confused yet ?) will not perform any bind parameter
203or result processing. Execution of "func.current_date()" will return a string.
204"func.current_timestamp()" is registered as returning a DATETIME type in
205SQLAlchemy, so this function still receives SQLAlchemy-level result
206processing.
207
208.. _pysqlite_threading_pooling:
209
210Concurrency/Threading/Pooling Behavior
211--------------------------------------
212
213The ``sqlite3`` DBAPI by default prohibits the use of a particular connection
214in a thread which is not the one in which it was created. As SQLite has
215matured, it's behavior under multiple threads has improved, and even includes
216options for memory only databases to be used in multiple threads.
217
218The thread prohibition is known as "check same thread" and may be controlled
219using the ``sqlite3`` parameter ``check_same_thread``, which will disable or
220enable this check. SQLAlchemy's default behavior here is to set
221``check_same_thread`` to ``False`` automatically whenever a file-based database
222is in use, to establish compatibility with the default pool class
223:class:`.QueuePool`.
224
225The SQLAlchemy ``pysqlite`` DBAPI establishes the connection pool differently
226based on the kind of SQLite database that's requested:
227
228* When a ``:memory:`` SQLite database is specified, the dialect by default
229 will use :class:`.SingletonThreadPool`. This pool maintains a single
230 connection per thread, so that all access to the engine within the current
231 thread use the same ``:memory:`` database - other threads would access a
232 different ``:memory:`` database. The ``check_same_thread`` parameter
233 defaults to ``True``.
234* When a file-based database is specified, the dialect will use
235 :class:`.QueuePool` as the source of connections. at the same time,
236 the ``check_same_thread`` flag is set to False by default unless overridden.
237
238 .. versionchanged:: 2.0
239
240 SQLite file database engines now use :class:`.QueuePool` by default.
241 Previously, :class:`.NullPool` were used. The :class:`.NullPool` class
242 may be used by specifying it via the
243 :paramref:`_sa.create_engine.poolclass` parameter.
244
245This selection is made based on the database name alone. Where a
246particular pool class is desired, it should be stated explicitly using the
247:paramref:`_sa.create_engine.poolclass` parameter, in which case no
248selection takes place at all.
249
250.. deprecated:: 2.1
251
252 A URL that passes ``mode=memory`` in the query string is currently also
253 given a single-connection pool class. This behavior is deprecated and
254 will be removed in a future release, at which point such URLs will
255 receive :class:`.QueuePool` like any other. This affects URLs such as
256 ``sqlite:///file:mydb?mode=memory&cache=shared&uri=true``, for which
257 :class:`.QueuePool` is in fact the appropriate class, as a shared cache
258 database supports multiple concurrent connections; see
259 :ref:`pysqlite_uri_shared_cache`. Applications relying on the present
260 behavior should state the pool class explicitly.
261
262Disabling Connection Pooling for File Databases
263^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
264
265Pooling may be disabled for a file based database by specifying the
266:class:`.NullPool` implementation for the :func:`_sa.create_engine.poolclass`
267parameter::
268
269 from sqlalchemy import NullPool
270
271 engine = create_engine("sqlite:///myfile.db", poolclass=NullPool)
272
273It's been observed that the :class:`.NullPool` implementation incurs an
274extremely small performance overhead for repeated checkouts due to the lack of
275connection reuse implemented by :class:`.QueuePool`. However, it still
276may be beneficial to use this class if the application is experiencing
277issues with files being locked.
278
279Using a Memory Database in Multiple Threads or Coroutines
280^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
281
282A ``:memory:`` SQLite database exists only within the scope of a single
283DBAPI connection. It is not possible for two separate ``sqlite3``
284connection objects to access the same ``:memory:`` database unless SQLite's
285shared-cache feature is enabled. Without shared cache, each connection
286creates its own independent in-memory database.
287
288This means a ``:memory:`` database is **not suitable** for use with
289multiple concurrent threads or coroutines unless either:
290
291* All workers are fully serialized (mutexed) against each other
292 such that only one is using the database at a time, or
293* SQLite's shared-cache URI feature is used to allow multiple
294 independent connections to access the same in-memory database.
295
296The same considerations apply when using the :ref:`aiosqlite <aiosqlite>`
297dialect, which wraps ``pysqlite`` connections in an async interface —
298without shared cache, each connection still creates its own independent
299in-memory database.
300
301The recommended approach for multithreaded or async in-memory
302use is the shared-cache URI feature, described below.
303
304.. _pysqlite_uri_shared_cache:
305
306Using a Shared-Cache Memory Database
307~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
308
309SQLite's
310`URI shared-cache <https://www.sqlite.org/sharedcache.html>`_
311mode allows multiple independent DBAPI connections, each with
312their own transaction state, to access the same in-memory
313database. This is enabled by using a ``file:`` URI with
314``cache=shared`` and passing ``uri=true`` in the query
315string::
316
317 engine = create_engine("sqlite:///file::memory:?cache=shared&uri=true")
318
319For async use with aiosqlite, use
320:func:`_asyncio.create_async_engine`::
321
322 engine = create_async_engine(
323 "sqlite+aiosqlite:///file::memory:?cache=shared&uri=true"
324 )
325
326Because this URL form is treated as a file-based database by the
327dialect, :class:`.QueuePool` is used automatically and
328``check_same_thread`` defaults to ``False``, so no additional pool
329or connect_args configuration is needed. Each checkout from the
330pool is a distinct DBAPI connection with its own transaction state.
331
332The shared-cache database is scoped by the filename component of
333the URI. ``file::memory:`` (empty name) is process-global — all
334engines in the process that use this URI share the same database.
335To maintain multiple independent in-memory databases within the
336same process, supply a distinct name for each. A named database of
337this kind requires ``mode=memory``, which presently causes a
338single-connection pool to be selected; :class:`.QueuePool` should
339therefore be requested explicitly::
340
341 from sqlalchemy.pool import QueuePool
342
343 engine_a = create_engine(
344 "sqlite:///file:db_a?mode=memory&cache=shared&uri=true",
345 poolclass=QueuePool,
346 )
347 engine_b = create_engine(
348 "sqlite:///file:db_b?mode=memory&cache=shared&uri=true",
349 poolclass=QueuePool,
350 )
351
352For :func:`_asyncio.create_async_engine`, use
353:class:`.AsyncAdaptedQueuePool` in the same way.
354
355.. deprecated:: 2.1
356
357 Selection of a single-connection pool class based on ``mode=memory``
358 is deprecated; a future release will use :class:`.QueuePool` for these
359 URLs, at which point stating
360 :paramref:`_sa.create_engine.poolclass` explicitly will no longer be
361 necessary. See :ref:`pysqlite_threading_pooling`.
362
363.. _pysqlite_shared_cache_lifespan:
364
365Lifespan of a Shared-Cache Memory Database
366~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
367
368A shared-cache in-memory database exists only for as long as at least one
369connection to it remains open; when the last connection is closed, the
370database and all of its contents are discarded. A subsequent connection
371using the same URI then opens a new, empty database, which typically
372surfaces as ``no such table`` errors.
373
374:class:`.QueuePool` retains connections that have been returned to it, so
375in a default configuration the database will normally persist once the
376first connection has been established. This is a consequence of pool
377behavior rather than a guarantee, however, and the database will be
378discarded by ordinary pool operations including:
379
380* :meth:`_engine.Engine.dispose`, which closes all connections currently
381 in the pool
382* :paramref:`_sa.create_engine.pool_recycle`, as a recycled connection is
383 closed before its replacement is opened
384* connection invalidation, including that performed by
385 :paramref:`_sa.create_engine.pool_pre_ping`
386* use of :class:`.NullPool`, which closes each connection as it is
387 returned
388
389Where the database must survive independently of pool activity, hold a
390single connection open for as long as the database is needed::
391
392 engine = create_engine("sqlite:///file::memory:?cache=shared&uri=true")
393
394 # keep the database alive for the lifetime of the engine
395 keepalive = engine.connect()
396
397The same consideration applies to the :ref:`aiosqlite <aiosqlite>`
398dialect, using :meth:`_asyncio.AsyncEngine.connect`.
399
400Using StaticPool for Single-Connection Memory Databases
401~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
402
403An older approach for sharing a ``:memory:`` database among threads
404is to force all checkouts to return the same DBAPI connection using
405:class:`.StaticPool`. **This approach does not support any form of
406concurrency** and is only useful when all access to the engine is
407fully serialized, such as in single-threaded test suites::
408
409 from sqlalchemy.pool import StaticPool
410
411 engine = create_engine(
412 "sqlite://",
413 connect_args={"check_same_thread": False},
414 poolclass=StaticPool,
415 )
416
417.. warning::
418
419 Because :class:`.StaticPool` maintains a single DBAPI connection,
420 all :class:`.Session` or :class:`.Connection` objects that use
421 this engine share that one underlying connection and its single
422 SQLite transaction state. A ``ROLLBACK`` issued by one session
423 (e.g. during error handling) will also roll back uncommitted work
424 from any other session, and concurrent ``COMMIT`` / ``ROLLBACK``
425 calls can interfere with each other unpredictably. This approach
426 is only appropriate when access to the engine is fully serialized,
427 such as in single-threaded test suites. For concurrent workloads,
428 use the shared-cache URI approach described above.
429
430Using Temporary Tables with SQLite
431^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
432
433Due to the way SQLite deals with temporary tables, if you wish to use a
434temporary table in a file-based SQLite database across multiple checkouts
435from the connection pool, such as when using an ORM :class:`.Session` where
436the temporary table should continue to remain after :meth:`.Session.commit` or
437:meth:`.Session.rollback` is called, a pool which maintains a single
438connection must be used. Use :class:`.SingletonThreadPool` if the scope is
439only needed within the current thread, or :class:`.StaticPool` is scope is
440needed within multiple threads for this case::
441
442 # maintain the same connection per thread
443 from sqlalchemy.pool import SingletonThreadPool
444
445 engine = create_engine("sqlite:///mydb.db", poolclass=SingletonThreadPool)
446
447
448 # maintain the same connection across all threads
449 from sqlalchemy.pool import StaticPool
450
451 engine = create_engine("sqlite:///mydb.db", poolclass=StaticPool)
452
453Note that :class:`.SingletonThreadPool` should be configured for the number
454of threads that are to be used; beyond that number, connections will be
455closed out in a non deterministic way.
456
457
458Dealing with Mixed String / Binary Columns
459------------------------------------------------------
460
461The SQLite database is weakly typed, and as such it is possible when using
462binary values, which in Python are represented as ``b'some string'``, that a
463particular SQLite database can have data values within different rows where
464some of them will be returned as a ``b''`` value by the Pysqlite driver, and
465others will be returned as Python strings, e.g. ``''`` values. This situation
466is not known to occur if the SQLAlchemy :class:`.LargeBinary` datatype is used
467consistently, however if a particular SQLite database has data that was
468inserted using the Pysqlite driver directly, or when using the SQLAlchemy
469:class:`.String` type which was later changed to :class:`.LargeBinary`, the
470table will not be consistently readable because SQLAlchemy's
471:class:`.LargeBinary` datatype does not handle strings so it has no way of
472"encoding" a value that is in string format.
473
474To deal with a SQLite table that has mixed string / binary data in the
475same column, use a custom type that will check each row individually::
476
477 from sqlalchemy import String
478 from sqlalchemy import TypeDecorator
479
480
481 class MixedBinary(TypeDecorator):
482 impl = String
483 cache_ok = True
484
485 def process_result_value(self, value, dialect):
486 if isinstance(value, str):
487 value = bytes(value, "utf-8")
488 elif value is not None:
489 value = bytes(value)
490
491 return value
492
493Then use the above ``MixedBinary`` datatype in the place where
494:class:`.LargeBinary` would normally be used.
495
496.. _pysqlite_serializable:
497
498Serializable isolation / Savepoints / Transactional DDL
499-------------------------------------------------------
500
501A newly revised version of this important section is now available
502at the top level of the SQLAlchemy SQLite documentation, in the section
503:ref:`sqlite_transactions`.
504
505
506.. _pysqlite_udfs:
507
508User-Defined Functions
509----------------------
510
511pysqlite supports a `create_function() <https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function>`_
512method that allows us to create our own user-defined functions (UDFs) in Python and use them directly in SQLite queries.
513These functions are registered with a specific DBAPI Connection.
514
515SQLAlchemy uses connection pooling with file-based SQLite databases, so we need to ensure that the UDF is attached to the
516connection when it is created. That is accomplished with an event listener::
517
518 from sqlalchemy import create_engine
519 from sqlalchemy import event
520 from sqlalchemy import text
521
522
523 def udf():
524 return "udf-ok"
525
526
527 engine = create_engine("sqlite:///./db_file")
528
529
530 @event.listens_for(engine, "connect")
531 def connect(conn, rec):
532 conn.create_function("udf", 0, udf)
533
534
535 for i in range(5):
536 with engine.connect() as conn:
537 print(conn.scalar(text("SELECT UDF()")))
538
539""" # noqa
540
541from __future__ import annotations
542
543import math
544import os
545import re
546import sys
547from typing import Any
548from typing import Callable
549from typing import cast
550from typing import Optional
551from typing import Pattern
552from typing import TYPE_CHECKING
553from typing import TypeVar
554from typing import Union
555
556from .base import DATE
557from .base import DATETIME
558from .base import SQLiteDialect
559from ... import exc
560from ... import pool
561from ... import types as sqltypes
562from ... import util
563from ...util.typing import Self
564
565if TYPE_CHECKING:
566 from ...engine.interfaces import ConnectArgsType
567 from ...engine.interfaces import DBAPIConnection
568 from ...engine.interfaces import DBAPICursor
569 from ...engine.interfaces import DBAPIModule
570 from ...engine.interfaces import IsolationLevel
571 from ...engine.interfaces import ServerVersionInfoType
572 from ...engine.url import URL
573 from ...pool.base import PoolProxiedConnection
574 from ...sql.type_api import _BindProcessorType
575 from ...sql.type_api import _ResultProcessorType
576
577
578class _SQLite_pysqliteTimeStamp(DATETIME):
579 def bind_processor( # type: ignore[override]
580 self, dialect: SQLiteDialect
581 ) -> Optional[_BindProcessorType[Any]]:
582 if dialect.native_datetime:
583 return None
584 else:
585 return DATETIME.bind_processor(self, dialect)
586
587 def result_processor( # type: ignore[override]
588 self, dialect: SQLiteDialect, coltype: object
589 ) -> Optional[_ResultProcessorType[Any]]:
590 if dialect.native_datetime:
591 return None
592 else:
593 return DATETIME.result_processor(self, dialect, coltype)
594
595
596class _SQLite_pysqliteDate(DATE):
597 def bind_processor( # type: ignore[override]
598 self, dialect: SQLiteDialect
599 ) -> Optional[_BindProcessorType[Any]]:
600 if dialect.native_datetime:
601 return None
602 else:
603 return DATE.bind_processor(self, dialect)
604
605 def result_processor( # type: ignore[override]
606 self, dialect: SQLiteDialect, coltype: object
607 ) -> Optional[_ResultProcessorType[Any]]:
608 if dialect.native_datetime:
609 return None
610 else:
611 return DATE.result_processor(self, dialect, coltype)
612
613
614class SQLiteDialect_pysqlite(SQLiteDialect):
615 default_paramstyle = "qmark"
616 supports_statement_cache = True
617 returns_native_bytes = True
618
619 colspecs = util.update_copy(
620 SQLiteDialect.colspecs,
621 {
622 sqltypes.Date: _SQLite_pysqliteDate,
623 sqltypes.TIMESTAMP: _SQLite_pysqliteTimeStamp,
624 },
625 )
626
627 description_encoding = None
628
629 driver = "pysqlite"
630
631 @classmethod
632 def import_dbapi(cls) -> DBAPIModule:
633 from sqlite3 import dbapi2 as sqlite
634
635 return cast("DBAPIModule", sqlite)
636
637 @classmethod
638 def _is_url_file_db(cls, url: URL) -> bool:
639 if (url.database and url.database != ":memory:") and (
640 url.query.get("mode", None) != "memory"
641 ):
642 return True
643 else:
644 return False
645
646 @classmethod
647 def _warn_memory_mode_pool_selection(
648 cls,
649 url: URL,
650 current_pool: type[pool.Pool],
651 future_pool: type[pool.Pool],
652 ) -> None:
653 """Warn when the ``mode=memory`` query string argument is what
654 caused a single-connection pool class to be selected.
655
656 See :ticket:`13433`.
657
658 """
659 if url.query.get("mode", None) != "memory":
660 return
661
662 util.warn_deprecated(
663 "Selection of the %s pool class based on the 'mode=memory' "
664 "query string argument is deprecated; a future release will "
665 "use %s for this URL. Indicate the intended pool class "
666 "using the create_engine.poolclass parameter."
667 % (current_pool.__name__, future_pool.__name__),
668 "2.1",
669 code="sqmp",
670 )
671
672 @classmethod
673 def get_pool_class(cls, url: URL) -> type[pool.Pool]:
674 if cls._is_url_file_db(url):
675 return pool.QueuePool
676 else:
677 cls._warn_memory_mode_pool_selection(
678 url, pool.SingletonThreadPool, pool.QueuePool
679 )
680 return pool.SingletonThreadPool
681
682 def _get_server_version_info(
683 self, connection: Any
684 ) -> ServerVersionInfoType:
685 return self.dbapi.sqlite_version_info # type: ignore[no-any-return, union-attr] # noqa: E501
686
687 def retrieve_dbapi_version(self, dbapi: DBAPIModule) -> util.VersionInfo:
688 # the ``sqlite3`` module ships with CPython and has no version of
689 # its own (the legacy ``sqlite3.version`` attribute was frozen at
690 # 2.6.0 and removed in Python 3.14), so the Python version is used
691 # here, which is what its feature set actually tracks. The version
692 # of the SQLite library itself is available as
693 # :attr:`.Dialect.server_version_info`.
694 return util.VersionInfo(sys.version_info[:3])
695
696 _isolation_lookup = SQLiteDialect._isolation_lookup.union(
697 {
698 "AUTOCOMMIT": None,
699 }
700 )
701
702 def set_isolation_level(
703 self, dbapi_connection: DBAPIConnection, level: IsolationLevel
704 ) -> None:
705 if level == "AUTOCOMMIT":
706 dbapi_connection.isolation_level = None
707 else:
708 dbapi_connection.isolation_level = ""
709 return super().set_isolation_level(dbapi_connection, level)
710
711 def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool:
712 return dbapi_conn.isolation_level is None
713
714 def on_connect(self) -> Callable[[DBAPIConnection], None]:
715 def regexp(a: str, b: Optional[str]) -> Optional[bool]:
716 if b is None:
717 return None
718 return re.search(a, b) is not None
719
720 if self._get_server_version_info(None) >= (3, 9):
721 # sqlite must be greater than 3.8.3 for deterministic=True
722 # https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function
723 # the check is more conservative since there were still issues
724 # with following 3.8 sqlite versions
725 create_func_kw = {"deterministic": True}
726 else:
727 create_func_kw = {}
728
729 def set_regexp(dbapi_connection: DBAPIConnection) -> None:
730 dbapi_connection.create_function(
731 "regexp", 2, regexp, **create_func_kw
732 )
733
734 def floor_func(dbapi_connection: DBAPIConnection) -> None:
735 # NOTE: floor is optionally present in sqlite 3.35+ , however
736 # as it is normally non-present we deliver floor() unconditionally
737 # for now.
738 # https://www.sqlite.org/lang_mathfunc.html
739 dbapi_connection.create_function(
740 "floor", 1, math.floor, **create_func_kw
741 )
742
743 fns = [set_regexp, floor_func]
744
745 def connect(conn: DBAPIConnection) -> None:
746 for fn in fns:
747 fn(conn)
748
749 return connect
750
751 def create_connect_args(self, url: URL) -> ConnectArgsType:
752 if url.username or url.password or url.host or url.port:
753 raise exc.ArgumentError(
754 "Invalid SQLite URL: %s\n"
755 "Valid SQLite URL forms are:\n"
756 " sqlite:///:memory: (or, sqlite://)\n"
757 " sqlite:///relative/path/to/file.db\n"
758 " sqlite:////absolute/path/to/file.db" % (url,)
759 )
760
761 # theoretically, this list can be augmented, at least as far as
762 # parameter names accepted by sqlite3/pysqlite, using
763 # inspect.getfullargspec(). for the moment this seems like overkill
764 # as these parameters don't change very often, and as always,
765 # parameters passed to connect_args will always go to the
766 # sqlite3/pysqlite driver.
767 pysqlite_args = [
768 ("uri", bool),
769 ("timeout", float),
770 ("isolation_level", str),
771 ("detect_types", int),
772 ("check_same_thread", bool),
773 ("cached_statements", int),
774 ]
775 opts = url.query
776 pysqlite_opts: dict[str, Any] = {}
777 for key, type_ in pysqlite_args:
778 util.coerce_kw_type(opts, key, type_, dest=pysqlite_opts)
779
780 if pysqlite_opts.get("uri", False):
781 uri_opts = dict(opts)
782 # here, we are actually separating the parameters that go to
783 # sqlite3/pysqlite vs. those that go the SQLite URI. What if
784 # two names conflict? again, this seems to be not the case right
785 # now, and in the case that new names are added to
786 # either side which overlap, again the sqlite3/pysqlite parameters
787 # can be passed through connect_args instead of in the URL.
788 # If SQLite native URIs add a parameter like "timeout" that
789 # we already have listed here for the python driver, then we need
790 # to adjust for that here.
791 for key, type_ in pysqlite_args:
792 uri_opts.pop(key, None)
793 filename: str = url.database # type: ignore[assignment]
794 if uri_opts:
795 # sorting of keys is for unit test support
796 filename += "?" + (
797 "&".join(
798 "%s=%s" % (key, uri_opts[key])
799 for key in sorted(uri_opts)
800 )
801 )
802 else:
803 # without uri=True, the SQLite URI query string is not in
804 # play at all, so anything left over here is silently
805 # discarded; warn rather than have it appear to take effect
806 ignored = sorted(
807 set(opts).difference(key for key, _ in pysqlite_args)
808 )
809 if ignored:
810 util.warn(
811 "Query string argument(s) %s are not accepted by the "
812 "pysqlite driver and are being ignored; SQLite URI "
813 "arguments require that 'uri=true' also be present "
814 "in the URL."
815 % (", ".join("'%s'" % key for key in ignored),),
816 code="squa",
817 )
818
819 filename = url.database or ":memory:"
820 if filename != ":memory:":
821 filename = os.path.abspath(filename)
822
823 pysqlite_opts.setdefault(
824 "check_same_thread", not self._is_url_file_db(url)
825 )
826
827 return ([filename], pysqlite_opts)
828
829 def is_disconnect(
830 self,
831 e: DBAPIModule.Error,
832 connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
833 cursor: Optional[DBAPICursor],
834 ) -> bool:
835 self.dbapi = cast("DBAPIModule", self.dbapi)
836 return isinstance(
837 e, self.dbapi.ProgrammingError
838 ) and "Cannot operate on a closed database." in str(e)
839
840
841dialect = SQLiteDialect_pysqlite
842
843
844class _SQLiteDialect_pysqlite_numeric(SQLiteDialect_pysqlite):
845 """numeric dialect for testing only
846
847 internal use only. This dialect is **NOT** supported by SQLAlchemy
848 and may change at any time.
849
850 """
851
852 supports_statement_cache = True
853 default_paramstyle = "numeric"
854 driver = "pysqlite_numeric"
855
856 _first_bind = ":1"
857 _not_in_statement_regexp: Optional[Pattern[str]] = None
858
859 def __init__(self, *arg: Any, **kw: Any) -> None:
860 kw.setdefault("paramstyle", "numeric")
861 super().__init__(*arg, **kw)
862
863 def create_connect_args(self, url: URL) -> ConnectArgsType:
864 arg, opts = super().create_connect_args(url)
865 opts["factory"] = self._fix_sqlite_issue_99953()
866 return arg, opts
867
868 def _fix_sqlite_issue_99953(self) -> Any:
869 import sqlite3
870
871 first_bind = self._first_bind
872 if self._not_in_statement_regexp:
873 nis = self._not_in_statement_regexp
874
875 def _test_sql(sql: str) -> None:
876 m = nis.search(sql)
877 assert not m, f"Found {nis.pattern!r} in {sql!r}"
878
879 else:
880
881 def _test_sql(sql: str) -> None:
882 pass
883
884 def _numeric_param_as_dict(
885 parameters: Any,
886 ) -> Union[dict[str, Any], tuple[Any, ...]]:
887 if parameters:
888 assert isinstance(parameters, tuple)
889 return {
890 str(idx): value for idx, value in enumerate(parameters, 1)
891 }
892 else:
893 return ()
894
895 class SQLiteFix99953Cursor(sqlite3.Cursor):
896 def execute(self, sql: str, parameters: Any = ()) -> Self:
897 _test_sql(sql)
898 if first_bind in sql:
899 parameters = _numeric_param_as_dict(parameters)
900 return super().execute(sql, parameters)
901
902 def executemany(self, sql: str, parameters: Any) -> Self:
903 _test_sql(sql)
904 if first_bind in sql:
905 parameters = [
906 _numeric_param_as_dict(p) for p in parameters
907 ]
908 return super().executemany(sql, parameters)
909
910 class SQLiteFix99953Connection(sqlite3.Connection):
911 _CursorT = TypeVar("_CursorT", bound=sqlite3.Cursor)
912
913 def cursor(
914 self,
915 factory: Optional[
916 Callable[[sqlite3.Connection], _CursorT]
917 ] = None,
918 ) -> _CursorT:
919 if factory is None:
920 factory = SQLiteFix99953Cursor # type: ignore[assignment]
921 return super().cursor(factory=factory) # type: ignore[return-value] # noqa[E501]
922
923 def execute(
924 self, sql: str, parameters: Any = ()
925 ) -> sqlite3.Cursor:
926 _test_sql(sql)
927 if first_bind in sql:
928 parameters = _numeric_param_as_dict(parameters)
929 return super().execute(sql, parameters)
930
931 def executemany(self, sql: str, parameters: Any) -> sqlite3.Cursor:
932 _test_sql(sql)
933 if first_bind in sql:
934 parameters = [
935 _numeric_param_as_dict(p) for p in parameters
936 ]
937 return super().executemany(sql, parameters)
938
939 return SQLiteFix99953Connection
940
941
942class _SQLiteDialect_pysqlite_dollar(_SQLiteDialect_pysqlite_numeric):
943 """numeric dialect that uses $ for testing only
944
945 internal use only. This dialect is **NOT** supported by SQLAlchemy
946 and may change at any time.
947
948 """
949
950 supports_statement_cache = True
951 default_paramstyle = "numeric_dollar"
952 driver = "pysqlite_dollar"
953
954 _first_bind = "$1"
955 _not_in_statement_regexp = re.compile(r"[^\d]:\d+")
956
957 def __init__(self, *arg: Any, **kw: Any) -> None:
958 kw.setdefault("paramstyle", "numeric_dollar")
959 super().__init__(*arg, **kw)