SQLAlchemy 2.0.47__cp313-cp313t-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlalchemy/__init__.py +283 -0
- sqlalchemy/connectors/__init__.py +18 -0
- sqlalchemy/connectors/aioodbc.py +184 -0
- sqlalchemy/connectors/asyncio.py +429 -0
- sqlalchemy/connectors/pyodbc.py +250 -0
- sqlalchemy/cyextension/__init__.py +6 -0
- sqlalchemy/cyextension/collections.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/collections.pyx +409 -0
- sqlalchemy/cyextension/immutabledict.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/immutabledict.pxd +8 -0
- sqlalchemy/cyextension/immutabledict.pyx +133 -0
- sqlalchemy/cyextension/processors.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/processors.pyx +68 -0
- sqlalchemy/cyextension/resultproxy.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/resultproxy.pyx +102 -0
- sqlalchemy/cyextension/util.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/util.pyx +90 -0
- sqlalchemy/dialects/__init__.py +62 -0
- sqlalchemy/dialects/_typing.py +30 -0
- sqlalchemy/dialects/mssql/__init__.py +88 -0
- sqlalchemy/dialects/mssql/aioodbc.py +63 -0
- sqlalchemy/dialects/mssql/base.py +4093 -0
- sqlalchemy/dialects/mssql/information_schema.py +285 -0
- sqlalchemy/dialects/mssql/json.py +129 -0
- sqlalchemy/dialects/mssql/provision.py +185 -0
- sqlalchemy/dialects/mssql/pymssql.py +126 -0
- sqlalchemy/dialects/mssql/pyodbc.py +760 -0
- sqlalchemy/dialects/mysql/__init__.py +104 -0
- sqlalchemy/dialects/mysql/aiomysql.py +250 -0
- sqlalchemy/dialects/mysql/asyncmy.py +231 -0
- sqlalchemy/dialects/mysql/base.py +3949 -0
- sqlalchemy/dialects/mysql/cymysql.py +106 -0
- sqlalchemy/dialects/mysql/dml.py +225 -0
- sqlalchemy/dialects/mysql/enumerated.py +282 -0
- sqlalchemy/dialects/mysql/expression.py +146 -0
- sqlalchemy/dialects/mysql/json.py +91 -0
- sqlalchemy/dialects/mysql/mariadb.py +72 -0
- sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
- sqlalchemy/dialects/mysql/mysqldb.py +314 -0
- sqlalchemy/dialects/mysql/provision.py +153 -0
- sqlalchemy/dialects/mysql/pymysql.py +158 -0
- sqlalchemy/dialects/mysql/pyodbc.py +157 -0
- sqlalchemy/dialects/mysql/reflection.py +727 -0
- sqlalchemy/dialects/mysql/reserved_words.py +570 -0
- sqlalchemy/dialects/mysql/types.py +835 -0
- sqlalchemy/dialects/oracle/__init__.py +81 -0
- sqlalchemy/dialects/oracle/base.py +3802 -0
- sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
- sqlalchemy/dialects/oracle/dictionary.py +507 -0
- sqlalchemy/dialects/oracle/oracledb.py +941 -0
- sqlalchemy/dialects/oracle/provision.py +297 -0
- sqlalchemy/dialects/oracle/types.py +316 -0
- sqlalchemy/dialects/oracle/vector.py +365 -0
- sqlalchemy/dialects/postgresql/__init__.py +167 -0
- sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
- sqlalchemy/dialects/postgresql/array.py +519 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
- sqlalchemy/dialects/postgresql/base.py +5378 -0
- sqlalchemy/dialects/postgresql/dml.py +339 -0
- sqlalchemy/dialects/postgresql/ext.py +540 -0
- sqlalchemy/dialects/postgresql/hstore.py +406 -0
- sqlalchemy/dialects/postgresql/json.py +404 -0
- sqlalchemy/dialects/postgresql/named_types.py +524 -0
- sqlalchemy/dialects/postgresql/operators.py +129 -0
- sqlalchemy/dialects/postgresql/pg8000.py +669 -0
- sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
- sqlalchemy/dialects/postgresql/provision.py +183 -0
- sqlalchemy/dialects/postgresql/psycopg.py +862 -0
- sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
- sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
- sqlalchemy/dialects/postgresql/ranges.py +1031 -0
- sqlalchemy/dialects/postgresql/types.py +313 -0
- sqlalchemy/dialects/sqlite/__init__.py +57 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
- sqlalchemy/dialects/sqlite/base.py +3056 -0
- sqlalchemy/dialects/sqlite/dml.py +263 -0
- sqlalchemy/dialects/sqlite/json.py +92 -0
- sqlalchemy/dialects/sqlite/provision.py +229 -0
- sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
- sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
- sqlalchemy/engine/__init__.py +62 -0
- sqlalchemy/engine/_py_processors.py +136 -0
- sqlalchemy/engine/_py_row.py +128 -0
- sqlalchemy/engine/_py_util.py +74 -0
- sqlalchemy/engine/base.py +3390 -0
- sqlalchemy/engine/characteristics.py +155 -0
- sqlalchemy/engine/create.py +893 -0
- sqlalchemy/engine/cursor.py +2298 -0
- sqlalchemy/engine/default.py +2394 -0
- sqlalchemy/engine/events.py +965 -0
- sqlalchemy/engine/interfaces.py +3471 -0
- sqlalchemy/engine/mock.py +134 -0
- sqlalchemy/engine/processors.py +61 -0
- sqlalchemy/engine/reflection.py +2102 -0
- sqlalchemy/engine/result.py +2399 -0
- sqlalchemy/engine/row.py +400 -0
- sqlalchemy/engine/strategies.py +16 -0
- sqlalchemy/engine/url.py +924 -0
- sqlalchemy/engine/util.py +167 -0
- sqlalchemy/event/__init__.py +26 -0
- sqlalchemy/event/api.py +220 -0
- sqlalchemy/event/attr.py +676 -0
- sqlalchemy/event/base.py +472 -0
- sqlalchemy/event/legacy.py +258 -0
- sqlalchemy/event/registry.py +390 -0
- sqlalchemy/events.py +17 -0
- sqlalchemy/exc.py +832 -0
- sqlalchemy/ext/__init__.py +11 -0
- sqlalchemy/ext/associationproxy.py +2027 -0
- sqlalchemy/ext/asyncio/__init__.py +25 -0
- sqlalchemy/ext/asyncio/base.py +281 -0
- sqlalchemy/ext/asyncio/engine.py +1471 -0
- sqlalchemy/ext/asyncio/exc.py +21 -0
- sqlalchemy/ext/asyncio/result.py +965 -0
- sqlalchemy/ext/asyncio/scoping.py +1599 -0
- sqlalchemy/ext/asyncio/session.py +1947 -0
- sqlalchemy/ext/automap.py +1701 -0
- sqlalchemy/ext/baked.py +570 -0
- sqlalchemy/ext/compiler.py +600 -0
- sqlalchemy/ext/declarative/__init__.py +65 -0
- sqlalchemy/ext/declarative/extensions.py +564 -0
- sqlalchemy/ext/horizontal_shard.py +478 -0
- sqlalchemy/ext/hybrid.py +1535 -0
- sqlalchemy/ext/indexable.py +364 -0
- sqlalchemy/ext/instrumentation.py +450 -0
- sqlalchemy/ext/mutable.py +1085 -0
- sqlalchemy/ext/mypy/__init__.py +6 -0
- sqlalchemy/ext/mypy/apply.py +324 -0
- sqlalchemy/ext/mypy/decl_class.py +515 -0
- sqlalchemy/ext/mypy/infer.py +590 -0
- sqlalchemy/ext/mypy/names.py +335 -0
- sqlalchemy/ext/mypy/plugin.py +303 -0
- sqlalchemy/ext/mypy/util.py +357 -0
- sqlalchemy/ext/orderinglist.py +439 -0
- sqlalchemy/ext/serializer.py +185 -0
- sqlalchemy/future/__init__.py +16 -0
- sqlalchemy/future/engine.py +15 -0
- sqlalchemy/inspection.py +174 -0
- sqlalchemy/log.py +288 -0
- sqlalchemy/orm/__init__.py +171 -0
- sqlalchemy/orm/_orm_constructors.py +2661 -0
- sqlalchemy/orm/_typing.py +179 -0
- sqlalchemy/orm/attributes.py +2845 -0
- sqlalchemy/orm/base.py +971 -0
- sqlalchemy/orm/bulk_persistence.py +2135 -0
- sqlalchemy/orm/clsregistry.py +571 -0
- sqlalchemy/orm/collections.py +1627 -0
- sqlalchemy/orm/context.py +3334 -0
- sqlalchemy/orm/decl_api.py +2004 -0
- sqlalchemy/orm/decl_base.py +2192 -0
- sqlalchemy/orm/dependency.py +1302 -0
- sqlalchemy/orm/descriptor_props.py +1092 -0
- sqlalchemy/orm/dynamic.py +300 -0
- sqlalchemy/orm/evaluator.py +379 -0
- sqlalchemy/orm/events.py +3252 -0
- sqlalchemy/orm/exc.py +237 -0
- sqlalchemy/orm/identity.py +302 -0
- sqlalchemy/orm/instrumentation.py +754 -0
- sqlalchemy/orm/interfaces.py +1496 -0
- sqlalchemy/orm/loading.py +1686 -0
- sqlalchemy/orm/mapped_collection.py +557 -0
- sqlalchemy/orm/mapper.py +4444 -0
- sqlalchemy/orm/path_registry.py +809 -0
- sqlalchemy/orm/persistence.py +1788 -0
- sqlalchemy/orm/properties.py +935 -0
- sqlalchemy/orm/query.py +3459 -0
- sqlalchemy/orm/relationships.py +3508 -0
- sqlalchemy/orm/scoping.py +2148 -0
- sqlalchemy/orm/session.py +5280 -0
- sqlalchemy/orm/state.py +1168 -0
- sqlalchemy/orm/state_changes.py +196 -0
- sqlalchemy/orm/strategies.py +3470 -0
- sqlalchemy/orm/strategy_options.py +2568 -0
- sqlalchemy/orm/sync.py +164 -0
- sqlalchemy/orm/unitofwork.py +796 -0
- sqlalchemy/orm/util.py +2403 -0
- sqlalchemy/orm/writeonly.py +674 -0
- sqlalchemy/pool/__init__.py +44 -0
- sqlalchemy/pool/base.py +1524 -0
- sqlalchemy/pool/events.py +375 -0
- sqlalchemy/pool/impl.py +588 -0
- sqlalchemy/py.typed +0 -0
- sqlalchemy/schema.py +69 -0
- sqlalchemy/sql/__init__.py +145 -0
- sqlalchemy/sql/_dml_constructors.py +132 -0
- sqlalchemy/sql/_elements_constructors.py +1872 -0
- sqlalchemy/sql/_orm_types.py +20 -0
- sqlalchemy/sql/_py_util.py +75 -0
- sqlalchemy/sql/_selectable_constructors.py +763 -0
- sqlalchemy/sql/_typing.py +482 -0
- sqlalchemy/sql/annotation.py +587 -0
- sqlalchemy/sql/base.py +2293 -0
- sqlalchemy/sql/cache_key.py +1057 -0
- sqlalchemy/sql/coercions.py +1404 -0
- sqlalchemy/sql/compiler.py +8081 -0
- sqlalchemy/sql/crud.py +1752 -0
- sqlalchemy/sql/ddl.py +1444 -0
- sqlalchemy/sql/default_comparator.py +551 -0
- sqlalchemy/sql/dml.py +1850 -0
- sqlalchemy/sql/elements.py +5589 -0
- sqlalchemy/sql/events.py +458 -0
- sqlalchemy/sql/expression.py +159 -0
- sqlalchemy/sql/functions.py +2158 -0
- sqlalchemy/sql/lambdas.py +1442 -0
- sqlalchemy/sql/naming.py +209 -0
- sqlalchemy/sql/operators.py +2623 -0
- sqlalchemy/sql/roles.py +323 -0
- sqlalchemy/sql/schema.py +6222 -0
- sqlalchemy/sql/selectable.py +7265 -0
- sqlalchemy/sql/sqltypes.py +3930 -0
- sqlalchemy/sql/traversals.py +1024 -0
- sqlalchemy/sql/type_api.py +2368 -0
- sqlalchemy/sql/util.py +1485 -0
- sqlalchemy/sql/visitors.py +1164 -0
- sqlalchemy/testing/__init__.py +96 -0
- sqlalchemy/testing/assertions.py +994 -0
- sqlalchemy/testing/assertsql.py +520 -0
- sqlalchemy/testing/asyncio.py +135 -0
- sqlalchemy/testing/config.py +434 -0
- sqlalchemy/testing/engines.py +483 -0
- sqlalchemy/testing/entities.py +117 -0
- sqlalchemy/testing/exclusions.py +476 -0
- sqlalchemy/testing/fixtures/__init__.py +28 -0
- sqlalchemy/testing/fixtures/base.py +384 -0
- sqlalchemy/testing/fixtures/mypy.py +332 -0
- sqlalchemy/testing/fixtures/orm.py +227 -0
- sqlalchemy/testing/fixtures/sql.py +482 -0
- sqlalchemy/testing/pickleable.py +155 -0
- sqlalchemy/testing/plugin/__init__.py +6 -0
- sqlalchemy/testing/plugin/bootstrap.py +51 -0
- sqlalchemy/testing/plugin/plugin_base.py +828 -0
- sqlalchemy/testing/plugin/pytestplugin.py +892 -0
- sqlalchemy/testing/profiling.py +329 -0
- sqlalchemy/testing/provision.py +603 -0
- sqlalchemy/testing/requirements.py +1945 -0
- sqlalchemy/testing/schema.py +198 -0
- sqlalchemy/testing/suite/__init__.py +19 -0
- sqlalchemy/testing/suite/test_cte.py +237 -0
- sqlalchemy/testing/suite/test_ddl.py +389 -0
- sqlalchemy/testing/suite/test_deprecations.py +153 -0
- sqlalchemy/testing/suite/test_dialect.py +776 -0
- sqlalchemy/testing/suite/test_insert.py +630 -0
- sqlalchemy/testing/suite/test_reflection.py +3557 -0
- sqlalchemy/testing/suite/test_results.py +504 -0
- sqlalchemy/testing/suite/test_rowcount.py +258 -0
- sqlalchemy/testing/suite/test_select.py +2010 -0
- sqlalchemy/testing/suite/test_sequence.py +317 -0
- sqlalchemy/testing/suite/test_types.py +2147 -0
- sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
- sqlalchemy/testing/suite/test_update_delete.py +139 -0
- sqlalchemy/testing/util.py +535 -0
- sqlalchemy/testing/warnings.py +52 -0
- sqlalchemy/types.py +74 -0
- sqlalchemy/util/__init__.py +162 -0
- sqlalchemy/util/_collections.py +712 -0
- sqlalchemy/util/_concurrency_py3k.py +288 -0
- sqlalchemy/util/_has_cy.py +40 -0
- sqlalchemy/util/_py_collections.py +541 -0
- sqlalchemy/util/compat.py +421 -0
- sqlalchemy/util/concurrency.py +110 -0
- sqlalchemy/util/deprecations.py +401 -0
- sqlalchemy/util/langhelpers.py +2203 -0
- sqlalchemy/util/preloaded.py +150 -0
- sqlalchemy/util/queue.py +322 -0
- sqlalchemy/util/tool_support.py +201 -0
- sqlalchemy/util/topological.py +120 -0
- sqlalchemy/util/typing.py +734 -0
- sqlalchemy-2.0.47.dist-info/METADATA +243 -0
- sqlalchemy-2.0.47.dist-info/RECORD +274 -0
- sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
- sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
- sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# dialects/sqlite/dml.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
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
from typing import List
|
|
11
|
+
from typing import Optional
|
|
12
|
+
from typing import Tuple
|
|
13
|
+
from typing import Union
|
|
14
|
+
|
|
15
|
+
from .._typing import _OnConflictIndexElementsT
|
|
16
|
+
from .._typing import _OnConflictIndexWhereT
|
|
17
|
+
from .._typing import _OnConflictSetT
|
|
18
|
+
from .._typing import _OnConflictWhereT
|
|
19
|
+
from ... import util
|
|
20
|
+
from ...sql import coercions
|
|
21
|
+
from ...sql import roles
|
|
22
|
+
from ...sql import schema
|
|
23
|
+
from ...sql._typing import _DMLTableArgument
|
|
24
|
+
from ...sql.base import _exclusive_against
|
|
25
|
+
from ...sql.base import _generative
|
|
26
|
+
from ...sql.base import ColumnCollection
|
|
27
|
+
from ...sql.base import ReadOnlyColumnCollection
|
|
28
|
+
from ...sql.dml import Insert as StandardInsert
|
|
29
|
+
from ...sql.elements import ClauseElement
|
|
30
|
+
from ...sql.elements import ColumnElement
|
|
31
|
+
from ...sql.elements import KeyedColumnElement
|
|
32
|
+
from ...sql.elements import TextClause
|
|
33
|
+
from ...sql.expression import alias
|
|
34
|
+
from ...util.typing import Self
|
|
35
|
+
|
|
36
|
+
__all__ = ("Insert", "insert")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def insert(table: _DMLTableArgument) -> Insert:
|
|
40
|
+
"""Construct a sqlite-specific variant :class:`_sqlite.Insert`
|
|
41
|
+
construct.
|
|
42
|
+
|
|
43
|
+
.. container:: inherited_member
|
|
44
|
+
|
|
45
|
+
The :func:`sqlalchemy.dialects.sqlite.insert` function creates
|
|
46
|
+
a :class:`sqlalchemy.dialects.sqlite.Insert`. This class is based
|
|
47
|
+
on the dialect-agnostic :class:`_sql.Insert` construct which may
|
|
48
|
+
be constructed using the :func:`_sql.insert` function in
|
|
49
|
+
SQLAlchemy Core.
|
|
50
|
+
|
|
51
|
+
The :class:`_sqlite.Insert` construct includes additional methods
|
|
52
|
+
:meth:`_sqlite.Insert.on_conflict_do_update`,
|
|
53
|
+
:meth:`_sqlite.Insert.on_conflict_do_nothing`.
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
return Insert(table)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Insert(StandardInsert):
|
|
60
|
+
"""SQLite-specific implementation of INSERT.
|
|
61
|
+
|
|
62
|
+
Adds methods for SQLite-specific syntaxes such as ON CONFLICT.
|
|
63
|
+
|
|
64
|
+
The :class:`_sqlite.Insert` object is created using the
|
|
65
|
+
:func:`sqlalchemy.dialects.sqlite.insert` function.
|
|
66
|
+
|
|
67
|
+
.. versionadded:: 1.4
|
|
68
|
+
|
|
69
|
+
.. seealso::
|
|
70
|
+
|
|
71
|
+
:ref:`sqlite_on_conflict_insert`
|
|
72
|
+
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
stringify_dialect = "sqlite"
|
|
76
|
+
inherit_cache = False
|
|
77
|
+
|
|
78
|
+
@util.memoized_property
|
|
79
|
+
def excluded(
|
|
80
|
+
self,
|
|
81
|
+
) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
|
|
82
|
+
"""Provide the ``excluded`` namespace for an ON CONFLICT statement
|
|
83
|
+
|
|
84
|
+
SQLite's ON CONFLICT clause allows reference to the row that would
|
|
85
|
+
be inserted, known as ``excluded``. This attribute provides
|
|
86
|
+
all columns in this row to be referenceable.
|
|
87
|
+
|
|
88
|
+
.. tip:: The :attr:`_sqlite.Insert.excluded` attribute is an instance
|
|
89
|
+
of :class:`_expression.ColumnCollection`, which provides an
|
|
90
|
+
interface the same as that of the :attr:`_schema.Table.c`
|
|
91
|
+
collection described at :ref:`metadata_tables_and_columns`.
|
|
92
|
+
With this collection, ordinary names are accessible like attributes
|
|
93
|
+
(e.g. ``stmt.excluded.some_column``), but special names and
|
|
94
|
+
dictionary method names should be accessed using indexed access,
|
|
95
|
+
such as ``stmt.excluded["column name"]`` or
|
|
96
|
+
``stmt.excluded["values"]``. See the docstring for
|
|
97
|
+
:class:`_expression.ColumnCollection` for further examples.
|
|
98
|
+
|
|
99
|
+
"""
|
|
100
|
+
return alias(self.table, name="excluded").columns
|
|
101
|
+
|
|
102
|
+
_on_conflict_exclusive = _exclusive_against(
|
|
103
|
+
"_post_values_clause",
|
|
104
|
+
msgs={
|
|
105
|
+
"_post_values_clause": "This Insert construct already has "
|
|
106
|
+
"an ON CONFLICT clause established"
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@_generative
|
|
111
|
+
@_on_conflict_exclusive
|
|
112
|
+
def on_conflict_do_update(
|
|
113
|
+
self,
|
|
114
|
+
index_elements: _OnConflictIndexElementsT = None,
|
|
115
|
+
index_where: _OnConflictIndexWhereT = None,
|
|
116
|
+
set_: _OnConflictSetT = None,
|
|
117
|
+
where: _OnConflictWhereT = None,
|
|
118
|
+
) -> Self:
|
|
119
|
+
r"""
|
|
120
|
+
Specifies a DO UPDATE SET action for ON CONFLICT clause.
|
|
121
|
+
|
|
122
|
+
:param index_elements:
|
|
123
|
+
A sequence consisting of string column names, :class:`_schema.Column`
|
|
124
|
+
objects, or other column expression objects that will be used
|
|
125
|
+
to infer a target index or unique constraint.
|
|
126
|
+
|
|
127
|
+
:param index_where:
|
|
128
|
+
Additional WHERE criterion that can be used to infer a
|
|
129
|
+
conditional target index.
|
|
130
|
+
|
|
131
|
+
:param set\_:
|
|
132
|
+
A dictionary or other mapping object
|
|
133
|
+
where the keys are either names of columns in the target table,
|
|
134
|
+
or :class:`_schema.Column` objects or other ORM-mapped columns
|
|
135
|
+
matching that of the target table, and expressions or literals
|
|
136
|
+
as values, specifying the ``SET`` actions to take.
|
|
137
|
+
|
|
138
|
+
.. versionadded:: 1.4 The
|
|
139
|
+
:paramref:`_sqlite.Insert.on_conflict_do_update.set_`
|
|
140
|
+
parameter supports :class:`_schema.Column` objects from the target
|
|
141
|
+
:class:`_schema.Table` as keys.
|
|
142
|
+
|
|
143
|
+
.. warning:: This dictionary does **not** take into account
|
|
144
|
+
Python-specified default UPDATE values or generation functions,
|
|
145
|
+
e.g. those specified using :paramref:`_schema.Column.onupdate`.
|
|
146
|
+
These values will not be exercised for an ON CONFLICT style of
|
|
147
|
+
UPDATE, unless they are manually specified in the
|
|
148
|
+
:paramref:`.Insert.on_conflict_do_update.set_` dictionary.
|
|
149
|
+
|
|
150
|
+
:param where:
|
|
151
|
+
Optional argument. An expression object representing a ``WHERE``
|
|
152
|
+
clause that restricts the rows affected by ``DO UPDATE SET``. Rows not
|
|
153
|
+
meeting the ``WHERE`` condition will not be updated (effectively a
|
|
154
|
+
``DO NOTHING`` for those rows).
|
|
155
|
+
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
self._post_values_clause = OnConflictDoUpdate(
|
|
159
|
+
index_elements, index_where, set_, where
|
|
160
|
+
)
|
|
161
|
+
return self
|
|
162
|
+
|
|
163
|
+
@_generative
|
|
164
|
+
@_on_conflict_exclusive
|
|
165
|
+
def on_conflict_do_nothing(
|
|
166
|
+
self,
|
|
167
|
+
index_elements: _OnConflictIndexElementsT = None,
|
|
168
|
+
index_where: _OnConflictIndexWhereT = None,
|
|
169
|
+
) -> Self:
|
|
170
|
+
"""
|
|
171
|
+
Specifies a DO NOTHING action for ON CONFLICT clause.
|
|
172
|
+
|
|
173
|
+
:param index_elements:
|
|
174
|
+
A sequence consisting of string column names, :class:`_schema.Column`
|
|
175
|
+
objects, or other column expression objects that will be used
|
|
176
|
+
to infer a target index or unique constraint.
|
|
177
|
+
|
|
178
|
+
:param index_where:
|
|
179
|
+
Additional WHERE criterion that can be used to infer a
|
|
180
|
+
conditional target index.
|
|
181
|
+
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
self._post_values_clause = OnConflictDoNothing(
|
|
185
|
+
index_elements, index_where
|
|
186
|
+
)
|
|
187
|
+
return self
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class OnConflictClause(ClauseElement):
|
|
191
|
+
stringify_dialect = "sqlite"
|
|
192
|
+
|
|
193
|
+
inferred_target_elements: Optional[List[Union[str, schema.Column[Any]]]]
|
|
194
|
+
inferred_target_whereclause: Optional[
|
|
195
|
+
Union[ColumnElement[Any], TextClause]
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
def __init__(
|
|
199
|
+
self,
|
|
200
|
+
index_elements: _OnConflictIndexElementsT = None,
|
|
201
|
+
index_where: _OnConflictIndexWhereT = None,
|
|
202
|
+
):
|
|
203
|
+
if index_elements is not None:
|
|
204
|
+
self.inferred_target_elements = [
|
|
205
|
+
coercions.expect(roles.DDLConstraintColumnRole, column)
|
|
206
|
+
for column in index_elements
|
|
207
|
+
]
|
|
208
|
+
self.inferred_target_whereclause = (
|
|
209
|
+
coercions.expect(
|
|
210
|
+
roles.WhereHavingRole,
|
|
211
|
+
index_where,
|
|
212
|
+
)
|
|
213
|
+
if index_where is not None
|
|
214
|
+
else None
|
|
215
|
+
)
|
|
216
|
+
else:
|
|
217
|
+
self.inferred_target_elements = (
|
|
218
|
+
self.inferred_target_whereclause
|
|
219
|
+
) = None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class OnConflictDoNothing(OnConflictClause):
|
|
223
|
+
__visit_name__ = "on_conflict_do_nothing"
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class OnConflictDoUpdate(OnConflictClause):
|
|
227
|
+
__visit_name__ = "on_conflict_do_update"
|
|
228
|
+
|
|
229
|
+
update_values_to_set: List[Tuple[Union[schema.Column[Any], str], Any]]
|
|
230
|
+
update_whereclause: Optional[ColumnElement[Any]]
|
|
231
|
+
|
|
232
|
+
def __init__(
|
|
233
|
+
self,
|
|
234
|
+
index_elements: _OnConflictIndexElementsT = None,
|
|
235
|
+
index_where: _OnConflictIndexWhereT = None,
|
|
236
|
+
set_: _OnConflictSetT = None,
|
|
237
|
+
where: _OnConflictWhereT = None,
|
|
238
|
+
):
|
|
239
|
+
super().__init__(
|
|
240
|
+
index_elements=index_elements,
|
|
241
|
+
index_where=index_where,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
if isinstance(set_, dict):
|
|
245
|
+
if not set_:
|
|
246
|
+
raise ValueError("set parameter dictionary must not be empty")
|
|
247
|
+
elif isinstance(set_, ColumnCollection):
|
|
248
|
+
set_ = dict(set_)
|
|
249
|
+
else:
|
|
250
|
+
raise ValueError(
|
|
251
|
+
"set parameter must be a non-empty dictionary "
|
|
252
|
+
"or a ColumnCollection such as the `.c.` collection "
|
|
253
|
+
"of a Table object"
|
|
254
|
+
)
|
|
255
|
+
self.update_values_to_set = [
|
|
256
|
+
(coercions.expect(roles.DMLColumnRole, key), value)
|
|
257
|
+
for key, value in set_.items()
|
|
258
|
+
]
|
|
259
|
+
self.update_whereclause = (
|
|
260
|
+
coercions.expect(roles.WhereHavingRole, where)
|
|
261
|
+
if where is not None
|
|
262
|
+
else None
|
|
263
|
+
)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# dialects/sqlite/json.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
|
+
# mypy: ignore-errors
|
|
8
|
+
|
|
9
|
+
from ... import types as sqltypes
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class JSON(sqltypes.JSON):
|
|
13
|
+
"""SQLite JSON type.
|
|
14
|
+
|
|
15
|
+
SQLite supports JSON as of version 3.9 through its JSON1_ extension. Note
|
|
16
|
+
that JSON1_ is a
|
|
17
|
+
`loadable extension <https://www.sqlite.org/loadext.html>`_ and as such
|
|
18
|
+
may not be available, or may require run-time loading.
|
|
19
|
+
|
|
20
|
+
:class:`_sqlite.JSON` is used automatically whenever the base
|
|
21
|
+
:class:`_types.JSON` datatype is used against a SQLite backend.
|
|
22
|
+
|
|
23
|
+
.. seealso::
|
|
24
|
+
|
|
25
|
+
:class:`_types.JSON` - main documentation for the generic
|
|
26
|
+
cross-platform JSON datatype.
|
|
27
|
+
|
|
28
|
+
The :class:`_sqlite.JSON` type supports persistence of JSON values
|
|
29
|
+
as well as the core index operations provided by :class:`_types.JSON`
|
|
30
|
+
datatype, by adapting the operations to render the ``JSON_EXTRACT``
|
|
31
|
+
function wrapped in the ``JSON_QUOTE`` function at the database level.
|
|
32
|
+
Extracted values are quoted in order to ensure that the results are
|
|
33
|
+
always JSON string values.
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
.. versionadded:: 1.3
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
.. _JSON1: https://www.sqlite.org/json1.html
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Note: these objects currently match exactly those of MySQL, however since
|
|
45
|
+
# these are not generalizable to all JSON implementations, remain separately
|
|
46
|
+
# implemented for each dialect.
|
|
47
|
+
class _FormatTypeMixin:
|
|
48
|
+
def _format_value(self, value):
|
|
49
|
+
raise NotImplementedError()
|
|
50
|
+
|
|
51
|
+
def bind_processor(self, dialect):
|
|
52
|
+
super_proc = self.string_bind_processor(dialect)
|
|
53
|
+
|
|
54
|
+
def process(value):
|
|
55
|
+
value = self._format_value(value)
|
|
56
|
+
if super_proc:
|
|
57
|
+
value = super_proc(value)
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
return process
|
|
61
|
+
|
|
62
|
+
def literal_processor(self, dialect):
|
|
63
|
+
super_proc = self.string_literal_processor(dialect)
|
|
64
|
+
|
|
65
|
+
def process(value):
|
|
66
|
+
value = self._format_value(value)
|
|
67
|
+
if super_proc:
|
|
68
|
+
value = super_proc(value)
|
|
69
|
+
return value
|
|
70
|
+
|
|
71
|
+
return process
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class JSONIndexType(_FormatTypeMixin, sqltypes.JSON.JSONIndexType):
|
|
75
|
+
def _format_value(self, value):
|
|
76
|
+
if isinstance(value, int):
|
|
77
|
+
value = "$[%s]" % value
|
|
78
|
+
else:
|
|
79
|
+
value = '$."%s"' % value
|
|
80
|
+
return value
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class JSONPathType(_FormatTypeMixin, sqltypes.JSON.JSONPathType):
|
|
84
|
+
def _format_value(self, value):
|
|
85
|
+
return "$%s" % (
|
|
86
|
+
"".join(
|
|
87
|
+
[
|
|
88
|
+
"[%s]" % elem if isinstance(elem, int) else '."%s"' % elem
|
|
89
|
+
for elem in value
|
|
90
|
+
]
|
|
91
|
+
)
|
|
92
|
+
)
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# dialects/sqlite/provision.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
|
+
# mypy: ignore-errors
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
from ... import event
|
|
13
|
+
from ... import exc
|
|
14
|
+
from ...engine import url as sa_url
|
|
15
|
+
from ...testing import config
|
|
16
|
+
from ...testing.provision import create_db
|
|
17
|
+
from ...testing.provision import drop_db
|
|
18
|
+
from ...testing.provision import follower_url_from_main
|
|
19
|
+
from ...testing.provision import generate_driver_url
|
|
20
|
+
from ...testing.provision import log
|
|
21
|
+
from ...testing.provision import post_configure_engine
|
|
22
|
+
from ...testing.provision import post_configure_testing_engine
|
|
23
|
+
from ...testing.provision import run_reap_dbs
|
|
24
|
+
from ...testing.provision import stop_test_class_outside_fixtures
|
|
25
|
+
from ...testing.provision import temp_table_keyword_args
|
|
26
|
+
from ...testing.provision import upsert
|
|
27
|
+
|
|
28
|
+
# TODO: I can't get this to build dynamically with pytest-xdist procs
|
|
29
|
+
_drivernames = {
|
|
30
|
+
"pysqlite",
|
|
31
|
+
"aiosqlite",
|
|
32
|
+
"pysqlcipher",
|
|
33
|
+
"pysqlite_numeric",
|
|
34
|
+
"pysqlite_dollar",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _format_url(url, driver, ident):
|
|
39
|
+
"""given a sqlite url + desired driver + ident, make a canonical
|
|
40
|
+
URL out of it
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
url = sa_url.make_url(url)
|
|
44
|
+
|
|
45
|
+
if driver is None:
|
|
46
|
+
driver = url.get_driver_name()
|
|
47
|
+
|
|
48
|
+
filename = url.database
|
|
49
|
+
|
|
50
|
+
needs_enc = driver == "pysqlcipher"
|
|
51
|
+
name_token = None
|
|
52
|
+
|
|
53
|
+
if filename and filename != ":memory:":
|
|
54
|
+
assert "test_schema" not in filename
|
|
55
|
+
tokens = re.split(r"[_\.]", filename)
|
|
56
|
+
|
|
57
|
+
for token in tokens:
|
|
58
|
+
if token in _drivernames:
|
|
59
|
+
if driver is None:
|
|
60
|
+
driver = token
|
|
61
|
+
continue
|
|
62
|
+
elif token in ("db", "enc"):
|
|
63
|
+
continue
|
|
64
|
+
elif name_token is None:
|
|
65
|
+
name_token = token.strip("_")
|
|
66
|
+
|
|
67
|
+
assert name_token, f"sqlite filename has no name token: {url.database}"
|
|
68
|
+
|
|
69
|
+
new_filename = f"{name_token}_{driver}"
|
|
70
|
+
if ident:
|
|
71
|
+
new_filename += f"_{ident}"
|
|
72
|
+
new_filename += ".db"
|
|
73
|
+
if needs_enc:
|
|
74
|
+
new_filename += ".enc"
|
|
75
|
+
url = url.set(database=new_filename)
|
|
76
|
+
|
|
77
|
+
if needs_enc:
|
|
78
|
+
url = url.set(password="test")
|
|
79
|
+
|
|
80
|
+
url = url.set(drivername="sqlite+%s" % (driver,))
|
|
81
|
+
|
|
82
|
+
return url
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@generate_driver_url.for_db("sqlite")
|
|
86
|
+
def generate_driver_url(url, driver, query_str):
|
|
87
|
+
url = _format_url(url, driver, None)
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
url.get_dialect()
|
|
91
|
+
except exc.NoSuchModuleError:
|
|
92
|
+
return None
|
|
93
|
+
else:
|
|
94
|
+
return url
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@follower_url_from_main.for_db("sqlite")
|
|
98
|
+
def _sqlite_follower_url_from_main(url, ident):
|
|
99
|
+
return _format_url(url, None, ident)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@post_configure_engine.for_db("sqlite")
|
|
103
|
+
def _sqlite_post_configure_engine(url, engine, follower_ident):
|
|
104
|
+
from sqlalchemy import event
|
|
105
|
+
|
|
106
|
+
if follower_ident:
|
|
107
|
+
attach_path = f"{follower_ident}_{engine.driver}_test_schema.db"
|
|
108
|
+
else:
|
|
109
|
+
attach_path = f"{engine.driver}_test_schema.db"
|
|
110
|
+
|
|
111
|
+
@event.listens_for(engine, "connect")
|
|
112
|
+
def connect(dbapi_connection, connection_record):
|
|
113
|
+
# use file DBs in all cases, memory acts kind of strangely
|
|
114
|
+
# as an attached
|
|
115
|
+
|
|
116
|
+
# NOTE! this has to be done *per connection*. New sqlite connection,
|
|
117
|
+
# as we get with say, QueuePool, the attaches are gone.
|
|
118
|
+
# so schemes to delete those attached files have to be done at the
|
|
119
|
+
# filesystem level and not rely upon what attachments are in a
|
|
120
|
+
# particular SQLite connection
|
|
121
|
+
dbapi_connection.execute(
|
|
122
|
+
f'ATTACH DATABASE "{attach_path}" AS test_schema'
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
@event.listens_for(engine, "engine_disposed")
|
|
126
|
+
def dispose(engine):
|
|
127
|
+
"""most databases should be dropped using
|
|
128
|
+
stop_test_class_outside_fixtures
|
|
129
|
+
|
|
130
|
+
however a few tests like AttachedDBTest might not get triggered on
|
|
131
|
+
that main hook
|
|
132
|
+
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
if os.path.exists(attach_path):
|
|
136
|
+
os.remove(attach_path)
|
|
137
|
+
|
|
138
|
+
filename = engine.url.database
|
|
139
|
+
|
|
140
|
+
if filename and filename != ":memory:" and os.path.exists(filename):
|
|
141
|
+
os.remove(filename)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@post_configure_testing_engine.for_db("sqlite")
|
|
145
|
+
def _sqlite_post_configure_testing_engine(url, engine, options, scope):
|
|
146
|
+
|
|
147
|
+
sqlite_savepoint = options.get("sqlite_savepoint", False)
|
|
148
|
+
sqlite_share_pool = options.get("sqlite_share_pool", False)
|
|
149
|
+
|
|
150
|
+
if sqlite_savepoint and engine.name == "sqlite":
|
|
151
|
+
# apply SQLite savepoint workaround
|
|
152
|
+
@event.listens_for(engine, "connect")
|
|
153
|
+
def do_connect(dbapi_connection, connection_record):
|
|
154
|
+
dbapi_connection.isolation_level = None
|
|
155
|
+
|
|
156
|
+
@event.listens_for(engine, "begin")
|
|
157
|
+
def do_begin(conn):
|
|
158
|
+
conn.exec_driver_sql("BEGIN")
|
|
159
|
+
|
|
160
|
+
if sqlite_share_pool:
|
|
161
|
+
# SingletonThreadPool, StaticPool both support "transfer"
|
|
162
|
+
# so a new pool can share the same SQLite connection
|
|
163
|
+
# (single thread only)
|
|
164
|
+
if hasattr(engine.pool, "_transfer_from"):
|
|
165
|
+
options["use_reaper"] = False
|
|
166
|
+
engine.pool._transfer_from(config.db.pool)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@create_db.for_db("sqlite")
|
|
170
|
+
def _sqlite_create_db(cfg, eng, ident):
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@drop_db.for_db("sqlite")
|
|
175
|
+
def _sqlite_drop_db(cfg, eng, ident):
|
|
176
|
+
_drop_dbs_w_ident(eng.url.database, eng.driver, ident)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _drop_dbs_w_ident(databasename, driver, ident):
|
|
180
|
+
for path in os.listdir("."):
|
|
181
|
+
fname, ext = os.path.split(path)
|
|
182
|
+
if ident in fname and ext in [".db", ".db.enc"]:
|
|
183
|
+
log.info("deleting SQLite database file: %s", path)
|
|
184
|
+
os.remove(path)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@stop_test_class_outside_fixtures.for_db("sqlite")
|
|
188
|
+
def stop_test_class_outside_fixtures(config, db, cls):
|
|
189
|
+
db.dispose()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@temp_table_keyword_args.for_db("sqlite")
|
|
193
|
+
def _sqlite_temp_table_keyword_args(cfg, eng):
|
|
194
|
+
return {"prefixes": ["TEMPORARY"]}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@run_reap_dbs.for_db("sqlite")
|
|
198
|
+
def _reap_sqlite_dbs(url, idents):
|
|
199
|
+
log.info("db reaper connecting to %r", url)
|
|
200
|
+
log.info("identifiers in file: %s", ", ".join(idents))
|
|
201
|
+
url = sa_url.make_url(url)
|
|
202
|
+
for ident in idents:
|
|
203
|
+
for drivername in _drivernames:
|
|
204
|
+
_drop_dbs_w_ident(url.database, drivername, ident)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@upsert.for_db("sqlite")
|
|
208
|
+
def _upsert(
|
|
209
|
+
cfg,
|
|
210
|
+
table,
|
|
211
|
+
returning,
|
|
212
|
+
*,
|
|
213
|
+
set_lambda=None,
|
|
214
|
+
sort_by_parameter_order=False,
|
|
215
|
+
index_elements=None,
|
|
216
|
+
):
|
|
217
|
+
from sqlalchemy.dialects.sqlite import insert
|
|
218
|
+
|
|
219
|
+
stmt = insert(table)
|
|
220
|
+
|
|
221
|
+
if set_lambda:
|
|
222
|
+
stmt = stmt.on_conflict_do_update(set_=set_lambda(stmt.excluded))
|
|
223
|
+
else:
|
|
224
|
+
stmt = stmt.on_conflict_do_nothing()
|
|
225
|
+
|
|
226
|
+
stmt = stmt.returning(
|
|
227
|
+
*returning, sort_by_parameter_order=sort_by_parameter_order
|
|
228
|
+
)
|
|
229
|
+
return stmt
|