添加注册登录功能
This commit is contained in:
@@ -136,99 +136,199 @@ name to be ``INTEGER`` when compiled against SQLite::
|
||||
|
||||
`Datatypes In SQLite Version 3 <https://sqlite.org/datatype3.html>`_
|
||||
|
||||
.. _sqlite_concurrency:
|
||||
.. _sqlite_transactions:
|
||||
|
||||
Database Locking Behavior / Concurrency
|
||||
---------------------------------------
|
||||
Transactions with SQLite and the sqlite3 driver
|
||||
-----------------------------------------------
|
||||
|
||||
SQLite is not designed for a high level of write concurrency. The database
|
||||
itself, being a file, is locked completely during write operations within
|
||||
transactions, meaning exactly one "connection" (in reality a file handle)
|
||||
has exclusive access to the database during this period - all other
|
||||
"connections" will be blocked during this time.
|
||||
As a file-based database, SQLite's approach to transactions differs from
|
||||
traditional databases in many ways. Additionally, the ``sqlite3`` driver
|
||||
standard with Python (as well as the async version ``aiosqlite`` which builds
|
||||
on top of it) has several quirks, workarounds, and API features in the
|
||||
area of transaction control, all of which generally need to be addressed when
|
||||
constructing a SQLAlchemy application that uses SQLite.
|
||||
|
||||
The Python DBAPI specification also calls for a connection model that is
|
||||
always in a transaction; there is no ``connection.begin()`` method,
|
||||
only ``connection.commit()`` and ``connection.rollback()``, upon which a
|
||||
new transaction is to be begun immediately. This may seem to imply
|
||||
that the SQLite driver would in theory allow only a single filehandle on a
|
||||
particular database file at any time; however, there are several
|
||||
factors both within SQLite itself as well as within the pysqlite driver
|
||||
which loosen this restriction significantly.
|
||||
Legacy Transaction Mode with the sqlite3 driver
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
However, no matter what locking modes are used, SQLite will still always
|
||||
lock the database file once a transaction is started and DML (e.g. INSERT,
|
||||
UPDATE, DELETE) has at least been emitted, and this will block
|
||||
other transactions at least at the point that they also attempt to emit DML.
|
||||
By default, the length of time on this block is very short before it times out
|
||||
with an error.
|
||||
The most important aspect of transaction handling with the sqlite3 driver is
|
||||
that it defaults (which will continue through Python 3.15 before being
|
||||
removed in Python 3.16) to legacy transactional behavior which does
|
||||
not strictly follow :pep:`249`. The way in which the driver diverges from the
|
||||
PEP is that it does not "begin" a transaction automatically as dictated by
|
||||
:pep:`249` except in the case of DML statements, e.g. INSERT, UPDATE, and
|
||||
DELETE. Normally, :pep:`249` dictates that a BEGIN must be emitted upon
|
||||
the first SQL statement of any kind, so that all subsequent operations will
|
||||
be established within a transaction until ``connection.commit()`` has been
|
||||
called. The ``sqlite3`` driver, in an effort to be easier to use in
|
||||
highly concurrent environments, skips this step for DQL (e.g. SELECT) statements,
|
||||
and also skips it for DDL (e.g. CREATE TABLE etc.) statements for more legacy
|
||||
reasons. Statements such as SAVEPOINT are also skipped.
|
||||
|
||||
This behavior becomes more critical when used in conjunction with the
|
||||
SQLAlchemy ORM. SQLAlchemy's :class:`.Session` object by default runs
|
||||
within a transaction, and with its autoflush model, may emit DML preceding
|
||||
any SELECT statement. This may lead to a SQLite database that locks
|
||||
more quickly than is expected. The locking mode of SQLite and the pysqlite
|
||||
driver can be manipulated to some degree, however it should be noted that
|
||||
achieving a high degree of write-concurrency with SQLite is a losing battle.
|
||||
In modern versions of the ``sqlite3`` driver as of Python 3.12, this legacy
|
||||
mode of operation is referred to as
|
||||
`"legacy transaction control" <https://docs.python.org/3/library/sqlite3.html#sqlite3-transaction-control-isolation-level>`_, and is in
|
||||
effect by default due to the ``Connection.autocommit`` parameter being set to
|
||||
the constant ``sqlite3.LEGACY_TRANSACTION_CONTROL``. Prior to Python 3.12,
|
||||
the ``Connection.autocommit`` attribute did not exist.
|
||||
|
||||
For more information on SQLite's lack of write concurrency by design, please
|
||||
see
|
||||
`Situations Where Another RDBMS May Work Better - High Concurrency
|
||||
<https://www.sqlite.org/whentouse.html>`_ near the bottom of the page.
|
||||
The implications of legacy transaction mode include:
|
||||
|
||||
The following subsections introduce areas that are impacted by SQLite's
|
||||
file-based architecture and additionally will usually require workarounds to
|
||||
work when using the pysqlite driver.
|
||||
* **Incorrect support for transactional DDL** - statements like CREATE TABLE, ALTER TABLE,
|
||||
CREATE INDEX etc. will not automatically BEGIN a transaction if one were not
|
||||
started already, leading to the changes by each statement being
|
||||
"autocommitted" immediately unless BEGIN were otherwise emitted first. Very
|
||||
old (pre Python 3.6) versions of SQLite would also force a COMMIT for these
|
||||
operations even if a transaction were present, however this is no longer the
|
||||
case.
|
||||
* **SERIALIZABLE behavior not fully functional** - SQLite's transaction isolation
|
||||
behavior is normally consistent with SERIALIZABLE isolation, as it is a file-
|
||||
based system that locks the database file entirely for write operations,
|
||||
preventing COMMIT until all reader transactions (and associated file locks)
|
||||
have completed. However, sqlite3's legacy transaction mode fails to emit BEGIN for SELECT
|
||||
statements, which causes these SELECT statements to no longer be "repeatable",
|
||||
failing one of the consistency guarantees of SERIALIZABLE.
|
||||
* **Incorrect behavior for SAVEPOINT** - as the SAVEPOINT statement does not
|
||||
imply a BEGIN, a new SAVEPOINT emitted before a BEGIN will function on its
|
||||
own but fails to participate in the enclosing transaction, meaning a ROLLBACK
|
||||
of the transaction will not rollback elements that were part of a released
|
||||
savepoint.
|
||||
|
||||
Legacy transaction mode first existed in order to faciliate working around
|
||||
SQLite's file locks. Because SQLite relies upon whole-file locks, it is easy to
|
||||
get "database is locked" errors, particularly when newer features like "write
|
||||
ahead logging" are disabled. This is a key reason why ``sqlite3``'s legacy
|
||||
transaction mode is still the default mode of operation; disabling it will
|
||||
produce behavior that is more susceptible to locked database errors. However
|
||||
note that **legacy transaction mode will no longer be the default** in a future
|
||||
Python version (3.16 as of this writing).
|
||||
|
||||
.. _sqlite_enabling_transactions:
|
||||
|
||||
Enabling Non-Legacy SQLite Transactional Modes with the sqlite3 or aiosqlite driver
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Current SQLAlchemy support allows either for setting the
|
||||
``.Connection.autocommit`` attribute, most directly by using a
|
||||
:func:`._sa.create_engine` parameter, or if on an older version of Python where
|
||||
the attribute is not available, using event hooks to control the behavior of
|
||||
BEGIN.
|
||||
|
||||
* **Enabling modern sqlite3 transaction control via the autocommit connect parameter** (Python 3.12 and above)
|
||||
|
||||
To use SQLite in the mode described at `Transaction control via the autocommit attribute <https://docs.python.org/3/library/sqlite3.html#transaction-control-via-the-autocommit-attribute>`_,
|
||||
the most straightforward approach is to set the attribute to its recommended value
|
||||
of ``False`` at the connect level using :paramref:`_sa.create_engine.connect_args``::
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
engine = create_engine(
|
||||
"sqlite:///myfile.db", connect_args={"autocommit": False}
|
||||
)
|
||||
|
||||
This parameter is also passed through when using the aiosqlite driver::
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
|
||||
engine = create_async_engine(
|
||||
"sqlite+aiosqlite:///myfile.db", connect_args={"autocommit": False}
|
||||
)
|
||||
|
||||
The parameter can also be set at the attribute level using the :meth:`.PoolEvents.connect`
|
||||
event hook, however this will only work for sqlite3, as aiosqlite does not yet expose this
|
||||
attribute on its ``Connection`` object::
|
||||
|
||||
from sqlalchemy import create_engine, event
|
||||
|
||||
engine = create_engine("sqlite:///myfile.db")
|
||||
|
||||
|
||||
@event.listens_for(engine, "connect")
|
||||
def do_connect(dbapi_connection, connection_record):
|
||||
# enable autocommit=False mode
|
||||
dbapi_connection.autocommit = False
|
||||
|
||||
* **Using SQLAlchemy to emit BEGIN in lieu of SQLite's transaction control** (all Python versions, sqlite3 and aiosqlite)
|
||||
|
||||
For older versions of ``sqlite3`` or for cross-compatiblity with older and
|
||||
newer versions, SQLAlchemy can also take over the job of transaction control.
|
||||
This is achieved by using the :meth:`.ConnectionEvents.begin` hook
|
||||
to emit the "BEGIN" command directly, while also disabling SQLite's control
|
||||
of this command using the :meth:`.PoolEvents.connect` event hook to set the
|
||||
``Connection.isolation_level`` attribute to ``None``::
|
||||
|
||||
|
||||
from sqlalchemy import create_engine, event
|
||||
|
||||
engine = create_engine("sqlite:///myfile.db")
|
||||
|
||||
|
||||
@event.listens_for(engine, "connect")
|
||||
def do_connect(dbapi_connection, connection_record):
|
||||
# disable sqlite3's emitting of the BEGIN statement entirely.
|
||||
dbapi_connection.isolation_level = None
|
||||
|
||||
|
||||
@event.listens_for(engine, "begin")
|
||||
def do_begin(conn):
|
||||
# emit our own BEGIN. sqlite3 still emits COMMIT/ROLLBACK correctly
|
||||
conn.exec_driver_sql("BEGIN")
|
||||
|
||||
When using the asyncio variant ``aiosqlite``, refer to ``engine.sync_engine``
|
||||
as in the example below::
|
||||
|
||||
from sqlalchemy import create_engine, event
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
|
||||
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.
|
||||
dbapi_connection.isolation_level = None
|
||||
|
||||
|
||||
@event.listens_for(engine.sync_engine, "begin")
|
||||
def do_begin(conn):
|
||||
# emit our own BEGIN. aiosqlite still emits COMMIT/ROLLBACK correctly
|
||||
conn.exec_driver_sql("BEGIN")
|
||||
|
||||
.. _sqlite_isolation_level:
|
||||
|
||||
Transaction Isolation Level / Autocommit
|
||||
----------------------------------------
|
||||
Using SQLAlchemy's Driver Level AUTOCOMMIT Feature with SQLite
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
SQLite supports "transaction isolation" in a non-standard way, along two
|
||||
axes. One is that of the
|
||||
`PRAGMA read_uncommitted <https://www.sqlite.org/pragma.html#pragma_read_uncommitted>`_
|
||||
instruction. This setting can essentially switch SQLite between its
|
||||
default mode of ``SERIALIZABLE`` isolation, and a "dirty read" isolation
|
||||
mode normally referred to as ``READ UNCOMMITTED``.
|
||||
SQLAlchemy has a comprehensive database isolation feature with optional
|
||||
autocommit support that is introduced in the section :ref:`dbapi_autocommit`.
|
||||
|
||||
SQLAlchemy ties into this PRAGMA statement using the
|
||||
:paramref:`_sa.create_engine.isolation_level` parameter of
|
||||
:func:`_sa.create_engine`.
|
||||
Valid values for this parameter when used with SQLite are ``"SERIALIZABLE"``
|
||||
and ``"READ UNCOMMITTED"`` corresponding to a value of 0 and 1, respectively.
|
||||
SQLite defaults to ``SERIALIZABLE``, however its behavior is impacted by
|
||||
the pysqlite driver's default behavior.
|
||||
For the ``sqlite3`` and ``aiosqlite`` drivers, SQLAlchemy only includes
|
||||
built-in support for "AUTOCOMMIT". Note that this mode is currently incompatible
|
||||
with the non-legacy isolation mode hooks documented in the previous
|
||||
section at :ref:`sqlite_enabling_transactions`.
|
||||
|
||||
When using the pysqlite driver, the ``"AUTOCOMMIT"`` isolation level is also
|
||||
available, which will alter the pysqlite connection using the ``.isolation_level``
|
||||
attribute on the DBAPI connection and set it to None for the duration
|
||||
of the setting.
|
||||
To use the ``sqlite3`` driver with SQLAlchemy driver-level autocommit,
|
||||
create an engine setting the :paramref:`_sa.create_engine.isolation_level`
|
||||
parameter to "AUTOCOMMIT"::
|
||||
|
||||
.. versionadded:: 1.3.16 added support for SQLite AUTOCOMMIT isolation level
|
||||
when using the pysqlite / sqlite3 SQLite driver.
|
||||
eng = create_engine("sqlite:///myfile.db", isolation_level="AUTOCOMMIT")
|
||||
|
||||
When using the above mode, any event hooks that set the sqlite3 ``Connection.autocommit``
|
||||
parameter away from its default of ``sqlite3.LEGACY_TRANSACTION_CONTROL``
|
||||
as well as hooks that emit ``BEGIN`` should be disabled.
|
||||
|
||||
The other axis along which SQLite's transactional locking is impacted is
|
||||
via the nature of the ``BEGIN`` statement used. The three varieties
|
||||
are "deferred", "immediate", and "exclusive", as described at
|
||||
`BEGIN TRANSACTION <https://sqlite.org/lang_transaction.html>`_. A straight
|
||||
``BEGIN`` statement uses the "deferred" mode, where the database file is
|
||||
not locked until the first read or write operation, and read access remains
|
||||
open to other transactions until the first write operation. But again,
|
||||
it is critical to note that the pysqlite driver interferes with this behavior
|
||||
by *not even emitting BEGIN* until the first write operation.
|
||||
Additional Reading for SQLite / sqlite3 transaction control
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. warning::
|
||||
Links with important information on SQLite, the sqlite3 driver,
|
||||
as well as long historical conversations on how things got to their current state:
|
||||
|
||||
SQLite's transactional scope is impacted by unresolved
|
||||
issues in the pysqlite driver, which defers BEGIN statements to a greater
|
||||
degree than is often feasible. See the section :ref:`pysqlite_serializable`
|
||||
or :ref:`aiosqlite_serializable` for techniques to work around this behavior.
|
||||
* `Isolation in SQLite <https://www.sqlite.org/isolation.html>`_ - on the SQLite website
|
||||
* `Transaction control <https://docs.python.org/3/library/sqlite3.html#transaction-control>`_ - describes the sqlite3 autocommit attribute as well
|
||||
as the legacy isolation_level attribute.
|
||||
* `sqlite3 SELECT does not BEGIN a transaction, but should according to spec <https://github.com/python/cpython/issues/54133>`_ - imported Python standard library issue on github
|
||||
* `sqlite3 module breaks transactions and potentially corrupts data <https://github.com/python/cpython/issues/54949>`_ - imported Python standard library issue on github
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`dbapi_autocommit`
|
||||
|
||||
INSERT/UPDATE/DELETE...RETURNING
|
||||
---------------------------------
|
||||
@@ -268,38 +368,6 @@ To specify an explicit ``RETURNING`` clause, use the
|
||||
|
||||
.. versionadded:: 2.0 Added support for SQLite RETURNING
|
||||
|
||||
SAVEPOINT Support
|
||||
----------------------------
|
||||
|
||||
SQLite supports SAVEPOINTs, which only function once a transaction is
|
||||
begun. SQLAlchemy's SAVEPOINT support is available using the
|
||||
:meth:`_engine.Connection.begin_nested` method at the Core level, and
|
||||
:meth:`.Session.begin_nested` at the ORM level. However, SAVEPOINTs
|
||||
won't work at all with pysqlite unless workarounds are taken.
|
||||
|
||||
.. warning::
|
||||
|
||||
SQLite's SAVEPOINT feature is impacted by unresolved
|
||||
issues in the pysqlite and aiosqlite drivers, which defer BEGIN statements
|
||||
to a greater degree than is often feasible. See the sections
|
||||
:ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable`
|
||||
for techniques to work around this behavior.
|
||||
|
||||
Transactional DDL
|
||||
----------------------------
|
||||
|
||||
The SQLite database supports transactional :term:`DDL` as well.
|
||||
In this case, the pysqlite driver is not only failing to start transactions,
|
||||
it also is ending any existing transaction when DDL is detected, so again,
|
||||
workarounds are required.
|
||||
|
||||
.. warning::
|
||||
|
||||
SQLite's transactional DDL is impacted by unresolved issues
|
||||
in the pysqlite driver, which fails to emit BEGIN and additionally
|
||||
forces a COMMIT to cancel any transaction when DDL is encountered.
|
||||
See the section :ref:`pysqlite_serializable`
|
||||
for techniques to work around this behavior.
|
||||
|
||||
.. _sqlite_foreign_keys:
|
||||
|
||||
@@ -328,10 +396,18 @@ new connections through the usage of events::
|
||||
|
||||
@event.listens_for(Engine, "connect")
|
||||
def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||
# the sqlite3 driver will not set PRAGMA foreign_keys
|
||||
# if autocommit=False; set to True temporarily
|
||||
ac = dbapi_connection.autocommit
|
||||
dbapi_connection.autocommit = True
|
||||
|
||||
cursor = dbapi_connection.cursor()
|
||||
cursor.execute("PRAGMA foreign_keys=ON")
|
||||
cursor.close()
|
||||
|
||||
# restore previous autocommit setting
|
||||
dbapi_connection.autocommit = ac
|
||||
|
||||
.. warning::
|
||||
|
||||
When SQLite foreign keys are enabled, it is **not possible**
|
||||
@@ -932,7 +1008,6 @@ from ...engine import processors
|
||||
from ...engine import reflection
|
||||
from ...engine.reflection import ReflectionDefaults
|
||||
from ...sql import coercions
|
||||
from ...sql import ColumnElement
|
||||
from ...sql import compiler
|
||||
from ...sql import elements
|
||||
from ...sql import roles
|
||||
@@ -1049,6 +1124,10 @@ class DATETIME(_DateTimeMixin, sqltypes.DateTime):
|
||||
regexp=r"(\d+)/(\d+)/(\d+) (\d+)-(\d+)-(\d+)",
|
||||
)
|
||||
|
||||
:param truncate_microseconds: when ``True`` microseconds will be truncated
|
||||
from the datetime. Can't be specified together with ``storage_format``
|
||||
or ``regexp``.
|
||||
|
||||
:param storage_format: format string which will be applied to the dict
|
||||
with keys year, month, day, hour, minute, second, and microsecond.
|
||||
|
||||
@@ -1235,6 +1314,10 @@ class TIME(_DateTimeMixin, sqltypes.Time):
|
||||
regexp=re.compile("(\d+)-(\d+)-(\d+)-(?:-(\d+))?"),
|
||||
)
|
||||
|
||||
:param truncate_microseconds: when ``True`` microseconds will be truncated
|
||||
from the time. Can't be specified together with ``storage_format``
|
||||
or ``regexp``.
|
||||
|
||||
:param storage_format: format string which will be applied to the dict
|
||||
with keys hour, minute, second, and microsecond.
|
||||
|
||||
@@ -1360,7 +1443,7 @@ class SQLiteCompiler(compiler.SQLCompiler):
|
||||
return "CURRENT_TIMESTAMP"
|
||||
|
||||
def visit_localtimestamp_func(self, func, **kw):
|
||||
return 'DATETIME(CURRENT_TIMESTAMP, "localtime")'
|
||||
return "DATETIME(CURRENT_TIMESTAMP, 'localtime')"
|
||||
|
||||
def visit_true(self, expr, **kw):
|
||||
return "1"
|
||||
@@ -1594,9 +1677,13 @@ class SQLiteDDLCompiler(compiler.DDLCompiler):
|
||||
colspec = self.preparer.format_column(column) + " " + coltype
|
||||
default = self.get_column_default_string(column)
|
||||
if default is not None:
|
||||
if isinstance(column.server_default.arg, ColumnElement):
|
||||
default = "(" + default + ")"
|
||||
colspec += " DEFAULT " + default
|
||||
|
||||
if not re.match(r"""^\s*[\'\"\(]""", default) and re.match(
|
||||
r".*\W.*", default
|
||||
):
|
||||
colspec += f" DEFAULT ({default})"
|
||||
else:
|
||||
colspec += f" DEFAULT {default}"
|
||||
|
||||
if not column.nullable:
|
||||
colspec += " NOT NULL"
|
||||
@@ -1758,12 +1845,18 @@ class SQLiteDDLCompiler(compiler.DDLCompiler):
|
||||
return text
|
||||
|
||||
def post_create_table(self, table):
|
||||
text = ""
|
||||
if table.dialect_options["sqlite"]["with_rowid"] is False:
|
||||
text += "\n WITHOUT ROWID"
|
||||
if table.dialect_options["sqlite"]["strict"] is True:
|
||||
text += "\n STRICT"
|
||||
return text
|
||||
table_options = []
|
||||
|
||||
if not table.dialect_options["sqlite"]["with_rowid"]:
|
||||
table_options.append("WITHOUT ROWID")
|
||||
|
||||
if table.dialect_options["sqlite"]["strict"]:
|
||||
table_options.append("STRICT")
|
||||
|
||||
if table_options:
|
||||
return "\n " + ",\n ".join(table_options)
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
class SQLiteTypeCompiler(compiler.GenericTypeCompiler):
|
||||
|
||||
Reference in New Issue
Block a user