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