mirror of
https://gitlab.com/MoonTestUse1/AdministrationItDepartmens.git
synced 2025-08-14 00:25:46 +02:00
Проверка 09.02.2025
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# dialects/sqlite/__init__.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/sqlite/aiosqlite.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -31,6 +31,7 @@ This dialect should normally be used only with the
|
||||
:func:`_asyncio.create_async_engine` engine creation function::
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
|
||||
engine = create_async_engine("sqlite+aiosqlite:///filename")
|
||||
|
||||
The URL passes through all arguments to the ``pysqlite`` driver, so all
|
||||
@@ -58,12 +59,14 @@ The solution is similar to :ref:`pysqlite_serializable`. This is achieved by the
|
||||
|
||||
engine = create_async_engine("sqlite+aiosqlite:///myfile.db")
|
||||
|
||||
|
||||
@event.listens_for(engine.sync_engine, "connect")
|
||||
def do_connect(dbapi_connection, connection_record):
|
||||
# disable aiosqlite's emitting of the BEGIN statement entirely.
|
||||
# also stops it from emitting COMMIT before any DDL.
|
||||
dbapi_connection.isolation_level = None
|
||||
|
||||
|
||||
@event.listens_for(engine.sync_engine, "begin")
|
||||
def do_begin(conn):
|
||||
# emit our own BEGIN
|
||||
@@ -75,9 +78,32 @@ The solution is similar to :ref:`pysqlite_serializable`. This is achieved by the
|
||||
with the SQLite driver,
|
||||
as this function necessarily will also alter the ".isolation_level" setting.
|
||||
|
||||
.. _aiosqlite_pooling:
|
||||
|
||||
Pooling Behavior
|
||||
----------------
|
||||
|
||||
The SQLAlchemy ``aiosqlite`` DBAPI establishes the connection pool differently
|
||||
based on the kind of SQLite database that's requested:
|
||||
|
||||
* When a ``:memory:`` SQLite database is specified, the dialect by default
|
||||
will use :class:`.StaticPool`. This pool maintains a single
|
||||
connection, so that all access to the engine
|
||||
use the same ``:memory:`` database.
|
||||
* When a file-based database is specified, the dialect will use
|
||||
:class:`.AsyncAdaptedQueuePool` as the source of connections.
|
||||
|
||||
.. versionchanged:: 2.0.38
|
||||
|
||||
SQLite file database engines now use :class:`.AsyncAdaptedQueuePool` by default.
|
||||
Previously, :class:`.NullPool` were used. The :class:`.NullPool` class
|
||||
may be used by specifying it via the
|
||||
:paramref:`_sa.create_engine.poolclass` parameter.
|
||||
|
||||
""" # noqa
|
||||
|
||||
import asyncio
|
||||
from collections import deque
|
||||
from functools import partial
|
||||
|
||||
from .base import SQLiteExecutionContext
|
||||
@@ -113,10 +139,10 @@ class AsyncAdapt_aiosqlite_cursor:
|
||||
self.arraysize = 1
|
||||
self.rowcount = -1
|
||||
self.description = None
|
||||
self._rows = []
|
||||
self._rows = deque()
|
||||
|
||||
def close(self):
|
||||
self._rows[:] = []
|
||||
self._rows.clear()
|
||||
|
||||
def execute(self, operation, parameters=None):
|
||||
try:
|
||||
@@ -132,7 +158,7 @@ class AsyncAdapt_aiosqlite_cursor:
|
||||
self.lastrowid = self.rowcount = -1
|
||||
|
||||
if not self.server_side:
|
||||
self._rows = self.await_(_cursor.fetchall())
|
||||
self._rows = deque(self.await_(_cursor.fetchall()))
|
||||
else:
|
||||
self.description = None
|
||||
self.lastrowid = _cursor.lastrowid
|
||||
@@ -161,11 +187,11 @@ class AsyncAdapt_aiosqlite_cursor:
|
||||
|
||||
def __iter__(self):
|
||||
while self._rows:
|
||||
yield self._rows.pop(0)
|
||||
yield self._rows.popleft()
|
||||
|
||||
def fetchone(self):
|
||||
if self._rows:
|
||||
return self._rows.pop(0)
|
||||
return self._rows.popleft()
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -173,13 +199,12 @@ class AsyncAdapt_aiosqlite_cursor:
|
||||
if size is None:
|
||||
size = self.arraysize
|
||||
|
||||
retval = self._rows[0:size]
|
||||
self._rows[:] = self._rows[size:]
|
||||
return retval
|
||||
rr = self._rows
|
||||
return [rr.popleft() for _ in range(min(size, len(rr)))]
|
||||
|
||||
def fetchall(self):
|
||||
retval = self._rows[:]
|
||||
self._rows[:] = []
|
||||
retval = list(self._rows)
|
||||
self._rows.clear()
|
||||
return retval
|
||||
|
||||
|
||||
@@ -377,7 +402,7 @@ class SQLiteDialect_aiosqlite(SQLiteDialect_pysqlite):
|
||||
@classmethod
|
||||
def get_pool_class(cls, url):
|
||||
if cls._is_url_file_db(url):
|
||||
return pool.NullPool
|
||||
return pool.AsyncAdaptedQueuePool
|
||||
else:
|
||||
return pool.StaticPool
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/sqlite/base.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -7,10 +7,9 @@
|
||||
# mypy: ignore-errors
|
||||
|
||||
|
||||
r"""
|
||||
r'''
|
||||
.. dialect:: sqlite
|
||||
:name: SQLite
|
||||
:full_support: 3.36.0
|
||||
:normal_support: 3.12+
|
||||
:best_effort: 3.7.16+
|
||||
|
||||
@@ -70,9 +69,12 @@ To specifically render the AUTOINCREMENT keyword on the primary key column
|
||||
when rendering DDL, add the flag ``sqlite_autoincrement=True`` to the Table
|
||||
construct::
|
||||
|
||||
Table('sometable', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
sqlite_autoincrement=True)
|
||||
Table(
|
||||
"sometable",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True),
|
||||
sqlite_autoincrement=True,
|
||||
)
|
||||
|
||||
Allowing autoincrement behavior SQLAlchemy types other than Integer/INTEGER
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -92,8 +94,13 @@ One approach to achieve this is to use :class:`.Integer` on SQLite
|
||||
only using :meth:`.TypeEngine.with_variant`::
|
||||
|
||||
table = Table(
|
||||
"my_table", metadata,
|
||||
Column("id", BigInteger().with_variant(Integer, "sqlite"), primary_key=True)
|
||||
"my_table",
|
||||
metadata,
|
||||
Column(
|
||||
"id",
|
||||
BigInteger().with_variant(Integer, "sqlite"),
|
||||
primary_key=True,
|
||||
),
|
||||
)
|
||||
|
||||
Another is to use a subclass of :class:`.BigInteger` that overrides its DDL
|
||||
@@ -102,21 +109,23 @@ name to be ``INTEGER`` when compiled against SQLite::
|
||||
from sqlalchemy import BigInteger
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
|
||||
|
||||
class SLBigInteger(BigInteger):
|
||||
pass
|
||||
|
||||
@compiles(SLBigInteger, 'sqlite')
|
||||
|
||||
@compiles(SLBigInteger, "sqlite")
|
||||
def bi_c(element, compiler, **kw):
|
||||
return "INTEGER"
|
||||
|
||||
|
||||
@compiles(SLBigInteger)
|
||||
def bi_c(element, compiler, **kw):
|
||||
return compiler.visit_BIGINT(element, **kw)
|
||||
|
||||
|
||||
table = Table(
|
||||
"my_table", metadata,
|
||||
Column("id", SLBigInteger(), primary_key=True)
|
||||
"my_table", metadata, Column("id", SLBigInteger(), primary_key=True)
|
||||
)
|
||||
|
||||
.. seealso::
|
||||
@@ -236,26 +245,24 @@ To specify an explicit ``RETURNING`` clause, use the
|
||||
|
||||
# INSERT..RETURNING
|
||||
result = connection.execute(
|
||||
table.insert().
|
||||
values(name='foo').
|
||||
returning(table.c.col1, table.c.col2)
|
||||
table.insert().values(name="foo").returning(table.c.col1, table.c.col2)
|
||||
)
|
||||
print(result.all())
|
||||
|
||||
# UPDATE..RETURNING
|
||||
result = connection.execute(
|
||||
table.update().
|
||||
where(table.c.name=='foo').
|
||||
values(name='bar').
|
||||
returning(table.c.col1, table.c.col2)
|
||||
table.update()
|
||||
.where(table.c.name == "foo")
|
||||
.values(name="bar")
|
||||
.returning(table.c.col1, table.c.col2)
|
||||
)
|
||||
print(result.all())
|
||||
|
||||
# DELETE..RETURNING
|
||||
result = connection.execute(
|
||||
table.delete().
|
||||
where(table.c.name=='foo').
|
||||
returning(table.c.col1, table.c.col2)
|
||||
table.delete()
|
||||
.where(table.c.name == "foo")
|
||||
.returning(table.c.col1, table.c.col2)
|
||||
)
|
||||
print(result.all())
|
||||
|
||||
@@ -318,6 +325,7 @@ new connections through the usage of events::
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy import event
|
||||
|
||||
|
||||
@event.listens_for(Engine, "connect")
|
||||
def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||
cursor = dbapi_connection.cursor()
|
||||
@@ -380,13 +388,16 @@ ABORT, FAIL, IGNORE, and REPLACE. For example, to add a UNIQUE constraint
|
||||
that specifies the IGNORE algorithm::
|
||||
|
||||
some_table = Table(
|
||||
'some_table', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('data', Integer),
|
||||
UniqueConstraint('id', 'data', sqlite_on_conflict='IGNORE')
|
||||
"some_table",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True),
|
||||
Column("data", Integer),
|
||||
UniqueConstraint("id", "data", sqlite_on_conflict="IGNORE"),
|
||||
)
|
||||
|
||||
The above renders CREATE TABLE DDL as::
|
||||
The above renders CREATE TABLE DDL as:
|
||||
|
||||
.. sourcecode:: sql
|
||||
|
||||
CREATE TABLE some_table (
|
||||
id INTEGER NOT NULL,
|
||||
@@ -403,13 +414,17 @@ be added to the :class:`_schema.Column` as well, which will be added to the
|
||||
UNIQUE constraint in the DDL::
|
||||
|
||||
some_table = Table(
|
||||
'some_table', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('data', Integer, unique=True,
|
||||
sqlite_on_conflict_unique='IGNORE')
|
||||
"some_table",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True),
|
||||
Column(
|
||||
"data", Integer, unique=True, sqlite_on_conflict_unique="IGNORE"
|
||||
),
|
||||
)
|
||||
|
||||
rendering::
|
||||
rendering:
|
||||
|
||||
.. sourcecode:: sql
|
||||
|
||||
CREATE TABLE some_table (
|
||||
id INTEGER NOT NULL,
|
||||
@@ -422,13 +437,17 @@ To apply the FAIL algorithm for a NOT NULL constraint,
|
||||
``sqlite_on_conflict_not_null`` is used::
|
||||
|
||||
some_table = Table(
|
||||
'some_table', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('data', Integer, nullable=False,
|
||||
sqlite_on_conflict_not_null='FAIL')
|
||||
"some_table",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True),
|
||||
Column(
|
||||
"data", Integer, nullable=False, sqlite_on_conflict_not_null="FAIL"
|
||||
),
|
||||
)
|
||||
|
||||
this renders the column inline ON CONFLICT phrase::
|
||||
this renders the column inline ON CONFLICT phrase:
|
||||
|
||||
.. sourcecode:: sql
|
||||
|
||||
CREATE TABLE some_table (
|
||||
id INTEGER NOT NULL,
|
||||
@@ -440,13 +459,20 @@ this renders the column inline ON CONFLICT phrase::
|
||||
Similarly, for an inline primary key, use ``sqlite_on_conflict_primary_key``::
|
||||
|
||||
some_table = Table(
|
||||
'some_table', metadata,
|
||||
Column('id', Integer, primary_key=True,
|
||||
sqlite_on_conflict_primary_key='FAIL')
|
||||
"some_table",
|
||||
metadata,
|
||||
Column(
|
||||
"id",
|
||||
Integer,
|
||||
primary_key=True,
|
||||
sqlite_on_conflict_primary_key="FAIL",
|
||||
),
|
||||
)
|
||||
|
||||
SQLAlchemy renders the PRIMARY KEY constraint separately, so the conflict
|
||||
resolution algorithm is applied to the constraint itself::
|
||||
resolution algorithm is applied to the constraint itself:
|
||||
|
||||
.. sourcecode:: sql
|
||||
|
||||
CREATE TABLE some_table (
|
||||
id INTEGER NOT NULL,
|
||||
@@ -456,7 +482,7 @@ resolution algorithm is applied to the constraint itself::
|
||||
.. _sqlite_on_conflict_insert:
|
||||
|
||||
INSERT...ON CONFLICT (Upsert)
|
||||
-----------------------------------
|
||||
-----------------------------
|
||||
|
||||
.. seealso:: This section describes the :term:`DML` version of "ON CONFLICT" for
|
||||
SQLite, which occurs within an INSERT statement. For "ON CONFLICT" as
|
||||
@@ -484,21 +510,18 @@ and :meth:`_sqlite.Insert.on_conflict_do_nothing`:
|
||||
>>> from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
>>> insert_stmt = insert(my_table).values(
|
||||
... id='some_existing_id',
|
||||
... data='inserted value')
|
||||
... id="some_existing_id", data="inserted value"
|
||||
... )
|
||||
|
||||
>>> do_update_stmt = insert_stmt.on_conflict_do_update(
|
||||
... index_elements=['id'],
|
||||
... set_=dict(data='updated value')
|
||||
... index_elements=["id"], set_=dict(data="updated value")
|
||||
... )
|
||||
|
||||
>>> print(do_update_stmt)
|
||||
{printsql}INSERT INTO my_table (id, data) VALUES (?, ?)
|
||||
ON CONFLICT (id) DO UPDATE SET data = ?{stop}
|
||||
|
||||
>>> do_nothing_stmt = insert_stmt.on_conflict_do_nothing(
|
||||
... index_elements=['id']
|
||||
... )
|
||||
>>> do_nothing_stmt = insert_stmt.on_conflict_do_nothing(index_elements=["id"])
|
||||
|
||||
>>> print(do_nothing_stmt)
|
||||
{printsql}INSERT INTO my_table (id, data) VALUES (?, ?)
|
||||
@@ -529,13 +552,13 @@ Both methods supply the "target" of the conflict using column inference:
|
||||
|
||||
.. sourcecode:: pycon+sql
|
||||
|
||||
>>> stmt = insert(my_table).values(user_email='a@b.com', data='inserted data')
|
||||
>>> stmt = insert(my_table).values(user_email="a@b.com", data="inserted data")
|
||||
|
||||
>>> do_update_stmt = stmt.on_conflict_do_update(
|
||||
... index_elements=[my_table.c.user_email],
|
||||
... index_where=my_table.c.user_email.like('%@gmail.com'),
|
||||
... set_=dict(data=stmt.excluded.data)
|
||||
... )
|
||||
... index_where=my_table.c.user_email.like("%@gmail.com"),
|
||||
... set_=dict(data=stmt.excluded.data),
|
||||
... )
|
||||
|
||||
>>> print(do_update_stmt)
|
||||
{printsql}INSERT INTO my_table (data, user_email) VALUES (?, ?)
|
||||
@@ -555,11 +578,10 @@ for UPDATE:
|
||||
|
||||
.. sourcecode:: pycon+sql
|
||||
|
||||
>>> stmt = insert(my_table).values(id='some_id', data='inserted value')
|
||||
>>> stmt = insert(my_table).values(id="some_id", data="inserted value")
|
||||
|
||||
>>> do_update_stmt = stmt.on_conflict_do_update(
|
||||
... index_elements=['id'],
|
||||
... set_=dict(data='updated value')
|
||||
... index_elements=["id"], set_=dict(data="updated value")
|
||||
... )
|
||||
|
||||
>>> print(do_update_stmt)
|
||||
@@ -587,14 +609,12 @@ would have been inserted had the constraint not failed:
|
||||
.. sourcecode:: pycon+sql
|
||||
|
||||
>>> stmt = insert(my_table).values(
|
||||
... id='some_id',
|
||||
... data='inserted value',
|
||||
... author='jlh'
|
||||
... id="some_id", data="inserted value", author="jlh"
|
||||
... )
|
||||
|
||||
>>> do_update_stmt = stmt.on_conflict_do_update(
|
||||
... index_elements=['id'],
|
||||
... set_=dict(data='updated value', author=stmt.excluded.author)
|
||||
... index_elements=["id"],
|
||||
... set_=dict(data="updated value", author=stmt.excluded.author),
|
||||
... )
|
||||
|
||||
>>> print(do_update_stmt)
|
||||
@@ -611,15 +631,13 @@ parameter, which will limit those rows which receive an UPDATE:
|
||||
.. sourcecode:: pycon+sql
|
||||
|
||||
>>> stmt = insert(my_table).values(
|
||||
... id='some_id',
|
||||
... data='inserted value',
|
||||
... author='jlh'
|
||||
... id="some_id", data="inserted value", author="jlh"
|
||||
... )
|
||||
|
||||
>>> on_update_stmt = stmt.on_conflict_do_update(
|
||||
... index_elements=['id'],
|
||||
... set_=dict(data='updated value', author=stmt.excluded.author),
|
||||
... where=(my_table.c.status == 2)
|
||||
... index_elements=["id"],
|
||||
... set_=dict(data="updated value", author=stmt.excluded.author),
|
||||
... where=(my_table.c.status == 2),
|
||||
... )
|
||||
>>> print(on_update_stmt)
|
||||
{printsql}INSERT INTO my_table (id, data, author) VALUES (?, ?, ?)
|
||||
@@ -636,8 +654,8 @@ using the :meth:`_sqlite.Insert.on_conflict_do_nothing` method:
|
||||
|
||||
.. sourcecode:: pycon+sql
|
||||
|
||||
>>> stmt = insert(my_table).values(id='some_id', data='inserted value')
|
||||
>>> stmt = stmt.on_conflict_do_nothing(index_elements=['id'])
|
||||
>>> stmt = insert(my_table).values(id="some_id", data="inserted value")
|
||||
>>> stmt = stmt.on_conflict_do_nothing(index_elements=["id"])
|
||||
>>> print(stmt)
|
||||
{printsql}INSERT INTO my_table (id, data) VALUES (?, ?) ON CONFLICT (id) DO NOTHING
|
||||
|
||||
@@ -648,7 +666,7 @@ occurs:
|
||||
|
||||
.. sourcecode:: pycon+sql
|
||||
|
||||
>>> stmt = insert(my_table).values(id='some_id', data='inserted value')
|
||||
>>> stmt = insert(my_table).values(id="some_id", data="inserted value")
|
||||
>>> stmt = stmt.on_conflict_do_nothing()
|
||||
>>> print(stmt)
|
||||
{printsql}INSERT INTO my_table (id, data) VALUES (?, ?) ON CONFLICT DO NOTHING
|
||||
@@ -708,11 +726,16 @@ Partial Indexes
|
||||
A partial index, e.g. one which uses a WHERE clause, can be specified
|
||||
with the DDL system using the argument ``sqlite_where``::
|
||||
|
||||
tbl = Table('testtbl', m, Column('data', Integer))
|
||||
idx = Index('test_idx1', tbl.c.data,
|
||||
sqlite_where=and_(tbl.c.data > 5, tbl.c.data < 10))
|
||||
tbl = Table("testtbl", m, Column("data", Integer))
|
||||
idx = Index(
|
||||
"test_idx1",
|
||||
tbl.c.data,
|
||||
sqlite_where=and_(tbl.c.data > 5, tbl.c.data < 10),
|
||||
)
|
||||
|
||||
The index will be rendered at create time as::
|
||||
The index will be rendered at create time as:
|
||||
|
||||
.. sourcecode:: sql
|
||||
|
||||
CREATE INDEX test_idx1 ON testtbl (data)
|
||||
WHERE data > 5 AND data < 10
|
||||
@@ -732,7 +755,11 @@ The bug, entirely outside of SQLAlchemy, can be illustrated thusly::
|
||||
|
||||
import sqlite3
|
||||
|
||||
assert sqlite3.sqlite_version_info < (3, 10, 0), "bug is fixed in this version"
|
||||
assert sqlite3.sqlite_version_info < (
|
||||
3,
|
||||
10,
|
||||
0,
|
||||
), "bug is fixed in this version"
|
||||
|
||||
conn = sqlite3.connect(":memory:")
|
||||
cursor = conn.cursor()
|
||||
@@ -742,17 +769,22 @@ The bug, entirely outside of SQLAlchemy, can be illustrated thusly::
|
||||
cursor.execute("insert into x (a, b) values (2, 2)")
|
||||
|
||||
cursor.execute("select x.a, x.b from x")
|
||||
assert [c[0] for c in cursor.description] == ['a', 'b']
|
||||
assert [c[0] for c in cursor.description] == ["a", "b"]
|
||||
|
||||
cursor.execute('''
|
||||
cursor.execute(
|
||||
"""
|
||||
select x.a, x.b from x where a=1
|
||||
union
|
||||
select x.a, x.b from x where a=2
|
||||
''')
|
||||
assert [c[0] for c in cursor.description] == ['a', 'b'], \
|
||||
[c[0] for c in cursor.description]
|
||||
"""
|
||||
)
|
||||
assert [c[0] for c in cursor.description] == ["a", "b"], [
|
||||
c[0] for c in cursor.description
|
||||
]
|
||||
|
||||
The second assertion fails::
|
||||
The second assertion fails:
|
||||
|
||||
.. sourcecode:: text
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "test.py", line 19, in <module>
|
||||
@@ -780,11 +812,13 @@ to filter these out::
|
||||
result = conn.exec_driver_sql("select x.a, x.b from x")
|
||||
assert result.keys() == ["a", "b"]
|
||||
|
||||
result = conn.exec_driver_sql('''
|
||||
result = conn.exec_driver_sql(
|
||||
"""
|
||||
select x.a, x.b from x where a=1
|
||||
union
|
||||
select x.a, x.b from x where a=2
|
||||
''')
|
||||
"""
|
||||
)
|
||||
assert result.keys() == ["a", "b"]
|
||||
|
||||
Note that above, even though SQLAlchemy filters out the dots, *both
|
||||
@@ -808,16 +842,20 @@ contain dots, and the functionality of :meth:`_engine.CursorResult.keys` and
|
||||
the ``sqlite_raw_colnames`` execution option may be provided, either on a
|
||||
per-:class:`_engine.Connection` basis::
|
||||
|
||||
result = conn.execution_options(sqlite_raw_colnames=True).exec_driver_sql('''
|
||||
result = conn.execution_options(sqlite_raw_colnames=True).exec_driver_sql(
|
||||
"""
|
||||
select x.a, x.b from x where a=1
|
||||
union
|
||||
select x.a, x.b from x where a=2
|
||||
''')
|
||||
"""
|
||||
)
|
||||
assert result.keys() == ["x.a", "x.b"]
|
||||
|
||||
or on a per-:class:`_engine.Engine` basis::
|
||||
|
||||
engine = create_engine("sqlite://", execution_options={"sqlite_raw_colnames": True})
|
||||
engine = create_engine(
|
||||
"sqlite://", execution_options={"sqlite_raw_colnames": True}
|
||||
)
|
||||
|
||||
When using the per-:class:`_engine.Engine` execution option, note that
|
||||
**Core and ORM queries that use UNION may not function properly**.
|
||||
@@ -832,12 +870,18 @@ dialect in conjunction with the :class:`_schema.Table` construct:
|
||||
|
||||
Table("some_table", metadata, ..., sqlite_with_rowid=False)
|
||||
|
||||
*
|
||||
``STRICT``::
|
||||
|
||||
Table("some_table", metadata, ..., sqlite_strict=True)
|
||||
|
||||
.. versionadded:: 2.0.37
|
||||
|
||||
.. seealso::
|
||||
|
||||
`SQLite CREATE TABLE options
|
||||
<https://www.sqlite.org/lang_createtable.html>`_
|
||||
|
||||
|
||||
.. _sqlite_include_internal:
|
||||
|
||||
Reflecting internal schema tables
|
||||
@@ -866,7 +910,7 @@ passed to methods such as :meth:`_schema.MetaData.reflect` or
|
||||
`SQLite Internal Schema Objects <https://www.sqlite.org/fileformat2.html#intschema>`_ - in the SQLite
|
||||
documentation.
|
||||
|
||||
""" # noqa
|
||||
''' # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
@@ -980,7 +1024,9 @@ class DATETIME(_DateTimeMixin, sqltypes.DateTime):
|
||||
|
||||
"%(year)04d-%(month)02d-%(day)02d %(hour)02d:%(minute)02d:%(second)02d.%(microsecond)06d"
|
||||
|
||||
e.g.::
|
||||
e.g.:
|
||||
|
||||
.. sourcecode:: text
|
||||
|
||||
2021-03-15 12:05:57.105542
|
||||
|
||||
@@ -996,9 +1042,11 @@ class DATETIME(_DateTimeMixin, sqltypes.DateTime):
|
||||
import re
|
||||
from sqlalchemy.dialects.sqlite import DATETIME
|
||||
|
||||
dt = DATETIME(storage_format="%(year)04d/%(month)02d/%(day)02d "
|
||||
"%(hour)02d:%(minute)02d:%(second)02d",
|
||||
regexp=r"(\d+)/(\d+)/(\d+) (\d+)-(\d+)-(\d+)"
|
||||
dt = DATETIME(
|
||||
storage_format=(
|
||||
"%(year)04d/%(month)02d/%(day)02d %(hour)02d:%(minute)02d:%(second)02d"
|
||||
),
|
||||
regexp=r"(\d+)/(\d+)/(\d+) (\d+)-(\d+)-(\d+)",
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be applied to the dict
|
||||
@@ -1088,7 +1136,9 @@ class DATE(_DateTimeMixin, sqltypes.Date):
|
||||
|
||||
"%(year)04d-%(month)02d-%(day)02d"
|
||||
|
||||
e.g.::
|
||||
e.g.:
|
||||
|
||||
.. sourcecode:: text
|
||||
|
||||
2011-03-15
|
||||
|
||||
@@ -1106,9 +1156,9 @@ class DATE(_DateTimeMixin, sqltypes.Date):
|
||||
from sqlalchemy.dialects.sqlite import DATE
|
||||
|
||||
d = DATE(
|
||||
storage_format="%(month)02d/%(day)02d/%(year)04d",
|
||||
regexp=re.compile("(?P<month>\d+)/(?P<day>\d+)/(?P<year>\d+)")
|
||||
)
|
||||
storage_format="%(month)02d/%(day)02d/%(year)04d",
|
||||
regexp=re.compile("(?P<month>\d+)/(?P<day>\d+)/(?P<year>\d+)"),
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be applied to the
|
||||
dict with keys year, month, and day.
|
||||
@@ -1162,7 +1212,9 @@ class TIME(_DateTimeMixin, sqltypes.Time):
|
||||
|
||||
"%(hour)02d:%(minute)02d:%(second)02d.%(microsecond)06d"
|
||||
|
||||
e.g.::
|
||||
e.g.:
|
||||
|
||||
.. sourcecode:: text
|
||||
|
||||
12:05:57.10558
|
||||
|
||||
@@ -1178,9 +1230,9 @@ class TIME(_DateTimeMixin, sqltypes.Time):
|
||||
import re
|
||||
from sqlalchemy.dialects.sqlite import TIME
|
||||
|
||||
t = TIME(storage_format="%(hour)02d-%(minute)02d-"
|
||||
"%(second)02d-%(microsecond)06d",
|
||||
regexp=re.compile("(\d+)-(\d+)-(\d+)-(?:-(\d+))?")
|
||||
t = TIME(
|
||||
storage_format="%(hour)02d-%(minute)02d-%(second)02d-%(microsecond)06d",
|
||||
regexp=re.compile("(\d+)-(\d+)-(\d+)-(?:-(\d+))?"),
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be applied to the dict
|
||||
@@ -1429,9 +1481,7 @@ class SQLiteCompiler(compiler.SQLCompiler):
|
||||
return self._generate_generic_binary(binary, " NOT REGEXP ", **kw)
|
||||
|
||||
def _on_conflict_target(self, clause, **kw):
|
||||
if clause.constraint_target is not None:
|
||||
target_text = "(%s)" % clause.constraint_target
|
||||
elif clause.inferred_target_elements is not None:
|
||||
if clause.inferred_target_elements is not None:
|
||||
target_text = "(%s)" % ", ".join(
|
||||
(
|
||||
self.preparer.quote(c)
|
||||
@@ -1445,7 +1495,7 @@ class SQLiteCompiler(compiler.SQLCompiler):
|
||||
clause.inferred_target_whereclause,
|
||||
include_table=False,
|
||||
use_schema=False,
|
||||
literal_binds=True,
|
||||
literal_execute=True,
|
||||
)
|
||||
|
||||
else:
|
||||
@@ -1528,6 +1578,13 @@ class SQLiteCompiler(compiler.SQLCompiler):
|
||||
|
||||
return "ON CONFLICT %s DO UPDATE SET %s" % (target_text, action_text)
|
||||
|
||||
def visit_bitwise_xor_op_binary(self, binary, operator, **kw):
|
||||
# sqlite has no xor. Use "a XOR b" = "(a | b) - (a & b)".
|
||||
kw["eager_grouping"] = True
|
||||
or_ = self._generate_generic_binary(binary, " | ", **kw)
|
||||
and_ = self._generate_generic_binary(binary, " & ", **kw)
|
||||
return f"({or_} - {and_})"
|
||||
|
||||
|
||||
class SQLiteDDLCompiler(compiler.DDLCompiler):
|
||||
def get_column_specification(self, column, **kwargs):
|
||||
@@ -1701,9 +1758,12 @@ class SQLiteDDLCompiler(compiler.DDLCompiler):
|
||||
return text
|
||||
|
||||
def post_create_table(self, table):
|
||||
text = ""
|
||||
if table.dialect_options["sqlite"]["with_rowid"] is False:
|
||||
return "\n WITHOUT ROWID"
|
||||
return ""
|
||||
text += "\n WITHOUT ROWID"
|
||||
if table.dialect_options["sqlite"]["strict"] is True:
|
||||
text += "\n STRICT"
|
||||
return text
|
||||
|
||||
|
||||
class SQLiteTypeCompiler(compiler.GenericTypeCompiler):
|
||||
@@ -1938,6 +1998,7 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
{
|
||||
"autoincrement": False,
|
||||
"with_rowid": True,
|
||||
"strict": False,
|
||||
},
|
||||
),
|
||||
(sa_schema.Index, {"where": None}),
|
||||
@@ -2231,6 +2292,14 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
tablesql = self._get_table_sql(
|
||||
connection, table_name, schema, **kw
|
||||
)
|
||||
# remove create table
|
||||
match = re.match(
|
||||
r"create table .*?\((.*)\)$",
|
||||
tablesql.strip(),
|
||||
re.DOTALL | re.IGNORECASE,
|
||||
)
|
||||
assert match, f"create table not found in {tablesql}"
|
||||
tablesql = match.group(1).strip()
|
||||
|
||||
columns.append(
|
||||
self._get_column_info(
|
||||
@@ -2285,7 +2354,10 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
if generated:
|
||||
sqltext = ""
|
||||
if tablesql:
|
||||
pattern = r"[^,]*\s+AS\s+\(([^,]*)\)\s*(?:virtual|stored)?"
|
||||
pattern = (
|
||||
r"[^,]*\s+GENERATED\s+ALWAYS\s+AS"
|
||||
r"\s+\((.*)\)\s*(?:virtual|stored)?"
|
||||
)
|
||||
match = re.search(
|
||||
re.escape(name) + pattern, tablesql, re.IGNORECASE
|
||||
)
|
||||
@@ -2570,8 +2642,8 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
return
|
||||
UNIQUE_PATTERN = r'(?:CONSTRAINT "?(.+?)"? +)?UNIQUE *\((.+?)\)'
|
||||
INLINE_UNIQUE_PATTERN = (
|
||||
r'(?:(".+?")|(?:[\[`])?([a-z0-9_]+)(?:[\]`])?) '
|
||||
r"+[a-z0-9_ ]+? +UNIQUE"
|
||||
r'(?:(".+?")|(?:[\[`])?([a-z0-9_]+)(?:[\]`])?)[\t ]'
|
||||
r"+[a-z0-9_ ]+?[\t ]+UNIQUE"
|
||||
)
|
||||
|
||||
for match in re.finditer(UNIQUE_PATTERN, table_data, re.I):
|
||||
@@ -2606,15 +2678,21 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
connection, table_name, schema=schema, **kw
|
||||
)
|
||||
|
||||
CHECK_PATTERN = r"(?:CONSTRAINT (.+) +)?" r"CHECK *\( *(.+) *\),? *"
|
||||
cks = []
|
||||
# NOTE: we aren't using re.S here because we actually are
|
||||
# taking advantage of each CHECK constraint being all on one
|
||||
# line in the table definition in order to delineate. This
|
||||
# NOTE NOTE NOTE
|
||||
# DO NOT CHANGE THIS REGULAR EXPRESSION. There is no known way
|
||||
# to parse CHECK constraints that contain newlines themselves using
|
||||
# regular expressions, and the approach here relies upon each
|
||||
# individual
|
||||
# CHECK constraint being on a single line by itself. This
|
||||
# necessarily makes assumptions as to how the CREATE TABLE
|
||||
# was emitted.
|
||||
# was emitted. A more comprehensive DDL parsing solution would be
|
||||
# needed to improve upon the current situation. See #11840 for
|
||||
# background
|
||||
CHECK_PATTERN = r"(?:CONSTRAINT (.+) +)?CHECK *\( *(.+) *\),? *"
|
||||
cks = []
|
||||
|
||||
for match in re.finditer(CHECK_PATTERN, table_data or "", re.I):
|
||||
|
||||
name = match.group(1)
|
||||
|
||||
if name:
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/sqlite/dml.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -7,6 +7,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
from .._typing import _OnConflictIndexElementsT
|
||||
from .._typing import _OnConflictIndexWhereT
|
||||
@@ -15,6 +19,7 @@ from .._typing import _OnConflictWhereT
|
||||
from ... import util
|
||||
from ...sql import coercions
|
||||
from ...sql import roles
|
||||
from ...sql import schema
|
||||
from ...sql._typing import _DMLTableArgument
|
||||
from ...sql.base import _exclusive_against
|
||||
from ...sql.base import _generative
|
||||
@@ -22,7 +27,9 @@ from ...sql.base import ColumnCollection
|
||||
from ...sql.base import ReadOnlyColumnCollection
|
||||
from ...sql.dml import Insert as StandardInsert
|
||||
from ...sql.elements import ClauseElement
|
||||
from ...sql.elements import ColumnElement
|
||||
from ...sql.elements import KeyedColumnElement
|
||||
from ...sql.elements import TextClause
|
||||
from ...sql.expression import alias
|
||||
from ...util.typing import Self
|
||||
|
||||
@@ -141,11 +148,10 @@ class Insert(StandardInsert):
|
||||
:paramref:`.Insert.on_conflict_do_update.set_` dictionary.
|
||||
|
||||
:param where:
|
||||
Optional argument. If present, can be a literal SQL
|
||||
string or an acceptable expression for a ``WHERE`` clause
|
||||
that restricts the rows affected by ``DO UPDATE SET``. Rows
|
||||
not meeting the ``WHERE`` condition will not be updated
|
||||
(effectively a ``DO NOTHING`` for those rows).
|
||||
Optional argument. An expression object representing a ``WHERE``
|
||||
clause that restricts the rows affected by ``DO UPDATE SET``. Rows not
|
||||
meeting the ``WHERE`` condition will not be updated (effectively a
|
||||
``DO NOTHING`` for those rows).
|
||||
|
||||
"""
|
||||
|
||||
@@ -184,9 +190,10 @@ class Insert(StandardInsert):
|
||||
class OnConflictClause(ClauseElement):
|
||||
stringify_dialect = "sqlite"
|
||||
|
||||
constraint_target: None
|
||||
inferred_target_elements: _OnConflictIndexElementsT
|
||||
inferred_target_whereclause: _OnConflictIndexWhereT
|
||||
inferred_target_elements: Optional[List[Union[str, schema.Column[Any]]]]
|
||||
inferred_target_whereclause: Optional[
|
||||
Union[ColumnElement[Any], TextClause]
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -194,11 +201,20 @@ class OnConflictClause(ClauseElement):
|
||||
index_where: _OnConflictIndexWhereT = None,
|
||||
):
|
||||
if index_elements is not None:
|
||||
self.constraint_target = None
|
||||
self.inferred_target_elements = index_elements
|
||||
self.inferred_target_whereclause = index_where
|
||||
self.inferred_target_elements = [
|
||||
coercions.expect(roles.DDLConstraintColumnRole, column)
|
||||
for column in index_elements
|
||||
]
|
||||
self.inferred_target_whereclause = (
|
||||
coercions.expect(
|
||||
roles.WhereHavingRole,
|
||||
index_where,
|
||||
)
|
||||
if index_where is not None
|
||||
else None
|
||||
)
|
||||
else:
|
||||
self.constraint_target = self.inferred_target_elements = (
|
||||
self.inferred_target_elements = (
|
||||
self.inferred_target_whereclause
|
||||
) = None
|
||||
|
||||
@@ -210,6 +226,9 @@ class OnConflictDoNothing(OnConflictClause):
|
||||
class OnConflictDoUpdate(OnConflictClause):
|
||||
__visit_name__ = "on_conflict_do_update"
|
||||
|
||||
update_values_to_set: List[Tuple[Union[schema.Column[Any], str], Any]]
|
||||
update_whereclause: Optional[ColumnElement[Any]]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
index_elements: _OnConflictIndexElementsT = None,
|
||||
@@ -237,4 +256,8 @@ class OnConflictDoUpdate(OnConflictClause):
|
||||
(coercions.expect(roles.DMLColumnRole, key), value)
|
||||
for key, value in set_.items()
|
||||
]
|
||||
self.update_whereclause = where
|
||||
self.update_whereclause = (
|
||||
coercions.expect(roles.WhereHavingRole, where)
|
||||
if where is not None
|
||||
else None
|
||||
)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/sqlite/json.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/sqlite/provision.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/sqlite/pysqlcipher.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -39,7 +39,7 @@ Current dialect selection logic is:
|
||||
|
||||
e = create_engine(
|
||||
"sqlite+pysqlcipher://:password@/dbname.db",
|
||||
module=sqlcipher_compatible_driver
|
||||
module=sqlcipher_compatible_driver,
|
||||
)
|
||||
|
||||
These drivers make use of the SQLCipher engine. This system essentially
|
||||
@@ -55,12 +55,12 @@ The format of the connect string is in every way the same as that
|
||||
of the :mod:`~sqlalchemy.dialects.sqlite.pysqlite` driver, except that the
|
||||
"password" field is now accepted, which should contain a passphrase::
|
||||
|
||||
e = create_engine('sqlite+pysqlcipher://:testing@/foo.db')
|
||||
e = create_engine("sqlite+pysqlcipher://:testing@/foo.db")
|
||||
|
||||
For an absolute file path, two leading slashes should be used for the
|
||||
database name::
|
||||
|
||||
e = create_engine('sqlite+pysqlcipher://:testing@//path/to/foo.db')
|
||||
e = create_engine("sqlite+pysqlcipher://:testing@//path/to/foo.db")
|
||||
|
||||
A selection of additional encryption-related pragmas supported by SQLCipher
|
||||
as documented at https://www.zetetic.net/sqlcipher/sqlcipher-api/ can be passed
|
||||
@@ -68,7 +68,9 @@ in the query string, and will result in that PRAGMA being called for each
|
||||
new connection. Currently, ``cipher``, ``kdf_iter``
|
||||
``cipher_page_size`` and ``cipher_use_hmac`` are supported::
|
||||
|
||||
e = create_engine('sqlite+pysqlcipher://:testing@/foo.db?cipher=aes-256-cfb&kdf_iter=64000')
|
||||
e = create_engine(
|
||||
"sqlite+pysqlcipher://:testing@/foo.db?cipher=aes-256-cfb&kdf_iter=64000"
|
||||
)
|
||||
|
||||
.. warning:: Previous versions of sqlalchemy did not take into consideration
|
||||
the encryption-related pragmas passed in the url string, that were silently
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/sqlite/pysqlite.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -28,7 +28,9 @@ Connect Strings
|
||||
---------------
|
||||
|
||||
The file specification for the SQLite database is taken as the "database"
|
||||
portion of the URL. Note that the format of a SQLAlchemy url is::
|
||||
portion of the URL. Note that the format of a SQLAlchemy url is:
|
||||
|
||||
.. sourcecode:: text
|
||||
|
||||
driver://user:pass@host/database
|
||||
|
||||
@@ -37,25 +39,28 @@ the **right** of the third slash. So connecting to a relative filepath
|
||||
looks like::
|
||||
|
||||
# relative path
|
||||
e = create_engine('sqlite:///path/to/database.db')
|
||||
e = create_engine("sqlite:///path/to/database.db")
|
||||
|
||||
An absolute path, which is denoted by starting with a slash, means you
|
||||
need **four** slashes::
|
||||
|
||||
# absolute path
|
||||
e = create_engine('sqlite:////path/to/database.db')
|
||||
e = create_engine("sqlite:////path/to/database.db")
|
||||
|
||||
To use a Windows path, regular drive specifications and backslashes can be
|
||||
used. Double backslashes are probably needed::
|
||||
|
||||
# absolute path on Windows
|
||||
e = create_engine('sqlite:///C:\\path\\to\\database.db')
|
||||
e = create_engine("sqlite:///C:\\path\\to\\database.db")
|
||||
|
||||
The sqlite ``:memory:`` identifier is the default if no filepath is
|
||||
present. Specify ``sqlite://`` and nothing else::
|
||||
To use sqlite ``:memory:`` database specify it as the filename using
|
||||
``sqlite:///:memory:``. It's also the default if no filepath is
|
||||
present, specifying only ``sqlite://`` and nothing else::
|
||||
|
||||
# in-memory database
|
||||
e = create_engine('sqlite://')
|
||||
# in-memory database (note three slashes)
|
||||
e = create_engine("sqlite:///:memory:")
|
||||
# also in-memory database
|
||||
e2 = create_engine("sqlite://")
|
||||
|
||||
.. _pysqlite_uri_connections:
|
||||
|
||||
@@ -95,7 +100,9 @@ Above, the pysqlite / sqlite3 DBAPI would be passed arguments as::
|
||||
|
||||
sqlite3.connect(
|
||||
"file:path/to/database?mode=ro&nolock=1",
|
||||
check_same_thread=True, timeout=10, uri=True
|
||||
check_same_thread=True,
|
||||
timeout=10,
|
||||
uri=True,
|
||||
)
|
||||
|
||||
Regarding future parameters added to either the Python or native drivers. new
|
||||
@@ -141,8 +148,11 @@ as follows::
|
||||
def regexp(a, b):
|
||||
return re.search(a, b) is not None
|
||||
|
||||
|
||||
sqlite_connection.create_function(
|
||||
"regexp", 2, regexp,
|
||||
"regexp",
|
||||
2,
|
||||
regexp,
|
||||
)
|
||||
|
||||
There is currently no support for regular expression flags as a separate
|
||||
@@ -183,10 +193,12 @@ Keeping in mind that pysqlite's parsing option is not recommended,
|
||||
nor should be necessary, for use with SQLAlchemy, usage of PARSE_DECLTYPES
|
||||
can be forced if one configures "native_datetime=True" on create_engine()::
|
||||
|
||||
engine = create_engine('sqlite://',
|
||||
connect_args={'detect_types':
|
||||
sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES},
|
||||
native_datetime=True
|
||||
engine = create_engine(
|
||||
"sqlite://",
|
||||
connect_args={
|
||||
"detect_types": sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES
|
||||
},
|
||||
native_datetime=True,
|
||||
)
|
||||
|
||||
With this flag enabled, the DATE and TIMESTAMP types (but note - not the
|
||||
@@ -241,6 +253,7 @@ Pooling may be disabled for a file based database by specifying the
|
||||
parameter::
|
||||
|
||||
from sqlalchemy import NullPool
|
||||
|
||||
engine = create_engine("sqlite:///myfile.db", poolclass=NullPool)
|
||||
|
||||
It's been observed that the :class:`.NullPool` implementation incurs an
|
||||
@@ -260,9 +273,12 @@ globally, and the ``check_same_thread`` flag can be passed to Pysqlite
|
||||
as ``False``::
|
||||
|
||||
from sqlalchemy.pool import StaticPool
|
||||
engine = create_engine('sqlite://',
|
||||
connect_args={'check_same_thread':False},
|
||||
poolclass=StaticPool)
|
||||
|
||||
engine = create_engine(
|
||||
"sqlite://",
|
||||
connect_args={"check_same_thread": False},
|
||||
poolclass=StaticPool,
|
||||
)
|
||||
|
||||
Note that using a ``:memory:`` database in multiple threads requires a recent
|
||||
version of SQLite.
|
||||
@@ -281,14 +297,14 @@ needed within multiple threads for this case::
|
||||
|
||||
# maintain the same connection per thread
|
||||
from sqlalchemy.pool import SingletonThreadPool
|
||||
engine = create_engine('sqlite:///mydb.db',
|
||||
poolclass=SingletonThreadPool)
|
||||
|
||||
engine = create_engine("sqlite:///mydb.db", poolclass=SingletonThreadPool)
|
||||
|
||||
|
||||
# maintain the same connection across all threads
|
||||
from sqlalchemy.pool import StaticPool
|
||||
engine = create_engine('sqlite:///mydb.db',
|
||||
poolclass=StaticPool)
|
||||
|
||||
engine = create_engine("sqlite:///mydb.db", poolclass=StaticPool)
|
||||
|
||||
Note that :class:`.SingletonThreadPool` should be configured for the number
|
||||
of threads that are to be used; beyond that number, connections will be
|
||||
@@ -317,13 +333,14 @@ same column, use a custom type that will check each row individually::
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy import TypeDecorator
|
||||
|
||||
|
||||
class MixedBinary(TypeDecorator):
|
||||
impl = String
|
||||
cache_ok = True
|
||||
|
||||
def process_result_value(self, value, dialect):
|
||||
if isinstance(value, str):
|
||||
value = bytes(value, 'utf-8')
|
||||
value = bytes(value, "utf-8")
|
||||
elif value is not None:
|
||||
value = bytes(value)
|
||||
|
||||
@@ -364,12 +381,14 @@ ourselves. This is achieved using two event listeners::
|
||||
|
||||
engine = create_engine("sqlite:///myfile.db")
|
||||
|
||||
|
||||
@event.listens_for(engine, "connect")
|
||||
def do_connect(dbapi_connection, connection_record):
|
||||
# disable pysqlite's emitting of the BEGIN statement entirely.
|
||||
# also stops it from emitting COMMIT before any DDL.
|
||||
dbapi_connection.isolation_level = None
|
||||
|
||||
|
||||
@event.listens_for(engine, "begin")
|
||||
def do_begin(conn):
|
||||
# emit our own BEGIN
|
||||
@@ -439,7 +458,6 @@ connection when it is created. That is accomplished with an event listener::
|
||||
with engine.connect() as conn:
|
||||
print(conn.scalar(text("SELECT UDF()")))
|
||||
|
||||
|
||||
""" # noqa
|
||||
|
||||
import math
|
||||
|
Reference in New Issue
Block a user