SQLAlchemy 2.1.0b2__cp313-cp313t-win_arm64.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.
Files changed (270) hide show
  1. sqlalchemy/__init__.py +298 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +171 -0
  4. sqlalchemy/connectors/asyncio.py +476 -0
  5. sqlalchemy/connectors/pyodbc.py +250 -0
  6. sqlalchemy/dialects/__init__.py +62 -0
  7. sqlalchemy/dialects/_typing.py +30 -0
  8. sqlalchemy/dialects/mssql/__init__.py +89 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4166 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  12. sqlalchemy/dialects/mssql/json.py +140 -0
  13. sqlalchemy/dialects/mssql/mssqlpython.py +220 -0
  14. sqlalchemy/dialects/mssql/provision.py +196 -0
  15. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  16. sqlalchemy/dialects/mssql/pyodbc.py +698 -0
  17. sqlalchemy/dialects/mysql/__init__.py +106 -0
  18. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  19. sqlalchemy/dialects/mysql/aiomysql.py +226 -0
  20. sqlalchemy/dialects/mysql/asyncmy.py +214 -0
  21. sqlalchemy/dialects/mysql/base.py +3877 -0
  22. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  23. sqlalchemy/dialects/mysql/dml.py +279 -0
  24. sqlalchemy/dialects/mysql/enumerated.py +277 -0
  25. sqlalchemy/dialects/mysql/expression.py +146 -0
  26. sqlalchemy/dialects/mysql/json.py +92 -0
  27. sqlalchemy/dialects/mysql/mariadb.py +67 -0
  28. sqlalchemy/dialects/mysql/mariadbconnector.py +330 -0
  29. sqlalchemy/dialects/mysql/mysqlconnector.py +296 -0
  30. sqlalchemy/dialects/mysql/mysqldb.py +312 -0
  31. sqlalchemy/dialects/mysql/provision.py +153 -0
  32. sqlalchemy/dialects/mysql/pymysql.py +157 -0
  33. sqlalchemy/dialects/mysql/pyodbc.py +156 -0
  34. sqlalchemy/dialects/mysql/reflection.py +724 -0
  35. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  36. sqlalchemy/dialects/mysql/types.py +845 -0
  37. sqlalchemy/dialects/oracle/__init__.py +85 -0
  38. sqlalchemy/dialects/oracle/base.py +3977 -0
  39. sqlalchemy/dialects/oracle/cx_oracle.py +1601 -0
  40. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  41. sqlalchemy/dialects/oracle/json.py +158 -0
  42. sqlalchemy/dialects/oracle/oracledb.py +909 -0
  43. sqlalchemy/dialects/oracle/provision.py +288 -0
  44. sqlalchemy/dialects/oracle/types.py +367 -0
  45. sqlalchemy/dialects/oracle/vector.py +368 -0
  46. sqlalchemy/dialects/postgresql/__init__.py +171 -0
  47. sqlalchemy/dialects/postgresql/_psycopg_common.py +229 -0
  48. sqlalchemy/dialects/postgresql/array.py +534 -0
  49. sqlalchemy/dialects/postgresql/asyncpg.py +1323 -0
  50. sqlalchemy/dialects/postgresql/base.py +5789 -0
  51. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  52. sqlalchemy/dialects/postgresql/dml.py +360 -0
  53. sqlalchemy/dialects/postgresql/ext.py +593 -0
  54. sqlalchemy/dialects/postgresql/hstore.py +423 -0
  55. sqlalchemy/dialects/postgresql/json.py +408 -0
  56. sqlalchemy/dialects/postgresql/named_types.py +521 -0
  57. sqlalchemy/dialects/postgresql/operators.py +130 -0
  58. sqlalchemy/dialects/postgresql/pg8000.py +670 -0
  59. sqlalchemy/dialects/postgresql/pg_catalog.py +344 -0
  60. sqlalchemy/dialects/postgresql/provision.py +184 -0
  61. sqlalchemy/dialects/postgresql/psycopg.py +799 -0
  62. sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
  63. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  64. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  65. sqlalchemy/dialects/postgresql/types.py +388 -0
  66. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  67. sqlalchemy/dialects/sqlite/aiosqlite.py +321 -0
  68. sqlalchemy/dialects/sqlite/base.py +3063 -0
  69. sqlalchemy/dialects/sqlite/dml.py +279 -0
  70. sqlalchemy/dialects/sqlite/json.py +100 -0
  71. sqlalchemy/dialects/sqlite/provision.py +229 -0
  72. sqlalchemy/dialects/sqlite/pysqlcipher.py +161 -0
  73. sqlalchemy/dialects/sqlite/pysqlite.py +754 -0
  74. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  75. sqlalchemy/engine/__init__.py +62 -0
  76. sqlalchemy/engine/_processors_cy.cp313t-win_arm64.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp313t-win_arm64.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +633 -0
  80. sqlalchemy/engine/_row_cy.cp313t-win_arm64.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp313t-win_arm64.pyd +0 -0
  83. sqlalchemy/engine/_util_cy.py +136 -0
  84. sqlalchemy/engine/base.py +3354 -0
  85. sqlalchemy/engine/characteristics.py +155 -0
  86. sqlalchemy/engine/create.py +877 -0
  87. sqlalchemy/engine/cursor.py +2421 -0
  88. sqlalchemy/engine/default.py +2402 -0
  89. sqlalchemy/engine/events.py +965 -0
  90. sqlalchemy/engine/interfaces.py +3495 -0
  91. sqlalchemy/engine/mock.py +134 -0
  92. sqlalchemy/engine/processors.py +82 -0
  93. sqlalchemy/engine/reflection.py +2100 -0
  94. sqlalchemy/engine/result.py +1966 -0
  95. sqlalchemy/engine/row.py +397 -0
  96. sqlalchemy/engine/strategies.py +16 -0
  97. sqlalchemy/engine/url.py +922 -0
  98. sqlalchemy/engine/util.py +156 -0
  99. sqlalchemy/event/__init__.py +26 -0
  100. sqlalchemy/event/api.py +220 -0
  101. sqlalchemy/event/attr.py +674 -0
  102. sqlalchemy/event/base.py +472 -0
  103. sqlalchemy/event/legacy.py +258 -0
  104. sqlalchemy/event/registry.py +390 -0
  105. sqlalchemy/events.py +17 -0
  106. sqlalchemy/exc.py +922 -0
  107. sqlalchemy/ext/__init__.py +11 -0
  108. sqlalchemy/ext/associationproxy.py +2072 -0
  109. sqlalchemy/ext/asyncio/__init__.py +29 -0
  110. sqlalchemy/ext/asyncio/base.py +281 -0
  111. sqlalchemy/ext/asyncio/engine.py +1487 -0
  112. sqlalchemy/ext/asyncio/exc.py +21 -0
  113. sqlalchemy/ext/asyncio/result.py +994 -0
  114. sqlalchemy/ext/asyncio/scoping.py +1679 -0
  115. sqlalchemy/ext/asyncio/session.py +2007 -0
  116. sqlalchemy/ext/automap.py +1701 -0
  117. sqlalchemy/ext/baked.py +559 -0
  118. sqlalchemy/ext/compiler.py +600 -0
  119. sqlalchemy/ext/declarative/__init__.py +65 -0
  120. sqlalchemy/ext/declarative/extensions.py +560 -0
  121. sqlalchemy/ext/horizontal_shard.py +481 -0
  122. sqlalchemy/ext/hybrid.py +1877 -0
  123. sqlalchemy/ext/indexable.py +364 -0
  124. sqlalchemy/ext/instrumentation.py +450 -0
  125. sqlalchemy/ext/mutable.py +1081 -0
  126. sqlalchemy/ext/orderinglist.py +439 -0
  127. sqlalchemy/ext/serializer.py +185 -0
  128. sqlalchemy/future/__init__.py +16 -0
  129. sqlalchemy/future/engine.py +15 -0
  130. sqlalchemy/inspection.py +174 -0
  131. sqlalchemy/log.py +283 -0
  132. sqlalchemy/orm/__init__.py +176 -0
  133. sqlalchemy/orm/_orm_constructors.py +2694 -0
  134. sqlalchemy/orm/_typing.py +179 -0
  135. sqlalchemy/orm/attributes.py +2868 -0
  136. sqlalchemy/orm/base.py +976 -0
  137. sqlalchemy/orm/bulk_persistence.py +2152 -0
  138. sqlalchemy/orm/clsregistry.py +582 -0
  139. sqlalchemy/orm/collections.py +1568 -0
  140. sqlalchemy/orm/context.py +3471 -0
  141. sqlalchemy/orm/decl_api.py +2280 -0
  142. sqlalchemy/orm/decl_base.py +2309 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1183 -0
  145. sqlalchemy/orm/dynamic.py +307 -0
  146. sqlalchemy/orm/evaluator.py +379 -0
  147. sqlalchemy/orm/events.py +3386 -0
  148. sqlalchemy/orm/exc.py +237 -0
  149. sqlalchemy/orm/identity.py +302 -0
  150. sqlalchemy/orm/instrumentation.py +746 -0
  151. sqlalchemy/orm/interfaces.py +1589 -0
  152. sqlalchemy/orm/loading.py +1684 -0
  153. sqlalchemy/orm/mapped_collection.py +557 -0
  154. sqlalchemy/orm/mapper.py +4411 -0
  155. sqlalchemy/orm/path_registry.py +829 -0
  156. sqlalchemy/orm/persistence.py +1789 -0
  157. sqlalchemy/orm/properties.py +973 -0
  158. sqlalchemy/orm/query.py +3528 -0
  159. sqlalchemy/orm/relationships.py +3570 -0
  160. sqlalchemy/orm/scoping.py +2232 -0
  161. sqlalchemy/orm/session.py +5403 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3492 -0
  165. sqlalchemy/orm/strategy_options.py +2562 -0
  166. sqlalchemy/orm/sync.py +164 -0
  167. sqlalchemy/orm/unitofwork.py +798 -0
  168. sqlalchemy/orm/util.py +2438 -0
  169. sqlalchemy/orm/writeonly.py +694 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1522 -0
  172. sqlalchemy/pool/events.py +375 -0
  173. sqlalchemy/pool/impl.py +582 -0
  174. sqlalchemy/py.typed +0 -0
  175. sqlalchemy/schema.py +74 -0
  176. sqlalchemy/sql/__init__.py +156 -0
  177. sqlalchemy/sql/_annotated_cols.py +397 -0
  178. sqlalchemy/sql/_dml_constructors.py +132 -0
  179. sqlalchemy/sql/_elements_constructors.py +2164 -0
  180. sqlalchemy/sql/_orm_types.py +20 -0
  181. sqlalchemy/sql/_selectable_constructors.py +840 -0
  182. sqlalchemy/sql/_typing.py +487 -0
  183. sqlalchemy/sql/_util_cy.cp313t-win_arm64.pyd +0 -0
  184. sqlalchemy/sql/_util_cy.py +127 -0
  185. sqlalchemy/sql/annotation.py +590 -0
  186. sqlalchemy/sql/base.py +2699 -0
  187. sqlalchemy/sql/cache_key.py +1066 -0
  188. sqlalchemy/sql/coercions.py +1373 -0
  189. sqlalchemy/sql/compiler.py +8327 -0
  190. sqlalchemy/sql/crud.py +1815 -0
  191. sqlalchemy/sql/ddl.py +1928 -0
  192. sqlalchemy/sql/default_comparator.py +654 -0
  193. sqlalchemy/sql/dml.py +1977 -0
  194. sqlalchemy/sql/elements.py +6033 -0
  195. sqlalchemy/sql/events.py +458 -0
  196. sqlalchemy/sql/expression.py +172 -0
  197. sqlalchemy/sql/functions.py +2305 -0
  198. sqlalchemy/sql/lambdas.py +1443 -0
  199. sqlalchemy/sql/naming.py +209 -0
  200. sqlalchemy/sql/operators.py +2897 -0
  201. sqlalchemy/sql/roles.py +332 -0
  202. sqlalchemy/sql/schema.py +6703 -0
  203. sqlalchemy/sql/selectable.py +7553 -0
  204. sqlalchemy/sql/sqltypes.py +4093 -0
  205. sqlalchemy/sql/traversals.py +1042 -0
  206. sqlalchemy/sql/type_api.py +2446 -0
  207. sqlalchemy/sql/util.py +1495 -0
  208. sqlalchemy/sql/visitors.py +1157 -0
  209. sqlalchemy/testing/__init__.py +96 -0
  210. sqlalchemy/testing/assertions.py +1007 -0
  211. sqlalchemy/testing/assertsql.py +519 -0
  212. sqlalchemy/testing/asyncio.py +128 -0
  213. sqlalchemy/testing/config.py +440 -0
  214. sqlalchemy/testing/engines.py +483 -0
  215. sqlalchemy/testing/entities.py +117 -0
  216. sqlalchemy/testing/exclusions.py +476 -0
  217. sqlalchemy/testing/fixtures/__init__.py +30 -0
  218. sqlalchemy/testing/fixtures/base.py +384 -0
  219. sqlalchemy/testing/fixtures/mypy.py +247 -0
  220. sqlalchemy/testing/fixtures/orm.py +227 -0
  221. sqlalchemy/testing/fixtures/sql.py +538 -0
  222. sqlalchemy/testing/pickleable.py +155 -0
  223. sqlalchemy/testing/plugin/__init__.py +6 -0
  224. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  225. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  226. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  227. sqlalchemy/testing/profiling.py +329 -0
  228. sqlalchemy/testing/provision.py +613 -0
  229. sqlalchemy/testing/requirements.py +1978 -0
  230. sqlalchemy/testing/schema.py +198 -0
  231. sqlalchemy/testing/suite/__init__.py +19 -0
  232. sqlalchemy/testing/suite/test_cte.py +237 -0
  233. sqlalchemy/testing/suite/test_ddl.py +420 -0
  234. sqlalchemy/testing/suite/test_dialect.py +776 -0
  235. sqlalchemy/testing/suite/test_insert.py +630 -0
  236. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  237. sqlalchemy/testing/suite/test_results.py +660 -0
  238. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  239. sqlalchemy/testing/suite/test_select.py +2112 -0
  240. sqlalchemy/testing/suite/test_sequence.py +317 -0
  241. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  242. sqlalchemy/testing/suite/test_types.py +2271 -0
  243. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  244. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  245. sqlalchemy/testing/util.py +535 -0
  246. sqlalchemy/testing/warnings.py +52 -0
  247. sqlalchemy/types.py +76 -0
  248. sqlalchemy/util/__init__.py +158 -0
  249. sqlalchemy/util/_collections.py +688 -0
  250. sqlalchemy/util/_collections_cy.cp313t-win_arm64.pyd +0 -0
  251. sqlalchemy/util/_collections_cy.pxd +8 -0
  252. sqlalchemy/util/_collections_cy.py +516 -0
  253. sqlalchemy/util/_has_cython.py +46 -0
  254. sqlalchemy/util/_immutabledict_cy.cp313t-win_arm64.pyd +0 -0
  255. sqlalchemy/util/_immutabledict_cy.py +240 -0
  256. sqlalchemy/util/compat.py +299 -0
  257. sqlalchemy/util/concurrency.py +322 -0
  258. sqlalchemy/util/cython.py +79 -0
  259. sqlalchemy/util/deprecations.py +401 -0
  260. sqlalchemy/util/langhelpers.py +2320 -0
  261. sqlalchemy/util/preloaded.py +152 -0
  262. sqlalchemy/util/queue.py +304 -0
  263. sqlalchemy/util/tool_support.py +201 -0
  264. sqlalchemy/util/topological.py +120 -0
  265. sqlalchemy/util/typing.py +711 -0
  266. sqlalchemy-2.1.0b2.dist-info/METADATA +269 -0
  267. sqlalchemy-2.1.0b2.dist-info/RECORD +270 -0
  268. sqlalchemy-2.1.0b2.dist-info/WHEEL +5 -0
  269. sqlalchemy-2.1.0b2.dist-info/licenses/LICENSE +19 -0
  270. sqlalchemy-2.1.0b2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3063 @@
1
+ # dialects/sqlite/base.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
+
10
+ r'''
11
+ .. dialect:: sqlite
12
+ :name: SQLite
13
+ :normal_support: 3.12+
14
+ :best_effort: 3.7.16+
15
+
16
+ .. _sqlite_datetime:
17
+
18
+ Date and Time Types
19
+ -------------------
20
+
21
+ SQLite does not have built-in DATE, TIME, or DATETIME types, and pysqlite does
22
+ not provide out of the box functionality for translating values between Python
23
+ `datetime` objects and a SQLite-supported format. SQLAlchemy's own
24
+ :class:`~sqlalchemy.types.DateTime` and related types provide date formatting
25
+ and parsing functionality when SQLite is used. The implementation classes are
26
+ :class:`_sqlite.DATETIME`, :class:`_sqlite.DATE` and :class:`_sqlite.TIME`.
27
+ These types represent dates and times as ISO formatted strings, which also
28
+ nicely support ordering. There's no reliance on typical "libc" internals for
29
+ these functions so historical dates are fully supported.
30
+
31
+ Ensuring Text affinity
32
+ ^^^^^^^^^^^^^^^^^^^^^^
33
+
34
+ The DDL rendered for these types is the standard ``DATE``, ``TIME``
35
+ and ``DATETIME`` indicators. However, custom storage formats can also be
36
+ applied to these types. When the
37
+ storage format is detected as containing no alpha characters, the DDL for
38
+ these types is rendered as ``DATE_CHAR``, ``TIME_CHAR``, and ``DATETIME_CHAR``,
39
+ so that the column continues to have textual affinity.
40
+
41
+ .. seealso::
42
+
43
+ `Type Affinity <https://www.sqlite.org/datatype3.html#affinity>`_ -
44
+ in the SQLite documentation
45
+
46
+ .. _sqlite_autoincrement:
47
+
48
+ SQLite Auto Incrementing Behavior
49
+ ----------------------------------
50
+
51
+ Background on SQLite's autoincrement is at: https://sqlite.org/autoinc.html
52
+
53
+ Key concepts:
54
+
55
+ * SQLite has an implicit "auto increment" feature that takes place for any
56
+ non-composite primary-key column that is specifically created using
57
+ "INTEGER PRIMARY KEY" for the type + primary key.
58
+
59
+ * SQLite also has an explicit "AUTOINCREMENT" keyword, that is **not**
60
+ equivalent to the implicit autoincrement feature; this keyword is not
61
+ recommended for general use. SQLAlchemy does not render this keyword
62
+ unless a special SQLite-specific directive is used (see below). However,
63
+ it still requires that the column's type is named "INTEGER".
64
+
65
+ Using the AUTOINCREMENT Keyword
66
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
67
+
68
+ To specifically render the AUTOINCREMENT keyword on the primary key column
69
+ when rendering DDL, add the flag ``sqlite_autoincrement=True`` to the Table
70
+ construct::
71
+
72
+ Table(
73
+ "sometable",
74
+ metadata,
75
+ Column("id", Integer, primary_key=True),
76
+ sqlite_autoincrement=True,
77
+ )
78
+
79
+ Allowing autoincrement behavior SQLAlchemy types other than Integer/INTEGER
80
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
81
+
82
+ SQLite's typing model is based on naming conventions. Among other things, this
83
+ means that any type name which contains the substring ``"INT"`` will be
84
+ determined to be of "integer affinity". A type named ``"BIGINT"``,
85
+ ``"SPECIAL_INT"`` or even ``"XYZINTQPR"``, will be considered by SQLite to be
86
+ of "integer" affinity. However, **the SQLite autoincrement feature, whether
87
+ implicitly or explicitly enabled, requires that the name of the column's type
88
+ is exactly the string "INTEGER"**. Therefore, if an application uses a type
89
+ like :class:`.BigInteger` for a primary key, on SQLite this type will need to
90
+ be rendered as the name ``"INTEGER"`` when emitting the initial ``CREATE
91
+ TABLE`` statement in order for the autoincrement behavior to be available.
92
+
93
+ One approach to achieve this is to use :class:`.Integer` on SQLite
94
+ only using :meth:`.TypeEngine.with_variant`::
95
+
96
+ table = Table(
97
+ "my_table",
98
+ metadata,
99
+ Column(
100
+ "id",
101
+ BigInteger().with_variant(Integer, "sqlite"),
102
+ primary_key=True,
103
+ ),
104
+ )
105
+
106
+ Another is to use a subclass of :class:`.BigInteger` that overrides its DDL
107
+ name to be ``INTEGER`` when compiled against SQLite::
108
+
109
+ from sqlalchemy import BigInteger
110
+ from sqlalchemy.ext.compiler import compiles
111
+
112
+
113
+ class SLBigInteger(BigInteger):
114
+ pass
115
+
116
+
117
+ @compiles(SLBigInteger, "sqlite")
118
+ def bi_c(element, compiler, **kw):
119
+ return "INTEGER"
120
+
121
+
122
+ @compiles(SLBigInteger)
123
+ def bi_c(element, compiler, **kw):
124
+ return compiler.visit_BIGINT(element, **kw)
125
+
126
+
127
+ table = Table(
128
+ "my_table", metadata, Column("id", SLBigInteger(), primary_key=True)
129
+ )
130
+
131
+ .. seealso::
132
+
133
+ :meth:`.TypeEngine.with_variant`
134
+
135
+ :ref:`sqlalchemy.ext.compiler_toplevel`
136
+
137
+ `Datatypes In SQLite Version 3 <https://sqlite.org/datatype3.html>`_
138
+
139
+ .. _sqlite_transactions:
140
+
141
+ Transactions with SQLite and the sqlite3 driver
142
+ -----------------------------------------------
143
+
144
+ As a file-based database, SQLite's approach to transactions differs from
145
+ traditional databases in many ways. Additionally, the ``sqlite3`` driver
146
+ standard with Python (as well as the async version ``aiosqlite`` which builds
147
+ on top of it) has several quirks, workarounds, and API features in the
148
+ area of transaction control, all of which generally need to be addressed when
149
+ constructing a SQLAlchemy application that uses SQLite.
150
+
151
+ Legacy Transaction Mode with the sqlite3 driver
152
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
153
+
154
+ The most important aspect of transaction handling with the sqlite3 driver is
155
+ that it defaults (which will continue through Python 3.15 before being
156
+ removed in Python 3.16) to legacy transactional behavior which does
157
+ not strictly follow :pep:`249`. The way in which the driver diverges from the
158
+ PEP is that it does not "begin" a transaction automatically as dictated by
159
+ :pep:`249` except in the case of DML statements, e.g. INSERT, UPDATE, and
160
+ DELETE. Normally, :pep:`249` dictates that a BEGIN must be emitted upon
161
+ the first SQL statement of any kind, so that all subsequent operations will
162
+ be established within a transaction until ``connection.commit()`` has been
163
+ called. The ``sqlite3`` driver, in an effort to be easier to use in
164
+ highly concurrent environments, skips this step for DQL (e.g. SELECT) statements,
165
+ and also skips it for DDL (e.g. CREATE TABLE etc.) statements for more legacy
166
+ reasons. Statements such as SAVEPOINT are also skipped.
167
+
168
+ In modern versions of the ``sqlite3`` driver as of Python 3.12, this legacy
169
+ mode of operation is referred to as
170
+ `"legacy transaction control" <https://docs.python.org/3/library/sqlite3.html#sqlite3-transaction-control-isolation-level>`_, and is in
171
+ effect by default due to the ``Connection.autocommit`` parameter being set to
172
+ the constant ``sqlite3.LEGACY_TRANSACTION_CONTROL``. Prior to Python 3.12,
173
+ the ``Connection.autocommit`` attribute did not exist.
174
+
175
+ The implications of legacy transaction mode include:
176
+
177
+ * **Incorrect support for transactional DDL** - statements like CREATE TABLE, ALTER TABLE,
178
+ CREATE INDEX etc. will not automatically BEGIN a transaction if one were not
179
+ started already, leading to the changes by each statement being
180
+ "autocommitted" immediately unless BEGIN were otherwise emitted first. Very
181
+ old (pre Python 3.6) versions of SQLite would also force a COMMIT for these
182
+ operations even if a transaction were present, however this is no longer the
183
+ case.
184
+ * **SERIALIZABLE behavior not fully functional** - SQLite's transaction isolation
185
+ behavior is normally consistent with SERIALIZABLE isolation, as it is a file-
186
+ based system that locks the database file entirely for write operations,
187
+ preventing COMMIT until all reader transactions (and associated file locks)
188
+ have completed. However, sqlite3's legacy transaction mode fails to emit BEGIN for SELECT
189
+ statements, which causes these SELECT statements to no longer be "repeatable",
190
+ failing one of the consistency guarantees of SERIALIZABLE.
191
+ * **Incorrect behavior for SAVEPOINT** - as the SAVEPOINT statement does not
192
+ imply a BEGIN, a new SAVEPOINT emitted before a BEGIN will function on its
193
+ own but fails to participate in the enclosing transaction, meaning a ROLLBACK
194
+ of the transaction will not rollback elements that were part of a released
195
+ savepoint.
196
+
197
+ Legacy transaction mode first existed in order to facilitate working around
198
+ SQLite's file locks. Because SQLite relies upon whole-file locks, it is easy to
199
+ get "database is locked" errors, particularly when newer features like "write
200
+ ahead logging" are disabled. This is a key reason why ``sqlite3``'s legacy
201
+ transaction mode is still the default mode of operation; disabling it will
202
+ produce behavior that is more susceptible to locked database errors. However
203
+ note that **legacy transaction mode will no longer be the default** in a future
204
+ Python version (3.16 as of this writing).
205
+
206
+ .. _sqlite_enabling_transactions:
207
+
208
+ Enabling Non-Legacy SQLite Transactional Modes with the sqlite3 or aiosqlite driver
209
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
210
+
211
+ Current SQLAlchemy support allows either for setting the
212
+ ``.Connection.autocommit`` attribute, most directly by using a
213
+ :func:`._sa.create_engine` parameter, or if on an older version of Python where
214
+ the attribute is not available, using event hooks to control the behavior of
215
+ BEGIN.
216
+
217
+ * **Enabling modern sqlite3 transaction control via the autocommit connect parameter** (Python 3.12 and above)
218
+
219
+ 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>`_,
220
+ the most straightforward approach is to set the attribute to its recommended value
221
+ of ``False`` at the connect level using :paramref:`_sa.create_engine.connect_args``::
222
+
223
+ from sqlalchemy import create_engine
224
+
225
+ engine = create_engine(
226
+ "sqlite:///myfile.db", connect_args={"autocommit": False}
227
+ )
228
+
229
+ This parameter is also passed through when using the aiosqlite driver::
230
+
231
+ from sqlalchemy.ext.asyncio import create_async_engine
232
+
233
+ engine = create_async_engine(
234
+ "sqlite+aiosqlite:///myfile.db", connect_args={"autocommit": False}
235
+ )
236
+
237
+ The parameter can also be set at the attribute level using the :meth:`.PoolEvents.connect`
238
+ event hook, however this will only work for sqlite3, as aiosqlite does not yet expose this
239
+ attribute on its ``Connection`` object::
240
+
241
+ from sqlalchemy import create_engine, event
242
+
243
+ engine = create_engine("sqlite:///myfile.db")
244
+
245
+
246
+ @event.listens_for(engine, "connect")
247
+ def do_connect(dbapi_connection, connection_record):
248
+ # enable autocommit=False mode
249
+ dbapi_connection.autocommit = False
250
+
251
+ * **Using SQLAlchemy to emit BEGIN in lieu of SQLite's transaction control** (all Python versions, sqlite3 and aiosqlite)
252
+
253
+ For older versions of ``sqlite3`` or for cross-compatibility with older and
254
+ newer versions, SQLAlchemy can also take over the job of transaction control.
255
+ This is achieved by using the :meth:`.ConnectionEvents.begin` hook
256
+ to emit the "BEGIN" command directly, while also disabling SQLite's control
257
+ of this command using the :meth:`.PoolEvents.connect` event hook to set the
258
+ ``Connection.isolation_level`` attribute to ``None``::
259
+
260
+
261
+ from sqlalchemy import create_engine, event
262
+
263
+ engine = create_engine("sqlite:///myfile.db")
264
+
265
+
266
+ @event.listens_for(engine, "connect")
267
+ def do_connect(dbapi_connection, connection_record):
268
+ # disable sqlite3's emitting of the BEGIN statement entirely.
269
+ dbapi_connection.isolation_level = None
270
+
271
+
272
+ @event.listens_for(engine, "begin")
273
+ def do_begin(conn):
274
+ # emit our own BEGIN. sqlite3 still emits COMMIT/ROLLBACK correctly
275
+ conn.exec_driver_sql("BEGIN")
276
+
277
+ When using the asyncio variant ``aiosqlite``, refer to ``engine.sync_engine``
278
+ as in the example below::
279
+
280
+ from sqlalchemy import create_engine, event
281
+ from sqlalchemy.ext.asyncio import create_async_engine
282
+
283
+ engine = create_async_engine("sqlite+aiosqlite:///myfile.db")
284
+
285
+
286
+ @event.listens_for(engine.sync_engine, "connect")
287
+ def do_connect(dbapi_connection, connection_record):
288
+ # disable aiosqlite's emitting of the BEGIN statement entirely.
289
+ dbapi_connection.isolation_level = None
290
+
291
+
292
+ @event.listens_for(engine.sync_engine, "begin")
293
+ def do_begin(conn):
294
+ # emit our own BEGIN. aiosqlite still emits COMMIT/ROLLBACK correctly
295
+ conn.exec_driver_sql("BEGIN")
296
+
297
+ .. _sqlite_isolation_level:
298
+
299
+ Using SQLAlchemy's Driver Level AUTOCOMMIT Feature with SQLite
300
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
301
+
302
+ SQLAlchemy has a comprehensive database isolation feature with optional
303
+ autocommit support that is introduced in the section :ref:`dbapi_autocommit`.
304
+
305
+ For the ``sqlite3`` and ``aiosqlite`` drivers, SQLAlchemy only includes
306
+ built-in support for "AUTOCOMMIT". Note that this mode is currently incompatible
307
+ with the non-legacy isolation mode hooks documented in the previous
308
+ section at :ref:`sqlite_enabling_transactions`.
309
+
310
+ To use the ``sqlite3`` driver with SQLAlchemy driver-level autocommit,
311
+ create an engine setting the :paramref:`_sa.create_engine.isolation_level`
312
+ parameter to "AUTOCOMMIT"::
313
+
314
+ eng = create_engine("sqlite:///myfile.db", isolation_level="AUTOCOMMIT")
315
+
316
+ When using the above mode, any event hooks that set the sqlite3 ``Connection.autocommit``
317
+ parameter away from its default of ``sqlite3.LEGACY_TRANSACTION_CONTROL``
318
+ as well as hooks that emit ``BEGIN`` should be disabled.
319
+
320
+ Additional Reading for SQLite / sqlite3 transaction control
321
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
322
+
323
+ Links with important information on SQLite, the sqlite3 driver,
324
+ as well as long historical conversations on how things got to their current state:
325
+
326
+ * `Isolation in SQLite <https://www.sqlite.org/isolation.html>`_ - on the SQLite website
327
+ * `Transaction control <https://docs.python.org/3/library/sqlite3.html#transaction-control>`_ - describes the sqlite3 autocommit attribute as well
328
+ as the legacy isolation_level attribute.
329
+ * `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
330
+ * `sqlite3 module breaks transactions and potentially corrupts data <https://github.com/python/cpython/issues/54949>`_ - imported Python standard library issue on github
331
+
332
+
333
+ INSERT/UPDATE/DELETE...RETURNING
334
+ ---------------------------------
335
+
336
+ The SQLite dialect supports SQLite 3.35's ``INSERT|UPDATE|DELETE..RETURNING``
337
+ syntax. ``INSERT..RETURNING`` may be used
338
+ automatically in some cases in order to fetch newly generated identifiers in
339
+ place of the traditional approach of using ``cursor.lastrowid``, however
340
+ ``cursor.lastrowid`` is currently still preferred for simple single-statement
341
+ cases for its better performance.
342
+
343
+ To specify an explicit ``RETURNING`` clause, use the
344
+ :meth:`._UpdateBase.returning` method on a per-statement basis::
345
+
346
+ # INSERT..RETURNING
347
+ result = connection.execute(
348
+ table.insert().values(name="foo").returning(table.c.col1, table.c.col2)
349
+ )
350
+ print(result.all())
351
+
352
+ # UPDATE..RETURNING
353
+ result = connection.execute(
354
+ table.update()
355
+ .where(table.c.name == "foo")
356
+ .values(name="bar")
357
+ .returning(table.c.col1, table.c.col2)
358
+ )
359
+ print(result.all())
360
+
361
+ # DELETE..RETURNING
362
+ result = connection.execute(
363
+ table.delete()
364
+ .where(table.c.name == "foo")
365
+ .returning(table.c.col1, table.c.col2)
366
+ )
367
+ print(result.all())
368
+
369
+ .. versionadded:: 2.0 Added support for SQLite RETURNING
370
+
371
+
372
+ .. _sqlite_foreign_keys:
373
+
374
+ Foreign Key Support
375
+ -------------------
376
+
377
+ SQLite supports FOREIGN KEY syntax when emitting CREATE statements for tables,
378
+ however by default these constraints have no effect on the operation of the
379
+ table.
380
+
381
+ Constraint checking on SQLite has three prerequisites:
382
+
383
+ * At least version 3.6.19 of SQLite must be in use
384
+ * The SQLite library must be compiled *without* the SQLITE_OMIT_FOREIGN_KEY
385
+ or SQLITE_OMIT_TRIGGER symbols enabled.
386
+ * The ``PRAGMA foreign_keys = ON`` statement must be emitted on all
387
+ connections before use -- including the initial call to
388
+ :meth:`sqlalchemy.schema.MetaData.create_all`.
389
+
390
+ SQLAlchemy allows for the ``PRAGMA`` statement to be emitted automatically for
391
+ new connections through the usage of events::
392
+
393
+ from sqlalchemy.engine import Engine
394
+ from sqlalchemy import event
395
+
396
+
397
+ @event.listens_for(Engine, "connect")
398
+ def set_sqlite_pragma(dbapi_connection, connection_record):
399
+ # the sqlite3 driver will not set PRAGMA foreign_keys
400
+ # if autocommit=False; set to True temporarily
401
+ ac = dbapi_connection.autocommit
402
+ dbapi_connection.autocommit = True
403
+
404
+ cursor = dbapi_connection.cursor()
405
+ cursor.execute("PRAGMA foreign_keys=ON")
406
+ cursor.close()
407
+
408
+ # restore previous autocommit setting
409
+ dbapi_connection.autocommit = ac
410
+
411
+ .. warning::
412
+
413
+ When SQLite foreign keys are enabled, it is **not possible**
414
+ to emit CREATE or DROP statements for tables that contain
415
+ mutually-dependent foreign key constraints;
416
+ to emit the DDL for these tables requires that ALTER TABLE be used to
417
+ create or drop these constraints separately, for which SQLite has
418
+ no support.
419
+
420
+ .. seealso::
421
+
422
+ `SQLite Foreign Key Support <https://www.sqlite.org/foreignkeys.html>`_
423
+ - on the SQLite web site.
424
+
425
+ :ref:`event_toplevel` - SQLAlchemy event API.
426
+
427
+ :ref:`use_alter` - more information on SQLAlchemy's facilities for handling
428
+ mutually-dependent foreign key constraints.
429
+
430
+ .. _sqlite_on_conflict_ddl:
431
+
432
+ ON CONFLICT support for constraints
433
+ -----------------------------------
434
+
435
+ .. seealso:: This section describes the :term:`DDL` version of "ON CONFLICT" for
436
+ SQLite, which occurs within a CREATE TABLE statement. For "ON CONFLICT" as
437
+ applied to an INSERT statement, see :ref:`sqlite_on_conflict_insert`.
438
+
439
+ SQLite supports a non-standard DDL clause known as ON CONFLICT which can be applied
440
+ to primary key, unique, check, and not null constraints. In DDL, it is
441
+ rendered either within the "CONSTRAINT" clause or within the column definition
442
+ itself depending on the location of the target constraint. To render this
443
+ clause within DDL, the extension parameter ``sqlite_on_conflict`` can be
444
+ specified with a string conflict resolution algorithm within the
445
+ :class:`.PrimaryKeyConstraint`, :class:`.UniqueConstraint`,
446
+ :class:`.CheckConstraint` objects. Within the :class:`_schema.Column` object,
447
+ there
448
+ are individual parameters ``sqlite_on_conflict_not_null``,
449
+ ``sqlite_on_conflict_primary_key``, ``sqlite_on_conflict_unique`` which each
450
+ correspond to the three types of relevant constraint types that can be
451
+ indicated from a :class:`_schema.Column` object.
452
+
453
+ .. seealso::
454
+
455
+ `ON CONFLICT <https://www.sqlite.org/lang_conflict.html>`_ - in the SQLite
456
+ documentation
457
+
458
+ The ``sqlite_on_conflict`` parameters accept a string argument which is just
459
+ the resolution name to be chosen, which on SQLite can be one of ROLLBACK,
460
+ ABORT, FAIL, IGNORE, and REPLACE. For example, to add a UNIQUE constraint
461
+ that specifies the IGNORE algorithm::
462
+
463
+ some_table = Table(
464
+ "some_table",
465
+ metadata,
466
+ Column("id", Integer, primary_key=True),
467
+ Column("data", Integer),
468
+ UniqueConstraint("id", "data", sqlite_on_conflict="IGNORE"),
469
+ )
470
+
471
+ The above renders CREATE TABLE DDL as:
472
+
473
+ .. sourcecode:: sql
474
+
475
+ CREATE TABLE some_table (
476
+ id INTEGER NOT NULL,
477
+ data INTEGER,
478
+ PRIMARY KEY (id),
479
+ UNIQUE (id, data) ON CONFLICT IGNORE
480
+ )
481
+
482
+
483
+ When using the :paramref:`_schema.Column.unique`
484
+ flag to add a UNIQUE constraint
485
+ to a single column, the ``sqlite_on_conflict_unique`` parameter can
486
+ be added to the :class:`_schema.Column` as well, which will be added to the
487
+ UNIQUE constraint in the DDL::
488
+
489
+ some_table = Table(
490
+ "some_table",
491
+ metadata,
492
+ Column("id", Integer, primary_key=True),
493
+ Column(
494
+ "data", Integer, unique=True, sqlite_on_conflict_unique="IGNORE"
495
+ ),
496
+ )
497
+
498
+ rendering:
499
+
500
+ .. sourcecode:: sql
501
+
502
+ CREATE TABLE some_table (
503
+ id INTEGER NOT NULL,
504
+ data INTEGER,
505
+ PRIMARY KEY (id),
506
+ UNIQUE (data) ON CONFLICT IGNORE
507
+ )
508
+
509
+ To apply the FAIL algorithm for a NOT NULL constraint,
510
+ ``sqlite_on_conflict_not_null`` is used::
511
+
512
+ some_table = Table(
513
+ "some_table",
514
+ metadata,
515
+ Column("id", Integer, primary_key=True),
516
+ Column(
517
+ "data", Integer, nullable=False, sqlite_on_conflict_not_null="FAIL"
518
+ ),
519
+ )
520
+
521
+ this renders the column inline ON CONFLICT phrase:
522
+
523
+ .. sourcecode:: sql
524
+
525
+ CREATE TABLE some_table (
526
+ id INTEGER NOT NULL,
527
+ data INTEGER NOT NULL ON CONFLICT FAIL,
528
+ PRIMARY KEY (id)
529
+ )
530
+
531
+
532
+ Similarly, for an inline primary key, use ``sqlite_on_conflict_primary_key``::
533
+
534
+ some_table = Table(
535
+ "some_table",
536
+ metadata,
537
+ Column(
538
+ "id",
539
+ Integer,
540
+ primary_key=True,
541
+ sqlite_on_conflict_primary_key="FAIL",
542
+ ),
543
+ )
544
+
545
+ SQLAlchemy renders the PRIMARY KEY constraint separately, so the conflict
546
+ resolution algorithm is applied to the constraint itself:
547
+
548
+ .. sourcecode:: sql
549
+
550
+ CREATE TABLE some_table (
551
+ id INTEGER NOT NULL,
552
+ PRIMARY KEY (id) ON CONFLICT FAIL
553
+ )
554
+
555
+ .. _sqlite_on_conflict_insert:
556
+
557
+ INSERT...ON CONFLICT (Upsert)
558
+ -----------------------------
559
+
560
+ .. seealso:: This section describes the :term:`DML` version of "ON CONFLICT" for
561
+ SQLite, which occurs within an INSERT statement. For "ON CONFLICT" as
562
+ applied to a CREATE TABLE statement, see :ref:`sqlite_on_conflict_ddl`.
563
+
564
+ From version 3.24.0 onwards, SQLite supports "upserts" (update or insert)
565
+ of rows into a table via the ``ON CONFLICT`` clause of the ``INSERT``
566
+ statement. A candidate row will only be inserted if that row does not violate
567
+ any unique or primary key constraints. In the case of a unique constraint violation, a
568
+ secondary action can occur which can be either "DO UPDATE", indicating that
569
+ the data in the target row should be updated, or "DO NOTHING", which indicates
570
+ to silently skip this row.
571
+
572
+ Conflicts are determined using columns that are part of existing unique
573
+ constraints and indexes. These constraints are identified by stating the
574
+ columns and conditions that comprise the indexes.
575
+
576
+ SQLAlchemy provides ``ON CONFLICT`` support via the SQLite-specific
577
+ :func:`_sqlite.insert()` function, which provides
578
+ the generative methods :meth:`_sqlite.Insert.on_conflict_do_update`
579
+ and :meth:`_sqlite.Insert.on_conflict_do_nothing`:
580
+
581
+ .. sourcecode:: pycon+sql
582
+
583
+ >>> from sqlalchemy.dialects.sqlite import insert
584
+
585
+ >>> insert_stmt = insert(my_table).values(
586
+ ... id="some_existing_id", data="inserted value"
587
+ ... )
588
+
589
+ >>> do_update_stmt = insert_stmt.on_conflict_do_update(
590
+ ... index_elements=["id"], set_=dict(data="updated value")
591
+ ... )
592
+
593
+ >>> print(do_update_stmt)
594
+ {printsql}INSERT INTO my_table (id, data) VALUES (?, ?)
595
+ ON CONFLICT (id) DO UPDATE SET data = ?{stop}
596
+
597
+ >>> do_nothing_stmt = insert_stmt.on_conflict_do_nothing(index_elements=["id"])
598
+
599
+ >>> print(do_nothing_stmt)
600
+ {printsql}INSERT INTO my_table (id, data) VALUES (?, ?)
601
+ ON CONFLICT (id) DO NOTHING
602
+
603
+ .. versionadded:: 1.4
604
+
605
+ .. seealso::
606
+
607
+ `Upsert
608
+ <https://sqlite.org/lang_UPSERT.html>`_
609
+ - in the SQLite documentation.
610
+
611
+
612
+ Specifying the Target
613
+ ^^^^^^^^^^^^^^^^^^^^^
614
+
615
+ Both methods supply the "target" of the conflict using column inference:
616
+
617
+ * The :paramref:`_sqlite.Insert.on_conflict_do_update.index_elements` argument
618
+ specifies a sequence containing string column names, :class:`_schema.Column`
619
+ objects, and/or SQL expression elements, which would identify a unique index
620
+ or unique constraint.
621
+
622
+ * When using :paramref:`_sqlite.Insert.on_conflict_do_update.index_elements`
623
+ to infer an index, a partial index can be inferred by also specifying the
624
+ :paramref:`_sqlite.Insert.on_conflict_do_update.index_where` parameter:
625
+
626
+ .. sourcecode:: pycon+sql
627
+
628
+ >>> stmt = insert(my_table).values(user_email="a@b.com", data="inserted data")
629
+
630
+ >>> do_update_stmt = stmt.on_conflict_do_update(
631
+ ... index_elements=[my_table.c.user_email],
632
+ ... index_where=my_table.c.user_email.like("%@gmail.com"),
633
+ ... set_=dict(data=stmt.excluded.data),
634
+ ... )
635
+
636
+ >>> print(do_update_stmt)
637
+ {printsql}INSERT INTO my_table (data, user_email) VALUES (?, ?)
638
+ ON CONFLICT (user_email)
639
+ WHERE user_email LIKE '%@gmail.com'
640
+ DO UPDATE SET data = excluded.data
641
+
642
+ The SET Clause
643
+ ^^^^^^^^^^^^^^^
644
+
645
+ ``ON CONFLICT...DO UPDATE`` is used to perform an update of the already
646
+ existing row, using any combination of new values as well as values
647
+ from the proposed insertion. These values are specified using the
648
+ :paramref:`_sqlite.Insert.on_conflict_do_update.set_` parameter. This
649
+ parameter accepts a dictionary which consists of direct values
650
+ for UPDATE:
651
+
652
+ .. sourcecode:: pycon+sql
653
+
654
+ >>> stmt = insert(my_table).values(id="some_id", data="inserted value")
655
+
656
+ >>> do_update_stmt = stmt.on_conflict_do_update(
657
+ ... index_elements=["id"], set_=dict(data="updated value")
658
+ ... )
659
+
660
+ >>> print(do_update_stmt)
661
+ {printsql}INSERT INTO my_table (id, data) VALUES (?, ?)
662
+ ON CONFLICT (id) DO UPDATE SET data = ?
663
+
664
+ .. warning::
665
+
666
+ The :meth:`_sqlite.Insert.on_conflict_do_update` method does **not** take
667
+ into account Python-side default UPDATE values or generation functions,
668
+ e.g. those specified using :paramref:`_schema.Column.onupdate`. These
669
+ values will not be exercised for an ON CONFLICT style of UPDATE, unless
670
+ they are manually specified in the
671
+ :paramref:`_sqlite.Insert.on_conflict_do_update.set_` dictionary.
672
+
673
+ Updating using the Excluded INSERT Values
674
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
675
+
676
+ In order to refer to the proposed insertion row, the special alias
677
+ :attr:`~.sqlite.Insert.excluded` is available as an attribute on
678
+ the :class:`_sqlite.Insert` object; this object creates an "excluded." prefix
679
+ on a column, that informs the DO UPDATE to update the row with the value that
680
+ would have been inserted had the constraint not failed:
681
+
682
+ .. sourcecode:: pycon+sql
683
+
684
+ >>> stmt = insert(my_table).values(
685
+ ... id="some_id", data="inserted value", author="jlh"
686
+ ... )
687
+
688
+ >>> do_update_stmt = stmt.on_conflict_do_update(
689
+ ... index_elements=["id"],
690
+ ... set_=dict(data="updated value", author=stmt.excluded.author),
691
+ ... )
692
+
693
+ >>> print(do_update_stmt)
694
+ {printsql}INSERT INTO my_table (id, data, author) VALUES (?, ?, ?)
695
+ ON CONFLICT (id) DO UPDATE SET data = ?, author = excluded.author
696
+
697
+ Additional WHERE Criteria
698
+ ^^^^^^^^^^^^^^^^^^^^^^^^^
699
+
700
+ The :meth:`_sqlite.Insert.on_conflict_do_update` method also accepts
701
+ a WHERE clause using the :paramref:`_sqlite.Insert.on_conflict_do_update.where`
702
+ parameter, which will limit those rows which receive an UPDATE:
703
+
704
+ .. sourcecode:: pycon+sql
705
+
706
+ >>> stmt = insert(my_table).values(
707
+ ... id="some_id", data="inserted value", author="jlh"
708
+ ... )
709
+
710
+ >>> on_update_stmt = stmt.on_conflict_do_update(
711
+ ... index_elements=["id"],
712
+ ... set_=dict(data="updated value", author=stmt.excluded.author),
713
+ ... where=(my_table.c.status == 2),
714
+ ... )
715
+ >>> print(on_update_stmt)
716
+ {printsql}INSERT INTO my_table (id, data, author) VALUES (?, ?, ?)
717
+ ON CONFLICT (id) DO UPDATE SET data = ?, author = excluded.author
718
+ WHERE my_table.status = ?
719
+
720
+
721
+ Skipping Rows with DO NOTHING
722
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
723
+
724
+ ``ON CONFLICT`` may be used to skip inserting a row entirely
725
+ if any conflict with a unique constraint occurs; below this is illustrated
726
+ using the :meth:`_sqlite.Insert.on_conflict_do_nothing` method:
727
+
728
+ .. sourcecode:: pycon+sql
729
+
730
+ >>> stmt = insert(my_table).values(id="some_id", data="inserted value")
731
+ >>> stmt = stmt.on_conflict_do_nothing(index_elements=["id"])
732
+ >>> print(stmt)
733
+ {printsql}INSERT INTO my_table (id, data) VALUES (?, ?) ON CONFLICT (id) DO NOTHING
734
+
735
+
736
+ If ``DO NOTHING`` is used without specifying any columns or constraint,
737
+ it has the effect of skipping the INSERT for any unique violation which
738
+ occurs:
739
+
740
+ .. sourcecode:: pycon+sql
741
+
742
+ >>> stmt = insert(my_table).values(id="some_id", data="inserted value")
743
+ >>> stmt = stmt.on_conflict_do_nothing()
744
+ >>> print(stmt)
745
+ {printsql}INSERT INTO my_table (id, data) VALUES (?, ?) ON CONFLICT DO NOTHING
746
+
747
+ .. _sqlite_type_reflection:
748
+
749
+ Type Reflection
750
+ ---------------
751
+
752
+ SQLite types are unlike those of most other database backends, in that
753
+ the string name of the type usually does not correspond to a "type" in a
754
+ one-to-one fashion. Instead, SQLite links per-column typing behavior
755
+ to one of five so-called "type affinities" based on a string matching
756
+ pattern for the type.
757
+
758
+ SQLAlchemy's reflection process, when inspecting types, uses a simple
759
+ lookup table to link the keywords returned to provided SQLAlchemy types.
760
+ This lookup table is present within the SQLite dialect as it is for all
761
+ other dialects. However, the SQLite dialect has a different "fallback"
762
+ routine for when a particular type name is not located in the lookup map;
763
+ it instead implements the SQLite "type affinity" scheme located at
764
+ https://www.sqlite.org/datatype3.html section 2.1.
765
+
766
+ The provided typemap will make direct associations from an exact string
767
+ name match for the following types:
768
+
769
+ :class:`_types.BIGINT`, :class:`_types.BLOB`,
770
+ :class:`_types.BOOLEAN`, :class:`_types.BOOLEAN`,
771
+ :class:`_types.CHAR`, :class:`_types.DATE`,
772
+ :class:`_types.DATETIME`, :class:`_types.FLOAT`,
773
+ :class:`_types.DECIMAL`, :class:`_types.FLOAT`,
774
+ :class:`_types.INTEGER`, :class:`_types.INTEGER`,
775
+ :class:`_types.NUMERIC`, :class:`_types.REAL`,
776
+ :class:`_types.SMALLINT`, :class:`_types.TEXT`,
777
+ :class:`_types.TIME`, :class:`_types.TIMESTAMP`,
778
+ :class:`_types.VARCHAR`, :class:`_types.NVARCHAR`,
779
+ :class:`_types.NCHAR`
780
+
781
+ When a type name does not match one of the above types, the "type affinity"
782
+ lookup is used instead:
783
+
784
+ * :class:`_types.INTEGER` is returned if the type name includes the
785
+ string ``INT``
786
+ * :class:`_types.TEXT` is returned if the type name includes the
787
+ string ``CHAR``, ``CLOB`` or ``TEXT``
788
+ * :class:`_types.NullType` is returned if the type name includes the
789
+ string ``BLOB``
790
+ * :class:`_types.REAL` is returned if the type name includes the string
791
+ ``REAL``, ``FLOA`` or ``DOUB``.
792
+ * Otherwise, the :class:`_types.NUMERIC` type is used.
793
+
794
+ .. _sqlite_partial_index:
795
+
796
+ Partial Indexes
797
+ ---------------
798
+
799
+ A partial index, e.g. one which uses a WHERE clause, can be specified
800
+ with the DDL system using the argument ``sqlite_where``::
801
+
802
+ tbl = Table("testtbl", m, Column("data", Integer))
803
+ idx = Index(
804
+ "test_idx1",
805
+ tbl.c.data,
806
+ sqlite_where=and_(tbl.c.data > 5, tbl.c.data < 10),
807
+ )
808
+
809
+ The index will be rendered at create time as:
810
+
811
+ .. sourcecode:: sql
812
+
813
+ CREATE INDEX test_idx1 ON testtbl (data)
814
+ WHERE data > 5 AND data < 10
815
+
816
+ .. _sqlite_dotted_column_names:
817
+
818
+ Dotted Column Names
819
+ -------------------
820
+
821
+ Using table or column names that explicitly have periods in them is
822
+ **not recommended**. While this is generally a bad idea for relational
823
+ databases in general, as the dot is a syntactically significant character,
824
+ the SQLite driver up until version **3.10.0** of SQLite has a bug which
825
+ requires that SQLAlchemy filter out these dots in result sets.
826
+
827
+ The bug, entirely outside of SQLAlchemy, can be illustrated thusly::
828
+
829
+ import sqlite3
830
+
831
+ assert sqlite3.sqlite_version_info < (
832
+ 3,
833
+ 10,
834
+ 0,
835
+ ), "bug is fixed in this version"
836
+
837
+ conn = sqlite3.connect(":memory:")
838
+ cursor = conn.cursor()
839
+
840
+ cursor.execute("create table x (a integer, b integer)")
841
+ cursor.execute("insert into x (a, b) values (1, 1)")
842
+ cursor.execute("insert into x (a, b) values (2, 2)")
843
+
844
+ cursor.execute("select x.a, x.b from x")
845
+ assert [c[0] for c in cursor.description] == ["a", "b"]
846
+
847
+ cursor.execute(
848
+ """
849
+ select x.a, x.b from x where a=1
850
+ union
851
+ select x.a, x.b from x where a=2
852
+ """
853
+ )
854
+ assert [c[0] for c in cursor.description] == ["a", "b"], [
855
+ c[0] for c in cursor.description
856
+ ]
857
+
858
+ The second assertion fails:
859
+
860
+ .. sourcecode:: text
861
+
862
+ Traceback (most recent call last):
863
+ File "test.py", line 19, in <module>
864
+ [c[0] for c in cursor.description]
865
+ AssertionError: ['x.a', 'x.b']
866
+
867
+ Where above, the driver incorrectly reports the names of the columns
868
+ including the name of the table, which is entirely inconsistent vs.
869
+ when the UNION is not present.
870
+
871
+ SQLAlchemy relies upon column names being predictable in how they match
872
+ to the original statement, so the SQLAlchemy dialect has no choice but
873
+ to filter these out::
874
+
875
+
876
+ from sqlalchemy import create_engine
877
+
878
+ eng = create_engine("sqlite://")
879
+ conn = eng.connect()
880
+
881
+ conn.exec_driver_sql("create table x (a integer, b integer)")
882
+ conn.exec_driver_sql("insert into x (a, b) values (1, 1)")
883
+ conn.exec_driver_sql("insert into x (a, b) values (2, 2)")
884
+
885
+ result = conn.exec_driver_sql("select x.a, x.b from x")
886
+ assert result.keys() == ["a", "b"]
887
+
888
+ result = conn.exec_driver_sql(
889
+ """
890
+ select x.a, x.b from x where a=1
891
+ union
892
+ select x.a, x.b from x where a=2
893
+ """
894
+ )
895
+ assert result.keys() == ["a", "b"]
896
+
897
+ Note that above, even though SQLAlchemy filters out the dots, *both
898
+ names are still addressable*::
899
+
900
+ >>> row = result.first()
901
+ >>> row["a"]
902
+ 1
903
+ >>> row["x.a"]
904
+ 1
905
+ >>> row["b"]
906
+ 1
907
+ >>> row["x.b"]
908
+ 1
909
+
910
+ Therefore, the workaround applied by SQLAlchemy only impacts
911
+ :meth:`_engine.CursorResult.keys` and :meth:`.Row.keys()` in the public API. In
912
+ the very specific case where an application is forced to use column names that
913
+ contain dots, and the functionality of :meth:`_engine.CursorResult.keys` and
914
+ :meth:`.Row.keys()` is required to return these dotted names unmodified,
915
+ the ``sqlite_raw_colnames`` execution option may be provided, either on a
916
+ per-:class:`_engine.Connection` basis::
917
+
918
+ result = conn.execution_options(sqlite_raw_colnames=True).exec_driver_sql(
919
+ """
920
+ select x.a, x.b from x where a=1
921
+ union
922
+ select x.a, x.b from x where a=2
923
+ """
924
+ )
925
+ assert result.keys() == ["x.a", "x.b"]
926
+
927
+ or on a per-:class:`_engine.Engine` basis::
928
+
929
+ engine = create_engine(
930
+ "sqlite://", execution_options={"sqlite_raw_colnames": True}
931
+ )
932
+
933
+ When using the per-:class:`_engine.Engine` execution option, note that
934
+ **Core and ORM queries that use UNION may not function properly**.
935
+
936
+ SQLite-specific table options
937
+ -----------------------------
938
+
939
+ One option for CREATE TABLE is supported directly by the SQLite
940
+ dialect in conjunction with the :class:`_schema.Table` construct:
941
+
942
+ * ``WITHOUT ROWID``::
943
+
944
+ Table("some_table", metadata, ..., sqlite_with_rowid=False)
945
+
946
+ *
947
+ ``STRICT``::
948
+
949
+ Table("some_table", metadata, ..., sqlite_strict=True)
950
+
951
+ .. versionadded:: 2.0.37
952
+
953
+ .. seealso::
954
+
955
+ `SQLite CREATE TABLE options
956
+ <https://www.sqlite.org/lang_createtable.html>`_
957
+
958
+ .. _sqlite_include_internal:
959
+
960
+ Reflecting internal schema tables
961
+ ----------------------------------
962
+
963
+ Reflection methods that return lists of tables will omit so-called
964
+ "SQLite internal schema object" names, which are considered by SQLite
965
+ as any object name that is prefixed with ``sqlite_``. An example of
966
+ such an object is the ``sqlite_sequence`` table that's generated when
967
+ the ``AUTOINCREMENT`` column parameter is used. In order to return
968
+ these objects, the parameter ``sqlite_include_internal=True`` may be
969
+ passed to methods such as :meth:`_schema.MetaData.reflect` or
970
+ :meth:`.Inspector.get_table_names`.
971
+
972
+ .. versionadded:: 2.0 Added the ``sqlite_include_internal=True`` parameter.
973
+ Previously, these tables were not ignored by SQLAlchemy reflection
974
+ methods.
975
+
976
+ .. note::
977
+
978
+ The ``sqlite_include_internal`` parameter does not refer to the
979
+ "system" tables that are present in schemas such as ``sqlite_master``.
980
+
981
+ .. seealso::
982
+
983
+ `SQLite Internal Schema Objects <https://www.sqlite.org/fileformat2.html#intschema>`_ - in the SQLite
984
+ documentation.
985
+
986
+ ''' # noqa
987
+ from __future__ import annotations
988
+
989
+ import datetime
990
+ import numbers
991
+ import re
992
+ from typing import Any
993
+ from typing import Callable
994
+ from typing import Optional
995
+ from typing import TYPE_CHECKING
996
+
997
+ from .json import JSON
998
+ from .json import JSONIndexType
999
+ from .json import JSONPathType
1000
+ from ... import exc
1001
+ from ... import schema as sa_schema
1002
+ from ... import sql
1003
+ from ... import text
1004
+ from ... import types as sqltypes
1005
+ from ... import util
1006
+ from ...engine import default
1007
+ from ...engine import processors
1008
+ from ...engine import reflection
1009
+ from ...engine.reflection import ReflectionDefaults
1010
+ from ...sql import coercions
1011
+ from ...sql import compiler
1012
+ from ...sql import ddl as sa_ddl
1013
+ from ...sql import elements
1014
+ from ...sql import roles
1015
+ from ...sql import schema
1016
+ from ...types import BLOB # noqa
1017
+ from ...types import BOOLEAN # noqa
1018
+ from ...types import CHAR # noqa
1019
+ from ...types import DECIMAL # noqa
1020
+ from ...types import FLOAT # noqa
1021
+ from ...types import INTEGER # noqa
1022
+ from ...types import NUMERIC # noqa
1023
+ from ...types import REAL # noqa
1024
+ from ...types import SMALLINT # noqa
1025
+ from ...types import TEXT # noqa
1026
+ from ...types import TIMESTAMP # noqa
1027
+ from ...types import VARCHAR # noqa
1028
+
1029
+ if TYPE_CHECKING:
1030
+ from ...engine.interfaces import DBAPIConnection
1031
+ from ...engine.interfaces import Dialect
1032
+ from ...engine.interfaces import IsolationLevel
1033
+ from ...sql.sqltypes import _JSON_VALUE
1034
+ from ...sql.type_api import _BindProcessorType
1035
+ from ...sql.type_api import _ResultProcessorType
1036
+
1037
+
1038
+ class _SQliteJson(JSON):
1039
+ def result_processor(self, dialect, coltype):
1040
+ default_processor = super().result_processor(dialect, coltype)
1041
+
1042
+ def process(value):
1043
+ try:
1044
+ return default_processor(value)
1045
+ except TypeError:
1046
+ if isinstance(value, numbers.Number):
1047
+ return value
1048
+ else:
1049
+ raise
1050
+
1051
+ return process
1052
+
1053
+
1054
+ class _DateTimeMixin:
1055
+ _reg = None
1056
+ _storage_format = None
1057
+
1058
+ def __init__(self, storage_format=None, regexp=None, **kw):
1059
+ super().__init__(**kw)
1060
+ if regexp is not None:
1061
+ self._reg = re.compile(regexp)
1062
+ if storage_format is not None:
1063
+ self._storage_format = storage_format
1064
+
1065
+ @property
1066
+ def format_is_text_affinity(self):
1067
+ """return True if the storage format will automatically imply
1068
+ a TEXT affinity.
1069
+
1070
+ If the storage format contains no non-numeric characters,
1071
+ it will imply a NUMERIC storage format on SQLite; in this case,
1072
+ the type will generate its DDL as DATE_CHAR, DATETIME_CHAR,
1073
+ TIME_CHAR.
1074
+
1075
+ """
1076
+ spec = self._storage_format % {
1077
+ "year": 0,
1078
+ "month": 0,
1079
+ "day": 0,
1080
+ "hour": 0,
1081
+ "minute": 0,
1082
+ "second": 0,
1083
+ "microsecond": 0,
1084
+ }
1085
+ return bool(re.search(r"[^0-9]", spec))
1086
+
1087
+ def adapt(self, cls, **kw):
1088
+ if issubclass(cls, _DateTimeMixin):
1089
+ if self._storage_format:
1090
+ kw["storage_format"] = self._storage_format
1091
+ if self._reg:
1092
+ kw["regexp"] = self._reg
1093
+ return super().adapt(cls, **kw)
1094
+
1095
+ def literal_processor(self, dialect):
1096
+ bp = self.bind_processor(dialect)
1097
+
1098
+ def process(value):
1099
+ return "'%s'" % bp(value)
1100
+
1101
+ return process
1102
+
1103
+
1104
+ class DATETIME(_DateTimeMixin, sqltypes.DateTime):
1105
+ r"""Represent a Python datetime object in SQLite using a string.
1106
+
1107
+ The default string storage format is::
1108
+
1109
+ "%(year)04d-%(month)02d-%(day)02d %(hour)02d:%(minute)02d:%(second)02d.%(microsecond)06d"
1110
+
1111
+ e.g.:
1112
+
1113
+ .. sourcecode:: text
1114
+
1115
+ 2021-03-15 12:05:57.105542
1116
+
1117
+ The incoming storage format is by default parsed using the
1118
+ Python ``datetime.fromisoformat()`` function.
1119
+
1120
+ .. versionchanged:: 2.0 ``datetime.fromisoformat()`` is used for default
1121
+ datetime string parsing.
1122
+
1123
+ The storage format can be customized to some degree using the
1124
+ ``storage_format`` and ``regexp`` parameters, such as::
1125
+
1126
+ import re
1127
+ from sqlalchemy.dialects.sqlite import DATETIME
1128
+
1129
+ dt = DATETIME(
1130
+ storage_format=(
1131
+ "%(year)04d/%(month)02d/%(day)02d %(hour)02d:%(minute)02d:%(second)02d"
1132
+ ),
1133
+ regexp=r"(\d+)/(\d+)/(\d+) (\d+)-(\d+)-(\d+)",
1134
+ )
1135
+
1136
+ :param truncate_microseconds: when ``True`` microseconds will be truncated
1137
+ from the datetime. Can't be specified together with ``storage_format``
1138
+ or ``regexp``.
1139
+
1140
+ :param storage_format: format string which will be applied to the dict
1141
+ with keys year, month, day, hour, minute, second, and microsecond.
1142
+
1143
+ :param regexp: regular expression which will be applied to incoming result
1144
+ rows, replacing the use of ``datetime.fromisoformat()`` to parse incoming
1145
+ strings. If the regexp contains named groups, the resulting match dict is
1146
+ applied to the Python datetime() constructor as keyword arguments.
1147
+ Otherwise, if positional groups are used, the datetime() constructor
1148
+ is called with positional arguments via
1149
+ ``*map(int, match_obj.groups(0))``.
1150
+
1151
+ """ # noqa
1152
+
1153
+ _storage_format = (
1154
+ "%(year)04d-%(month)02d-%(day)02d "
1155
+ "%(hour)02d:%(minute)02d:%(second)02d.%(microsecond)06d"
1156
+ )
1157
+
1158
+ def __init__(self, *args, **kwargs):
1159
+ truncate_microseconds = kwargs.pop("truncate_microseconds", False)
1160
+ super().__init__(*args, **kwargs)
1161
+ if truncate_microseconds:
1162
+ assert "storage_format" not in kwargs, (
1163
+ "You can specify only "
1164
+ "one of truncate_microseconds or storage_format."
1165
+ )
1166
+ assert "regexp" not in kwargs, (
1167
+ "You can specify only one of "
1168
+ "truncate_microseconds or regexp."
1169
+ )
1170
+ self._storage_format = (
1171
+ "%(year)04d-%(month)02d-%(day)02d "
1172
+ "%(hour)02d:%(minute)02d:%(second)02d"
1173
+ )
1174
+
1175
+ def bind_processor(
1176
+ self, dialect: Dialect
1177
+ ) -> Optional[_BindProcessorType[Any]]:
1178
+ datetime_datetime = datetime.datetime
1179
+ datetime_date = datetime.date
1180
+ format_ = self._storage_format
1181
+
1182
+ def process(value):
1183
+ if value is None:
1184
+ return None
1185
+ elif isinstance(value, datetime_datetime):
1186
+ return format_ % {
1187
+ "year": value.year,
1188
+ "month": value.month,
1189
+ "day": value.day,
1190
+ "hour": value.hour,
1191
+ "minute": value.minute,
1192
+ "second": value.second,
1193
+ "microsecond": value.microsecond,
1194
+ }
1195
+ elif isinstance(value, datetime_date):
1196
+ return format_ % {
1197
+ "year": value.year,
1198
+ "month": value.month,
1199
+ "day": value.day,
1200
+ "hour": 0,
1201
+ "minute": 0,
1202
+ "second": 0,
1203
+ "microsecond": 0,
1204
+ }
1205
+ else:
1206
+ raise TypeError(
1207
+ "SQLite DateTime type only accepts Python "
1208
+ "datetime and date objects as input."
1209
+ )
1210
+
1211
+ return process
1212
+
1213
+ def result_processor(
1214
+ self, dialect: Dialect, coltype: object
1215
+ ) -> Optional[_ResultProcessorType[Any]]:
1216
+ if self._reg:
1217
+ return processors.str_to_datetime_processor_factory(
1218
+ self._reg, datetime.datetime
1219
+ )
1220
+ else:
1221
+ return processors.str_to_datetime
1222
+
1223
+
1224
+ class DATE(_DateTimeMixin, sqltypes.Date):
1225
+ r"""Represent a Python date object in SQLite using a string.
1226
+
1227
+ The default string storage format is::
1228
+
1229
+ "%(year)04d-%(month)02d-%(day)02d"
1230
+
1231
+ e.g.:
1232
+
1233
+ .. sourcecode:: text
1234
+
1235
+ 2011-03-15
1236
+
1237
+ The incoming storage format is by default parsed using the
1238
+ Python ``date.fromisoformat()`` function.
1239
+
1240
+ .. versionchanged:: 2.0 ``date.fromisoformat()`` is used for default
1241
+ date string parsing.
1242
+
1243
+
1244
+ The storage format can be customized to some degree using the
1245
+ ``storage_format`` and ``regexp`` parameters, such as::
1246
+
1247
+ import re
1248
+ from sqlalchemy.dialects.sqlite import DATE
1249
+
1250
+ d = DATE(
1251
+ storage_format="%(month)02d/%(day)02d/%(year)04d",
1252
+ regexp=re.compile("(?P<month>\d+)/(?P<day>\d+)/(?P<year>\d+)"),
1253
+ )
1254
+
1255
+ :param storage_format: format string which will be applied to the
1256
+ dict with keys year, month, and day.
1257
+
1258
+ :param regexp: regular expression which will be applied to
1259
+ incoming result rows, replacing the use of ``date.fromisoformat()`` to
1260
+ parse incoming strings. If the regexp contains named groups, the resulting
1261
+ match dict is applied to the Python date() constructor as keyword
1262
+ arguments. Otherwise, if positional groups are used, the date()
1263
+ constructor is called with positional arguments via
1264
+ ``*map(int, match_obj.groups(0))``.
1265
+
1266
+ """
1267
+
1268
+ _storage_format = "%(year)04d-%(month)02d-%(day)02d"
1269
+
1270
+ def bind_processor(
1271
+ self, dialect: Dialect
1272
+ ) -> Optional[_BindProcessorType[Any]]:
1273
+ datetime_date = datetime.date
1274
+ format_ = self._storage_format
1275
+
1276
+ def process(value):
1277
+ if value is None:
1278
+ return None
1279
+ elif isinstance(value, datetime_date):
1280
+ return format_ % {
1281
+ "year": value.year,
1282
+ "month": value.month,
1283
+ "day": value.day,
1284
+ }
1285
+ else:
1286
+ raise TypeError(
1287
+ "SQLite Date type only accepts Python "
1288
+ "date objects as input."
1289
+ )
1290
+
1291
+ return process
1292
+
1293
+ def result_processor(
1294
+ self, dialect: Dialect, coltype: object
1295
+ ) -> Optional[_ResultProcessorType[Any]]:
1296
+ if self._reg:
1297
+ return processors.str_to_datetime_processor_factory(
1298
+ self._reg, datetime.date
1299
+ )
1300
+ else:
1301
+ return processors.str_to_date
1302
+
1303
+
1304
+ class TIME(_DateTimeMixin, sqltypes.Time):
1305
+ r"""Represent a Python time object in SQLite using a string.
1306
+
1307
+ The default string storage format is::
1308
+
1309
+ "%(hour)02d:%(minute)02d:%(second)02d.%(microsecond)06d"
1310
+
1311
+ e.g.:
1312
+
1313
+ .. sourcecode:: text
1314
+
1315
+ 12:05:57.10558
1316
+
1317
+ The incoming storage format is by default parsed using the
1318
+ Python ``time.fromisoformat()`` function.
1319
+
1320
+ .. versionchanged:: 2.0 ``time.fromisoformat()`` is used for default
1321
+ time string parsing.
1322
+
1323
+ The storage format can be customized to some degree using the
1324
+ ``storage_format`` and ``regexp`` parameters, such as::
1325
+
1326
+ import re
1327
+ from sqlalchemy.dialects.sqlite import TIME
1328
+
1329
+ t = TIME(
1330
+ storage_format="%(hour)02d-%(minute)02d-%(second)02d-%(microsecond)06d",
1331
+ regexp=re.compile("(\d+)-(\d+)-(\d+)-(?:-(\d+))?"),
1332
+ )
1333
+
1334
+ :param truncate_microseconds: when ``True`` microseconds will be truncated
1335
+ from the time. Can't be specified together with ``storage_format``
1336
+ or ``regexp``.
1337
+
1338
+ :param storage_format: format string which will be applied to the dict
1339
+ with keys hour, minute, second, and microsecond.
1340
+
1341
+ :param regexp: regular expression which will be applied to incoming result
1342
+ rows, replacing the use of ``datetime.fromisoformat()`` to parse incoming
1343
+ strings. If the regexp contains named groups, the resulting match dict is
1344
+ applied to the Python time() constructor as keyword arguments. Otherwise,
1345
+ if positional groups are used, the time() constructor is called with
1346
+ positional arguments via ``*map(int, match_obj.groups(0))``.
1347
+
1348
+ """
1349
+
1350
+ _storage_format = "%(hour)02d:%(minute)02d:%(second)02d.%(microsecond)06d"
1351
+
1352
+ def __init__(self, *args, **kwargs):
1353
+ truncate_microseconds = kwargs.pop("truncate_microseconds", False)
1354
+ super().__init__(*args, **kwargs)
1355
+ if truncate_microseconds:
1356
+ assert "storage_format" not in kwargs, (
1357
+ "You can specify only "
1358
+ "one of truncate_microseconds or storage_format."
1359
+ )
1360
+ assert "regexp" not in kwargs, (
1361
+ "You can specify only one of "
1362
+ "truncate_microseconds or regexp."
1363
+ )
1364
+ self._storage_format = "%(hour)02d:%(minute)02d:%(second)02d"
1365
+
1366
+ def bind_processor(self, dialect):
1367
+ datetime_time = datetime.time
1368
+ format_ = self._storage_format
1369
+
1370
+ def process(value):
1371
+ if value is None:
1372
+ return None
1373
+ elif isinstance(value, datetime_time):
1374
+ return format_ % {
1375
+ "hour": value.hour,
1376
+ "minute": value.minute,
1377
+ "second": value.second,
1378
+ "microsecond": value.microsecond,
1379
+ }
1380
+ else:
1381
+ raise TypeError(
1382
+ "SQLite Time type only accepts Python "
1383
+ "time objects as input."
1384
+ )
1385
+
1386
+ return process
1387
+
1388
+ def result_processor(self, dialect, coltype):
1389
+ if self._reg:
1390
+ return processors.str_to_datetime_processor_factory(
1391
+ self._reg, datetime.time
1392
+ )
1393
+ else:
1394
+ return processors.str_to_time
1395
+
1396
+
1397
+ colspecs = {
1398
+ sqltypes.Date: DATE,
1399
+ sqltypes.DateTime: DATETIME,
1400
+ sqltypes.JSON: _SQliteJson,
1401
+ sqltypes.JSON.JSONIndexType: JSONIndexType,
1402
+ sqltypes.JSON.JSONPathType: JSONPathType,
1403
+ sqltypes.Time: TIME,
1404
+ }
1405
+
1406
+ ischema_names = {
1407
+ "BIGINT": sqltypes.BIGINT,
1408
+ "BLOB": sqltypes.BLOB,
1409
+ "BOOL": sqltypes.BOOLEAN,
1410
+ "BOOLEAN": sqltypes.BOOLEAN,
1411
+ "CHAR": sqltypes.CHAR,
1412
+ "DATE": sqltypes.DATE,
1413
+ "DATE_CHAR": sqltypes.DATE,
1414
+ "DATETIME": sqltypes.DATETIME,
1415
+ "DATETIME_CHAR": sqltypes.DATETIME,
1416
+ "DOUBLE": sqltypes.DOUBLE,
1417
+ "DECIMAL": sqltypes.DECIMAL,
1418
+ "FLOAT": sqltypes.FLOAT,
1419
+ "INT": sqltypes.INTEGER,
1420
+ "INTEGER": sqltypes.INTEGER,
1421
+ "JSON": JSON,
1422
+ "NUMERIC": sqltypes.NUMERIC,
1423
+ "REAL": sqltypes.REAL,
1424
+ "SMALLINT": sqltypes.SMALLINT,
1425
+ "TEXT": sqltypes.TEXT,
1426
+ "TIME": sqltypes.TIME,
1427
+ "TIME_CHAR": sqltypes.TIME,
1428
+ "TIMESTAMP": sqltypes.TIMESTAMP,
1429
+ "VARCHAR": sqltypes.VARCHAR,
1430
+ "NVARCHAR": sqltypes.NVARCHAR,
1431
+ "NCHAR": sqltypes.NCHAR,
1432
+ }
1433
+
1434
+
1435
+ class SQLiteCompiler(compiler.SQLCompiler):
1436
+ extract_map = util.update_copy(
1437
+ compiler.SQLCompiler.extract_map,
1438
+ {
1439
+ "month": "%m",
1440
+ "day": "%d",
1441
+ "year": "%Y",
1442
+ "second": "%S",
1443
+ "hour": "%H",
1444
+ "doy": "%j",
1445
+ "minute": "%M",
1446
+ "epoch": "%s",
1447
+ "dow": "%w",
1448
+ "week": "%W",
1449
+ },
1450
+ )
1451
+
1452
+ def visit_truediv_binary(self, binary, operator, **kw):
1453
+ return (
1454
+ self.process(binary.left, **kw)
1455
+ + " / "
1456
+ + "(%s + 0.0)" % self.process(binary.right, **kw)
1457
+ )
1458
+
1459
+ def visit_now_func(self, fn, **kw):
1460
+ return "CURRENT_TIMESTAMP"
1461
+
1462
+ def visit_localtimestamp_func(self, func, **kw):
1463
+ return "DATETIME(CURRENT_TIMESTAMP, 'localtime')"
1464
+
1465
+ def visit_true(self, expr, **kw):
1466
+ return "1"
1467
+
1468
+ def visit_false(self, expr, **kw):
1469
+ return "0"
1470
+
1471
+ def visit_char_length_func(self, fn, **kw):
1472
+ return "length%s" % self.function_argspec(fn)
1473
+
1474
+ def visit_aggregate_strings_func(self, fn, **kw):
1475
+ return super().visit_aggregate_strings_func(
1476
+ fn, use_function_name="group_concat", **kw
1477
+ )
1478
+
1479
+ def visit_cast(self, cast, **kwargs):
1480
+ if self.dialect.supports_cast:
1481
+ return super().visit_cast(cast, **kwargs)
1482
+ else:
1483
+ return self.process(cast.clause, **kwargs)
1484
+
1485
+ def visit_extract(self, extract, **kw):
1486
+ try:
1487
+ return "CAST(STRFTIME('%s', %s) AS INTEGER)" % (
1488
+ self.extract_map[extract.field],
1489
+ self.process(extract.expr, **kw),
1490
+ )
1491
+ except KeyError as err:
1492
+ raise exc.CompileError(
1493
+ "%s is not a valid extract argument." % extract.field
1494
+ ) from err
1495
+
1496
+ def returning_clause(
1497
+ self,
1498
+ stmt,
1499
+ returning_cols,
1500
+ *,
1501
+ populate_result_map,
1502
+ **kw,
1503
+ ):
1504
+ kw["include_table"] = False
1505
+ return super().returning_clause(
1506
+ stmt, returning_cols, populate_result_map=populate_result_map, **kw
1507
+ )
1508
+
1509
+ def limit_clause(self, select, **kw):
1510
+ text = ""
1511
+ if select._limit_clause is not None:
1512
+ text += "\n LIMIT " + self.process(select._limit_clause, **kw)
1513
+ if select._offset_clause is not None:
1514
+ if select._limit_clause is None:
1515
+ text += "\n LIMIT " + self.process(sql.literal(-1))
1516
+ text += " OFFSET " + self.process(select._offset_clause, **kw)
1517
+ else:
1518
+ text += " OFFSET " + self.process(sql.literal(0), **kw)
1519
+ return text
1520
+
1521
+ def for_update_clause(self, select, **kw):
1522
+ # sqlite has no "FOR UPDATE" AFAICT
1523
+ return ""
1524
+
1525
+ def update_from_clause(
1526
+ self, update_stmt, from_table, extra_froms, from_hints, **kw
1527
+ ):
1528
+ kw["asfrom"] = True
1529
+ return "FROM " + ", ".join(
1530
+ t._compiler_dispatch(self, fromhints=from_hints, **kw)
1531
+ for t in extra_froms
1532
+ )
1533
+
1534
+ def visit_is_distinct_from_binary(self, binary, operator, **kw):
1535
+ return "%s IS NOT %s" % (
1536
+ self.process(binary.left),
1537
+ self.process(binary.right),
1538
+ )
1539
+
1540
+ def visit_is_not_distinct_from_binary(self, binary, operator, **kw):
1541
+ return "%s IS %s" % (
1542
+ self.process(binary.left),
1543
+ self.process(binary.right),
1544
+ )
1545
+
1546
+ def visit_json_getitem_op_binary(
1547
+ self, binary, operator, _cast_applied=False, **kw
1548
+ ):
1549
+ if (
1550
+ not _cast_applied
1551
+ and binary.type._type_affinity is not sqltypes.JSON
1552
+ ):
1553
+ kw["_cast_applied"] = True
1554
+ return self.process(sql.cast(binary, binary.type), **kw)
1555
+
1556
+ if binary.type._type_affinity is sqltypes.JSON:
1557
+ expr = "JSON_QUOTE(JSON_EXTRACT(%s, %s))"
1558
+ else:
1559
+ expr = "JSON_EXTRACT(%s, %s)"
1560
+
1561
+ return expr % (
1562
+ self.process(binary.left, **kw),
1563
+ self.process(binary.right, **kw),
1564
+ )
1565
+
1566
+ def visit_json_path_getitem_op_binary(
1567
+ self, binary, operator, _cast_applied=False, **kw
1568
+ ):
1569
+ if (
1570
+ not _cast_applied
1571
+ and binary.type._type_affinity is not sqltypes.JSON
1572
+ ):
1573
+ kw["_cast_applied"] = True
1574
+ return self.process(sql.cast(binary, binary.type), **kw)
1575
+
1576
+ if binary.type._type_affinity is sqltypes.JSON:
1577
+ expr = "JSON_QUOTE(JSON_EXTRACT(%s, %s))"
1578
+ else:
1579
+ expr = "JSON_EXTRACT(%s, %s)"
1580
+
1581
+ return expr % (
1582
+ self.process(binary.left, **kw),
1583
+ self.process(binary.right, **kw),
1584
+ )
1585
+
1586
+ def visit_empty_set_op_expr(self, type_, expand_op, **kw):
1587
+ # slightly old SQLite versions don't seem to be able to handle
1588
+ # the empty set impl
1589
+ return self.visit_empty_set_expr(type_)
1590
+
1591
+ def visit_empty_set_expr(self, element_types, **kw):
1592
+ return "SELECT %s FROM (SELECT %s) WHERE 1!=1" % (
1593
+ ", ".join("1" for type_ in element_types or [INTEGER()]),
1594
+ ", ".join("1" for type_ in element_types or [INTEGER()]),
1595
+ )
1596
+
1597
+ def visit_regexp_match_op_binary(self, binary, operator, **kw):
1598
+ return self._generate_generic_binary(binary, " REGEXP ", **kw)
1599
+
1600
+ def visit_not_regexp_match_op_binary(self, binary, operator, **kw):
1601
+ return self._generate_generic_binary(binary, " NOT REGEXP ", **kw)
1602
+
1603
+ def _on_conflict_target(self, clause, **kw):
1604
+ if clause.inferred_target_elements is not None:
1605
+ target_text = "(%s)" % ", ".join(
1606
+ (
1607
+ self.preparer.quote(c)
1608
+ if isinstance(c, str)
1609
+ else self.process(c, include_table=False, use_schema=False)
1610
+ )
1611
+ for c in clause.inferred_target_elements
1612
+ )
1613
+ if clause.inferred_target_whereclause is not None:
1614
+ whereclause_kw = dict(kw)
1615
+ whereclause_kw.update(
1616
+ include_table=False,
1617
+ use_schema=False,
1618
+ literal_execute=True,
1619
+ )
1620
+ target_text += " WHERE %s" % self.process(
1621
+ clause.inferred_target_whereclause,
1622
+ **whereclause_kw,
1623
+ )
1624
+
1625
+ else:
1626
+ target_text = ""
1627
+
1628
+ return target_text
1629
+
1630
+ def visit_on_conflict_do_nothing(self, on_conflict, **kw):
1631
+ target_text = self._on_conflict_target(on_conflict, **kw)
1632
+
1633
+ if target_text:
1634
+ return "ON CONFLICT %s DO NOTHING" % target_text
1635
+ else:
1636
+ return "ON CONFLICT DO NOTHING"
1637
+
1638
+ def visit_on_conflict_do_update(self, on_conflict, **kw):
1639
+ clause = on_conflict
1640
+
1641
+ target_text = self._on_conflict_target(on_conflict, **kw)
1642
+
1643
+ action_set_ops = []
1644
+
1645
+ set_parameters = dict(clause.update_values_to_set)
1646
+ # create a list of column assignment clauses as tuples
1647
+
1648
+ insert_statement = self.stack[-1]["selectable"]
1649
+ cols = insert_statement.table.c
1650
+ set_kw = dict(kw)
1651
+ set_kw.update(use_schema=False)
1652
+ for c in cols:
1653
+ col_key = c.key
1654
+
1655
+ if col_key in set_parameters:
1656
+ value = set_parameters.pop(col_key)
1657
+ elif c in set_parameters:
1658
+ value = set_parameters.pop(c)
1659
+ else:
1660
+ continue
1661
+
1662
+ if (
1663
+ isinstance(value, elements.BindParameter)
1664
+ and value.type._isnull
1665
+ ):
1666
+ value = value._with_binary_element_type(c.type)
1667
+
1668
+ value_text = self.process(
1669
+ value.self_group(), is_upsert_set=True, **set_kw
1670
+ )
1671
+
1672
+ key_text = self.preparer.quote(c.name)
1673
+ action_set_ops.append("%s = %s" % (key_text, value_text))
1674
+
1675
+ # check for names that don't match columns
1676
+ if set_parameters:
1677
+ util.warn(
1678
+ "Additional column names not matching "
1679
+ "any column keys in table '%s': %s"
1680
+ % (
1681
+ self.current_executable.table.name,
1682
+ (", ".join("'%s'" % c for c in set_parameters)),
1683
+ )
1684
+ )
1685
+ for k, v in set_parameters.items():
1686
+ key_text = (
1687
+ self.preparer.quote(k)
1688
+ if isinstance(k, str)
1689
+ else self.process(k, **set_kw)
1690
+ )
1691
+ value_text = self.process(
1692
+ coercions.expect(roles.ExpressionElementRole, v),
1693
+ is_upsert_set=True,
1694
+ **set_kw,
1695
+ )
1696
+ action_set_ops.append("%s = %s" % (key_text, value_text))
1697
+
1698
+ action_text = ", ".join(action_set_ops)
1699
+ if clause.update_whereclause is not None:
1700
+ where_kw = dict(kw)
1701
+ where_kw.update(include_table=True, use_schema=False)
1702
+ action_text += " WHERE %s" % self.process(
1703
+ clause.update_whereclause, **where_kw
1704
+ )
1705
+
1706
+ return "ON CONFLICT %s DO UPDATE SET %s" % (target_text, action_text)
1707
+
1708
+ def visit_bitwise_xor_op_binary(self, binary, operator, **kw):
1709
+ # sqlite has no xor. Use "a XOR b" = "(a | b) - (a & b)".
1710
+ kw["eager_grouping"] = True
1711
+ or_ = self._generate_generic_binary(binary, " | ", **kw)
1712
+ and_ = self._generate_generic_binary(binary, " & ", **kw)
1713
+ return f"({or_} - {and_})"
1714
+
1715
+
1716
+ class SQLiteDDLCompiler(compiler.DDLCompiler):
1717
+ def get_column_specification(self, column, **kwargs):
1718
+ coltype = self.dialect.type_compiler_instance.process(
1719
+ column.type, type_expression=column
1720
+ )
1721
+ colspec = self.preparer.format_column(column) + " " + coltype
1722
+ default = self.get_column_default_string(column)
1723
+ if default is not None:
1724
+
1725
+ if not re.match(r"""^\s*[\'\"\(]""", default) and re.match(
1726
+ r".*\W.*", default
1727
+ ):
1728
+ colspec += f" DEFAULT ({default})"
1729
+ else:
1730
+ colspec += f" DEFAULT {default}"
1731
+
1732
+ if not column.nullable:
1733
+ colspec += " NOT NULL"
1734
+
1735
+ on_conflict_clause = column.dialect_options["sqlite"][
1736
+ "on_conflict_not_null"
1737
+ ]
1738
+ if on_conflict_clause is not None:
1739
+ colspec += " ON CONFLICT " + on_conflict_clause
1740
+
1741
+ if column.primary_key:
1742
+ if (
1743
+ column.autoincrement is True
1744
+ and len(column.table.primary_key.columns) != 1
1745
+ ):
1746
+ raise exc.CompileError(
1747
+ "SQLite does not support autoincrement for "
1748
+ "composite primary keys"
1749
+ )
1750
+
1751
+ if (
1752
+ column.table.dialect_options["sqlite"]["autoincrement"]
1753
+ and len(column.table.primary_key.columns) == 1
1754
+ and issubclass(column.type._type_affinity, sqltypes.Integer)
1755
+ and not column.foreign_keys
1756
+ ):
1757
+ colspec += " PRIMARY KEY"
1758
+
1759
+ on_conflict_clause = column.dialect_options["sqlite"][
1760
+ "on_conflict_primary_key"
1761
+ ]
1762
+ if on_conflict_clause is not None:
1763
+ colspec += " ON CONFLICT " + on_conflict_clause
1764
+
1765
+ colspec += " AUTOINCREMENT"
1766
+
1767
+ if column.computed is not None:
1768
+ colspec += " " + self.process(column.computed)
1769
+
1770
+ return colspec
1771
+
1772
+ def visit_primary_key_constraint(self, constraint, **kw):
1773
+ # for columns with sqlite_autoincrement=True,
1774
+ # the PRIMARY KEY constraint can only be inline
1775
+ # with the column itself.
1776
+ if len(constraint.columns) == 1:
1777
+ c = list(constraint)[0]
1778
+ if (
1779
+ c.primary_key
1780
+ and c.table.dialect_options["sqlite"]["autoincrement"]
1781
+ and issubclass(c.type._type_affinity, sqltypes.Integer)
1782
+ and not c.foreign_keys
1783
+ ):
1784
+ return None
1785
+
1786
+ text = super().visit_primary_key_constraint(constraint)
1787
+
1788
+ on_conflict_clause = constraint.dialect_options["sqlite"][
1789
+ "on_conflict"
1790
+ ]
1791
+ if on_conflict_clause is None and len(constraint.columns) == 1:
1792
+ on_conflict_clause = list(constraint)[0].dialect_options["sqlite"][
1793
+ "on_conflict_primary_key"
1794
+ ]
1795
+
1796
+ if on_conflict_clause is not None:
1797
+ text += " ON CONFLICT " + on_conflict_clause
1798
+
1799
+ return text
1800
+
1801
+ def visit_unique_constraint(self, constraint, **kw):
1802
+ text = super().visit_unique_constraint(constraint)
1803
+
1804
+ on_conflict_clause = constraint.dialect_options["sqlite"][
1805
+ "on_conflict"
1806
+ ]
1807
+ if on_conflict_clause is None and len(constraint.columns) == 1:
1808
+ col1 = list(constraint)[0]
1809
+ if isinstance(col1, schema.SchemaItem):
1810
+ on_conflict_clause = list(constraint)[0].dialect_options[
1811
+ "sqlite"
1812
+ ]["on_conflict_unique"]
1813
+
1814
+ if on_conflict_clause is not None:
1815
+ text += " ON CONFLICT " + on_conflict_clause
1816
+
1817
+ return text
1818
+
1819
+ def visit_check_constraint(self, constraint, **kw):
1820
+ text = super().visit_check_constraint(constraint)
1821
+
1822
+ on_conflict_clause = constraint.dialect_options["sqlite"][
1823
+ "on_conflict"
1824
+ ]
1825
+
1826
+ if on_conflict_clause is not None:
1827
+ text += " ON CONFLICT " + on_conflict_clause
1828
+
1829
+ return text
1830
+
1831
+ def visit_column_check_constraint(self, constraint, **kw):
1832
+ text = super().visit_column_check_constraint(constraint)
1833
+
1834
+ if constraint.dialect_options["sqlite"]["on_conflict"] is not None:
1835
+ raise exc.CompileError(
1836
+ "SQLite does not support on conflict clause for "
1837
+ "column check constraint"
1838
+ )
1839
+
1840
+ return text
1841
+
1842
+ def visit_foreign_key_constraint(self, constraint, **kw):
1843
+ local_table = constraint.elements[0].parent.table
1844
+ remote_table = constraint.elements[0].column.table
1845
+
1846
+ if local_table.schema != remote_table.schema:
1847
+ return None
1848
+ else:
1849
+ return super().visit_foreign_key_constraint(constraint)
1850
+
1851
+ def define_constraint_remote_table(self, constraint, table, preparer):
1852
+ """Format the remote table clause of a CREATE CONSTRAINT clause."""
1853
+
1854
+ return preparer.format_table(table, use_schema=False)
1855
+
1856
+ def visit_create_index(
1857
+ self, create, include_schema=False, include_table_schema=True, **kw
1858
+ ):
1859
+ index = create.element
1860
+ self._verify_index_table(index)
1861
+ preparer = self.preparer
1862
+ text = "CREATE "
1863
+ if index.unique:
1864
+ text += "UNIQUE "
1865
+
1866
+ text += "INDEX "
1867
+
1868
+ if create.if_not_exists:
1869
+ text += "IF NOT EXISTS "
1870
+
1871
+ text += "%s ON %s (%s)" % (
1872
+ self._prepared_index_name(index, include_schema=True),
1873
+ preparer.format_table(index.table, use_schema=False),
1874
+ ", ".join(
1875
+ self.sql_compiler.process(
1876
+ expr, include_table=False, literal_binds=True
1877
+ )
1878
+ for expr in index.expressions
1879
+ ),
1880
+ )
1881
+
1882
+ whereclause = index.dialect_options["sqlite"]["where"]
1883
+ if whereclause is not None:
1884
+ where_compiled = self.sql_compiler.process(
1885
+ whereclause, include_table=False, literal_binds=True
1886
+ )
1887
+ text += " WHERE " + where_compiled
1888
+
1889
+ return text
1890
+
1891
+ def post_create_table(self, table):
1892
+ table_options = []
1893
+
1894
+ if not table.dialect_options["sqlite"]["with_rowid"]:
1895
+ table_options.append("WITHOUT ROWID")
1896
+
1897
+ if table.dialect_options["sqlite"]["strict"]:
1898
+ table_options.append("STRICT")
1899
+
1900
+ if table_options:
1901
+ return "\n " + ",\n ".join(table_options)
1902
+ else:
1903
+ return ""
1904
+
1905
+ def visit_create_view(self, create, **kw):
1906
+ """Handle SQLite if_not_exists dialect option for CREATE VIEW."""
1907
+ # Get the if_not_exists dialect option from the CreateView object
1908
+ if_not_exists = create.dialect_options["sqlite"].get(
1909
+ "if_not_exists", False
1910
+ )
1911
+
1912
+ # Pass if_not_exists through kw to the parent's _generate_table_select
1913
+ kw["if_not_exists"] = if_not_exists
1914
+ return super().visit_create_view(create, **kw)
1915
+
1916
+
1917
+ class SQLiteTypeCompiler(compiler.GenericTypeCompiler):
1918
+ def visit_large_binary(self, type_, **kw):
1919
+ return self.visit_BLOB(type_)
1920
+
1921
+ def visit_DATETIME(self, type_, **kw):
1922
+ if (
1923
+ not isinstance(type_, _DateTimeMixin)
1924
+ or type_.format_is_text_affinity
1925
+ ):
1926
+ return super().visit_DATETIME(type_)
1927
+ else:
1928
+ return "DATETIME_CHAR"
1929
+
1930
+ def visit_DATE(self, type_, **kw):
1931
+ if (
1932
+ not isinstance(type_, _DateTimeMixin)
1933
+ or type_.format_is_text_affinity
1934
+ ):
1935
+ return super().visit_DATE(type_)
1936
+ else:
1937
+ return "DATE_CHAR"
1938
+
1939
+ def visit_TIME(self, type_, **kw):
1940
+ if (
1941
+ not isinstance(type_, _DateTimeMixin)
1942
+ or type_.format_is_text_affinity
1943
+ ):
1944
+ return super().visit_TIME(type_)
1945
+ else:
1946
+ return "TIME_CHAR"
1947
+
1948
+ def visit_JSON(self, type_, **kw):
1949
+ # note this name provides NUMERIC affinity, not TEXT.
1950
+ # should not be an issue unless the JSON value consists of a single
1951
+ # numeric value. JSONTEXT can be used if this case is required.
1952
+ return "JSON"
1953
+
1954
+
1955
+ class SQLiteIdentifierPreparer(compiler.IdentifierPreparer):
1956
+ reserved_words = {
1957
+ "add",
1958
+ "after",
1959
+ "all",
1960
+ "alter",
1961
+ "analyze",
1962
+ "and",
1963
+ "as",
1964
+ "asc",
1965
+ "attach",
1966
+ "autoincrement",
1967
+ "before",
1968
+ "begin",
1969
+ "between",
1970
+ "by",
1971
+ "cascade",
1972
+ "case",
1973
+ "cast",
1974
+ "check",
1975
+ "collate",
1976
+ "column",
1977
+ "commit",
1978
+ "conflict",
1979
+ "constraint",
1980
+ "create",
1981
+ "cross",
1982
+ "current_date",
1983
+ "current_time",
1984
+ "current_timestamp",
1985
+ "database",
1986
+ "default",
1987
+ "deferrable",
1988
+ "deferred",
1989
+ "delete",
1990
+ "desc",
1991
+ "detach",
1992
+ "distinct",
1993
+ "drop",
1994
+ "each",
1995
+ "else",
1996
+ "end",
1997
+ "escape",
1998
+ "except",
1999
+ "exclusive",
2000
+ "exists",
2001
+ "explain",
2002
+ "false",
2003
+ "fail",
2004
+ "for",
2005
+ "foreign",
2006
+ "from",
2007
+ "full",
2008
+ "glob",
2009
+ "group",
2010
+ "having",
2011
+ "if",
2012
+ "ignore",
2013
+ "immediate",
2014
+ "in",
2015
+ "index",
2016
+ "indexed",
2017
+ "initially",
2018
+ "inner",
2019
+ "insert",
2020
+ "instead",
2021
+ "intersect",
2022
+ "into",
2023
+ "is",
2024
+ "isnull",
2025
+ "join",
2026
+ "key",
2027
+ "left",
2028
+ "like",
2029
+ "limit",
2030
+ "match",
2031
+ "natural",
2032
+ "not",
2033
+ "notnull",
2034
+ "null",
2035
+ "of",
2036
+ "offset",
2037
+ "on",
2038
+ "or",
2039
+ "order",
2040
+ "outer",
2041
+ "plan",
2042
+ "pragma",
2043
+ "primary",
2044
+ "query",
2045
+ "raise",
2046
+ "references",
2047
+ "reindex",
2048
+ "rename",
2049
+ "replace",
2050
+ "restrict",
2051
+ "right",
2052
+ "rollback",
2053
+ "row",
2054
+ "select",
2055
+ "set",
2056
+ "table",
2057
+ "temp",
2058
+ "temporary",
2059
+ "then",
2060
+ "to",
2061
+ "transaction",
2062
+ "trigger",
2063
+ "true",
2064
+ "union",
2065
+ "unique",
2066
+ "update",
2067
+ "using",
2068
+ "vacuum",
2069
+ "values",
2070
+ "view",
2071
+ "virtual",
2072
+ "when",
2073
+ "where",
2074
+ }
2075
+
2076
+
2077
+ class SQLiteExecutionContext(default.DefaultExecutionContext):
2078
+ @util.memoized_property
2079
+ def _preserve_raw_colnames(self):
2080
+ return (
2081
+ not self.dialect._broken_dotted_colnames
2082
+ or self.execution_options.get("sqlite_raw_colnames", False)
2083
+ )
2084
+
2085
+ def _translate_colname(self, colname):
2086
+ # TODO: detect SQLite version 3.10.0 or greater;
2087
+ # see [ticket:3633]
2088
+
2089
+ # adjust for dotted column names. SQLite
2090
+ # in the case of UNION may store col names as
2091
+ # "tablename.colname", or if using an attached database,
2092
+ # "database.tablename.colname", in cursor.description
2093
+ if not self._preserve_raw_colnames and "." in colname:
2094
+ return colname.split(".")[-1], colname
2095
+ else:
2096
+ return colname, None
2097
+
2098
+
2099
+ class SQLiteDialect(default.DefaultDialect):
2100
+ name = "sqlite"
2101
+ supports_alter = False
2102
+
2103
+ # SQlite supports "DEFAULT VALUES" but *does not* support
2104
+ # "VALUES (DEFAULT)"
2105
+ supports_default_values = True
2106
+ supports_default_metavalue = False
2107
+
2108
+ # sqlite issue:
2109
+ # https://github.com/python/cpython/issues/93421
2110
+ # note this parameter is no longer used by the ORM or default dialect
2111
+ # see #9414
2112
+ supports_sane_rowcount_returning = False
2113
+
2114
+ supports_empty_insert = False
2115
+ supports_cast = True
2116
+ supports_multivalues_insert = True
2117
+ use_insertmanyvalues = True
2118
+ tuple_in_values = True
2119
+ supports_statement_cache = True
2120
+ insert_null_pk_still_autoincrements = True
2121
+ insert_returning = True
2122
+ update_returning = True
2123
+ update_returning_multifrom = True
2124
+ delete_returning = True
2125
+ update_returning_multifrom = True
2126
+
2127
+ supports_default_metavalue = True
2128
+ """dialect supports INSERT... VALUES (DEFAULT) syntax"""
2129
+
2130
+ default_metavalue_token = "NULL"
2131
+ """for INSERT... VALUES (DEFAULT) syntax, the token to put in the
2132
+ parenthesis."""
2133
+
2134
+ default_paramstyle = "qmark"
2135
+ execution_ctx_cls = SQLiteExecutionContext
2136
+ statement_compiler = SQLiteCompiler
2137
+ ddl_compiler = SQLiteDDLCompiler
2138
+ type_compiler_cls = SQLiteTypeCompiler
2139
+ preparer = SQLiteIdentifierPreparer
2140
+ ischema_names = ischema_names
2141
+ colspecs = colspecs
2142
+
2143
+ construct_arguments = [
2144
+ (
2145
+ sa_schema.Table,
2146
+ {
2147
+ "autoincrement": False,
2148
+ "with_rowid": True,
2149
+ "strict": False,
2150
+ },
2151
+ ),
2152
+ (sa_schema.Index, {"where": None}),
2153
+ (
2154
+ sa_schema.Column,
2155
+ {
2156
+ "on_conflict_primary_key": None,
2157
+ "on_conflict_not_null": None,
2158
+ "on_conflict_unique": None,
2159
+ },
2160
+ ),
2161
+ (sa_schema.Constraint, {"on_conflict": None}),
2162
+ (sa_ddl.CreateView, {"if_not_exists": False}),
2163
+ ]
2164
+
2165
+ _broken_fk_pragma_quotes = False
2166
+ _broken_dotted_colnames = False
2167
+
2168
+ def __init__(
2169
+ self,
2170
+ native_datetime: bool = False,
2171
+ json_serializer: Callable[[_JSON_VALUE], str] | None = None,
2172
+ json_deserializer: Callable[[str], _JSON_VALUE] | None = None,
2173
+ **kwargs: Any,
2174
+ ) -> None:
2175
+ default.DefaultDialect.__init__(self, **kwargs)
2176
+
2177
+ self._json_serializer = json_serializer
2178
+ self._json_deserializer = json_deserializer
2179
+
2180
+ # this flag used by pysqlite dialect, and perhaps others in the
2181
+ # future, to indicate the driver is handling date/timestamp
2182
+ # conversions (and perhaps datetime/time as well on some hypothetical
2183
+ # driver ?)
2184
+ self.native_datetime = native_datetime
2185
+
2186
+ if self.dbapi is not None:
2187
+ if self.dbapi.sqlite_version_info < (3, 7, 16):
2188
+ util.warn(
2189
+ "SQLite version %s is older than 3.7.16, and will not "
2190
+ "support right nested joins, as are sometimes used in "
2191
+ "more complex ORM scenarios. SQLAlchemy 1.4 and above "
2192
+ "no longer tries to rewrite these joins."
2193
+ % (self.dbapi.sqlite_version_info,)
2194
+ )
2195
+
2196
+ # NOTE: python 3.7 on fedora for me has SQLite 3.34.1. These
2197
+ # version checks are getting very stale.
2198
+ self._broken_dotted_colnames = self.dbapi.sqlite_version_info < (
2199
+ 3,
2200
+ 10,
2201
+ 0,
2202
+ )
2203
+ self.supports_default_values = self.dbapi.sqlite_version_info >= (
2204
+ 3,
2205
+ 3,
2206
+ 8,
2207
+ )
2208
+ self.supports_cast = self.dbapi.sqlite_version_info >= (3, 2, 3)
2209
+ self.supports_multivalues_insert = (
2210
+ # https://www.sqlite.org/releaselog/3_7_11.html
2211
+ self.dbapi.sqlite_version_info
2212
+ >= (3, 7, 11)
2213
+ )
2214
+ # see https://www.sqlalchemy.org/trac/ticket/2568
2215
+ # as well as https://www.sqlite.org/src/info/600482d161
2216
+ self._broken_fk_pragma_quotes = self.dbapi.sqlite_version_info < (
2217
+ 3,
2218
+ 6,
2219
+ 14,
2220
+ )
2221
+
2222
+ if self.dbapi.sqlite_version_info < (3, 35) or util.pypy:
2223
+ self.update_returning = self.delete_returning = (
2224
+ self.insert_returning
2225
+ ) = False
2226
+
2227
+ if self.dbapi.sqlite_version_info < (3, 32, 0):
2228
+ # https://www.sqlite.org/limits.html
2229
+ self.insertmanyvalues_max_parameters = 999
2230
+
2231
+ _isolation_lookup = util.immutabledict(
2232
+ {"READ UNCOMMITTED": 1, "SERIALIZABLE": 0}
2233
+ )
2234
+
2235
+ def get_isolation_level_values(self, dbapi_connection):
2236
+ return list(self._isolation_lookup)
2237
+
2238
+ def set_isolation_level(
2239
+ self, dbapi_connection: DBAPIConnection, level: IsolationLevel
2240
+ ) -> None:
2241
+ isolation_level = self._isolation_lookup[level]
2242
+
2243
+ cursor = dbapi_connection.cursor()
2244
+ cursor.execute(f"PRAGMA read_uncommitted = {isolation_level}")
2245
+ cursor.close()
2246
+
2247
+ def get_isolation_level(self, dbapi_connection):
2248
+ cursor = dbapi_connection.cursor()
2249
+ cursor.execute("PRAGMA read_uncommitted")
2250
+ res = cursor.fetchone()
2251
+ if res:
2252
+ value = res[0]
2253
+ else:
2254
+ # https://www.sqlite.org/changes.html#version_3_3_3
2255
+ # "Optional READ UNCOMMITTED isolation (instead of the
2256
+ # default isolation level of SERIALIZABLE) and
2257
+ # table level locking when database connections
2258
+ # share a common cache.""
2259
+ # pre-SQLite 3.3.0 default to 0
2260
+ value = 0
2261
+ cursor.close()
2262
+ if value == 0:
2263
+ return "SERIALIZABLE"
2264
+ elif value == 1:
2265
+ return "READ UNCOMMITTED"
2266
+ else:
2267
+ assert False, "Unknown isolation level %s" % value
2268
+
2269
+ @reflection.cache
2270
+ def get_schema_names(self, connection, **kw):
2271
+ s = "PRAGMA database_list"
2272
+ dl = connection.exec_driver_sql(s)
2273
+
2274
+ return [db[1] for db in dl if db[1] != "temp"]
2275
+
2276
+ def _format_schema(self, schema, table_name):
2277
+ if schema is not None:
2278
+ qschema = self.identifier_preparer.quote_identifier(schema)
2279
+ name = f"{qschema}.{table_name}"
2280
+ else:
2281
+ name = table_name
2282
+ return name
2283
+
2284
+ def _sqlite_main_query(
2285
+ self,
2286
+ table: str,
2287
+ type_: str,
2288
+ schema: Optional[str],
2289
+ sqlite_include_internal: bool,
2290
+ ):
2291
+ main = self._format_schema(schema, table)
2292
+ if not sqlite_include_internal:
2293
+ filter_table = " AND name NOT LIKE 'sqlite~_%' ESCAPE '~'"
2294
+ else:
2295
+ filter_table = ""
2296
+ query = (
2297
+ f"SELECT name FROM {main} "
2298
+ f"WHERE type='{type_}'{filter_table} "
2299
+ "ORDER BY name"
2300
+ )
2301
+ return query
2302
+
2303
+ @reflection.cache
2304
+ def get_table_names(
2305
+ self, connection, schema=None, sqlite_include_internal=False, **kw
2306
+ ):
2307
+ query = self._sqlite_main_query(
2308
+ "sqlite_master", "table", schema, sqlite_include_internal
2309
+ )
2310
+ names = connection.exec_driver_sql(query).scalars().all()
2311
+ return names
2312
+
2313
+ @reflection.cache
2314
+ def get_temp_table_names(
2315
+ self, connection, sqlite_include_internal=False, **kw
2316
+ ):
2317
+ query = self._sqlite_main_query(
2318
+ "sqlite_temp_master", "table", None, sqlite_include_internal
2319
+ )
2320
+ names = connection.exec_driver_sql(query).scalars().all()
2321
+ return names
2322
+
2323
+ @reflection.cache
2324
+ def get_temp_view_names(
2325
+ self, connection, sqlite_include_internal=False, **kw
2326
+ ):
2327
+ query = self._sqlite_main_query(
2328
+ "sqlite_temp_master", "view", None, sqlite_include_internal
2329
+ )
2330
+ names = connection.exec_driver_sql(query).scalars().all()
2331
+ return names
2332
+
2333
+ @reflection.cache
2334
+ def has_table(self, connection, table_name, schema=None, **kw):
2335
+ self._ensure_has_table_connection(connection)
2336
+
2337
+ if schema is not None and schema not in self.get_schema_names(
2338
+ connection, **kw
2339
+ ):
2340
+ return False
2341
+
2342
+ info = self._get_table_pragma(
2343
+ connection, "table_info", table_name, schema=schema
2344
+ )
2345
+ return bool(info)
2346
+
2347
+ def _get_default_schema_name(self, connection):
2348
+ return "main"
2349
+
2350
+ @reflection.cache
2351
+ def get_view_names(
2352
+ self, connection, schema=None, sqlite_include_internal=False, **kw
2353
+ ):
2354
+ query = self._sqlite_main_query(
2355
+ "sqlite_master", "view", schema, sqlite_include_internal
2356
+ )
2357
+ names = connection.exec_driver_sql(query).scalars().all()
2358
+ return names
2359
+
2360
+ @reflection.cache
2361
+ def get_view_definition(self, connection, view_name, schema=None, **kw):
2362
+ if schema is not None:
2363
+ qschema = self.identifier_preparer.quote_identifier(schema)
2364
+ master = f"{qschema}.sqlite_master"
2365
+ s = ("SELECT sql FROM %s WHERE name = ? AND type='view'") % (
2366
+ master,
2367
+ )
2368
+ rs = connection.exec_driver_sql(s, (view_name,))
2369
+ else:
2370
+ try:
2371
+ s = (
2372
+ "SELECT sql FROM "
2373
+ " (SELECT * FROM sqlite_master UNION ALL "
2374
+ " SELECT * FROM sqlite_temp_master) "
2375
+ "WHERE name = ? "
2376
+ "AND type='view'"
2377
+ )
2378
+ rs = connection.exec_driver_sql(s, (view_name,))
2379
+ except exc.DBAPIError:
2380
+ s = (
2381
+ "SELECT sql FROM sqlite_master WHERE name = ? "
2382
+ "AND type='view'"
2383
+ )
2384
+ rs = connection.exec_driver_sql(s, (view_name,))
2385
+
2386
+ result = rs.fetchall()
2387
+ if result:
2388
+ return result[0].sql
2389
+ else:
2390
+ raise exc.NoSuchTableError(
2391
+ f"{schema}.{view_name}" if schema else view_name
2392
+ )
2393
+
2394
+ @reflection.cache
2395
+ def get_columns(self, connection, table_name, schema=None, **kw):
2396
+ pragma = "table_info"
2397
+ # computed columns are threaded as hidden, they require table_xinfo
2398
+ if self.server_version_info >= (3, 31):
2399
+ pragma = "table_xinfo"
2400
+ info = self._get_table_pragma(
2401
+ connection, pragma, table_name, schema=schema
2402
+ )
2403
+ columns = []
2404
+ tablesql = None
2405
+ for row in info:
2406
+ name = row[1]
2407
+ type_ = row[2].upper()
2408
+ nullable = not row[3]
2409
+ default = row[4]
2410
+ primary_key = row[5]
2411
+ hidden = row[6] if pragma == "table_xinfo" else 0
2412
+
2413
+ # hidden has value 0 for normal columns, 1 for hidden columns,
2414
+ # 2 for computed virtual columns and 3 for computed stored columns
2415
+ # https://www.sqlite.org/src/info/069351b85f9a706f60d3e98fbc8aaf40c374356b967c0464aede30ead3d9d18b
2416
+ if hidden == 1:
2417
+ continue
2418
+
2419
+ generated = bool(hidden)
2420
+ persisted = hidden == 3
2421
+
2422
+ if tablesql is None and generated:
2423
+ tablesql = self._get_table_sql(
2424
+ connection, table_name, schema, **kw
2425
+ )
2426
+ # remove create table
2427
+ match = re.match(
2428
+ (
2429
+ r"create table .*?\((.*)\)"
2430
+ r"(?:\s*,?\s*(?:WITHOUT\s+ROWID|STRICT))*$"
2431
+ ),
2432
+ tablesql.strip(),
2433
+ re.DOTALL | re.IGNORECASE,
2434
+ )
2435
+ assert match, f"create table not found in {tablesql}"
2436
+ tablesql = match.group(1).strip()
2437
+
2438
+ columns.append(
2439
+ self._get_column_info(
2440
+ name,
2441
+ type_,
2442
+ nullable,
2443
+ default,
2444
+ primary_key,
2445
+ generated,
2446
+ persisted,
2447
+ tablesql,
2448
+ )
2449
+ )
2450
+ if columns:
2451
+ return columns
2452
+ elif not self.has_table(connection, table_name, schema):
2453
+ raise exc.NoSuchTableError(
2454
+ f"{schema}.{table_name}" if schema else table_name
2455
+ )
2456
+ else:
2457
+ return ReflectionDefaults.columns()
2458
+
2459
+ def _get_column_info(
2460
+ self,
2461
+ name,
2462
+ type_,
2463
+ nullable,
2464
+ default,
2465
+ primary_key,
2466
+ generated,
2467
+ persisted,
2468
+ tablesql,
2469
+ ):
2470
+ if generated:
2471
+ # the type of a column "cc INTEGER GENERATED ALWAYS AS (1 + 42)"
2472
+ # somehow is "INTEGER GENERATED ALWAYS"
2473
+ type_ = re.sub("generated", "", type_, flags=re.IGNORECASE)
2474
+ type_ = re.sub("always", "", type_, flags=re.IGNORECASE).strip()
2475
+
2476
+ coltype = self._resolve_type_affinity(type_)
2477
+
2478
+ if default is not None:
2479
+ default = str(default)
2480
+
2481
+ colspec = {
2482
+ "name": name,
2483
+ "type": coltype,
2484
+ "nullable": nullable,
2485
+ "default": default,
2486
+ "primary_key": primary_key,
2487
+ }
2488
+ if generated:
2489
+ sqltext = ""
2490
+ if tablesql:
2491
+ pattern = (
2492
+ r"[^,]*\s+GENERATED\s+ALWAYS\s+AS"
2493
+ r"\s+\((.*)\)\s*(?:virtual|stored)?"
2494
+ )
2495
+ match = re.search(
2496
+ re.escape(name) + pattern, tablesql, re.IGNORECASE
2497
+ )
2498
+ if match:
2499
+ sqltext = match.group(1)
2500
+ colspec["computed"] = {"sqltext": sqltext, "persisted": persisted}
2501
+ return colspec
2502
+
2503
+ def _resolve_type_affinity(self, type_):
2504
+ """Return a data type from a reflected column, using affinity rules.
2505
+
2506
+ SQLite's goal for universal compatibility introduces some complexity
2507
+ during reflection, as a column's defined type might not actually be a
2508
+ type that SQLite understands - or indeed, my not be defined *at all*.
2509
+ Internally, SQLite handles this with a 'data type affinity' for each
2510
+ column definition, mapping to one of 'TEXT', 'NUMERIC', 'INTEGER',
2511
+ 'REAL', or 'NONE' (raw bits). The algorithm that determines this is
2512
+ listed in https://www.sqlite.org/datatype3.html section 2.1.
2513
+
2514
+ This method allows SQLAlchemy to support that algorithm, while still
2515
+ providing access to smarter reflection utilities by recognizing
2516
+ column definitions that SQLite only supports through affinity (like
2517
+ DATE and DOUBLE).
2518
+
2519
+ """
2520
+ match = re.match(r"([\w ]+)(\(.*?\))?", type_)
2521
+ if match:
2522
+ coltype = match.group(1)
2523
+ args = match.group(2)
2524
+ else:
2525
+ coltype = ""
2526
+ args = ""
2527
+
2528
+ if coltype in self.ischema_names:
2529
+ coltype = self.ischema_names[coltype]
2530
+ elif "INT" in coltype:
2531
+ coltype = sqltypes.INTEGER
2532
+ elif "CHAR" in coltype or "CLOB" in coltype or "TEXT" in coltype:
2533
+ coltype = sqltypes.TEXT
2534
+ elif "BLOB" in coltype or not coltype:
2535
+ coltype = sqltypes.NullType
2536
+ elif "REAL" in coltype or "FLOA" in coltype or "DOUB" in coltype:
2537
+ coltype = sqltypes.REAL
2538
+ else:
2539
+ coltype = sqltypes.NUMERIC
2540
+
2541
+ if args is not None:
2542
+ args = re.findall(r"(\d+)", args)
2543
+ try:
2544
+ coltype = coltype(*[int(a) for a in args])
2545
+ except TypeError:
2546
+ util.warn(
2547
+ "Could not instantiate type %s with "
2548
+ "reflected arguments %s; using no arguments."
2549
+ % (coltype, args)
2550
+ )
2551
+ coltype = coltype()
2552
+ else:
2553
+ coltype = coltype()
2554
+
2555
+ return coltype
2556
+
2557
+ @reflection.cache
2558
+ def get_pk_constraint(self, connection, table_name, schema=None, **kw):
2559
+ constraint_name = None
2560
+ table_data = self._get_table_sql(connection, table_name, schema=schema)
2561
+ if table_data:
2562
+ PK_PATTERN = r'CONSTRAINT +(?:"(.+?)"|(\w+)) +PRIMARY KEY'
2563
+ result = re.search(PK_PATTERN, table_data, re.I)
2564
+ if result:
2565
+ constraint_name = result.group(1) or result.group(2)
2566
+ else:
2567
+ constraint_name = None
2568
+
2569
+ cols = self.get_columns(connection, table_name, schema, **kw)
2570
+ # consider only pk columns. This also avoids sorting the cached
2571
+ # value returned by get_columns
2572
+ cols = [col for col in cols if col.get("primary_key", 0) > 0]
2573
+ cols.sort(key=lambda col: col.get("primary_key"))
2574
+ pkeys = [col["name"] for col in cols]
2575
+
2576
+ if pkeys:
2577
+ return {"constrained_columns": pkeys, "name": constraint_name}
2578
+ else:
2579
+ return ReflectionDefaults.pk_constraint()
2580
+
2581
+ @reflection.cache
2582
+ def get_foreign_keys(self, connection, table_name, schema=None, **kw):
2583
+ # sqlite makes this *extremely difficult*.
2584
+ # First, use the pragma to get the actual FKs.
2585
+ pragma_fks = self._get_table_pragma(
2586
+ connection, "foreign_key_list", table_name, schema=schema
2587
+ )
2588
+
2589
+ fks = {}
2590
+
2591
+ for row in pragma_fks:
2592
+ (numerical_id, rtbl, lcol, rcol) = (row[0], row[2], row[3], row[4])
2593
+
2594
+ if not rcol:
2595
+ # no referred column, which means it was not named in the
2596
+ # original DDL. The referred columns of the foreign key
2597
+ # constraint are therefore the primary key of the referred
2598
+ # table.
2599
+ try:
2600
+ referred_pk = self.get_pk_constraint(
2601
+ connection, rtbl, schema=schema, **kw
2602
+ )
2603
+ referred_columns = referred_pk["constrained_columns"]
2604
+ except exc.NoSuchTableError:
2605
+ # ignore not existing parents
2606
+ referred_columns = []
2607
+ else:
2608
+ # note we use this list only if this is the first column
2609
+ # in the constraint. for subsequent columns we ignore the
2610
+ # list and append "rcol" if present.
2611
+ referred_columns = []
2612
+
2613
+ if self._broken_fk_pragma_quotes:
2614
+ rtbl = re.sub(r"^[\"\[`\']|[\"\]`\']$", "", rtbl)
2615
+
2616
+ if numerical_id in fks:
2617
+ fk = fks[numerical_id]
2618
+ else:
2619
+ fk = fks[numerical_id] = {
2620
+ "name": None,
2621
+ "constrained_columns": [],
2622
+ "referred_schema": schema,
2623
+ "referred_table": rtbl,
2624
+ "referred_columns": referred_columns,
2625
+ "options": {},
2626
+ }
2627
+ fks[numerical_id] = fk
2628
+
2629
+ fk["constrained_columns"].append(lcol)
2630
+
2631
+ if rcol:
2632
+ fk["referred_columns"].append(rcol)
2633
+
2634
+ def fk_sig(constrained_columns, referred_table, referred_columns):
2635
+ return (
2636
+ tuple(constrained_columns)
2637
+ + (referred_table,)
2638
+ + tuple(referred_columns)
2639
+ )
2640
+
2641
+ # then, parse the actual SQL and attempt to find DDL that matches
2642
+ # the names as well. SQLite saves the DDL in whatever format
2643
+ # it was typed in as, so need to be liberal here.
2644
+
2645
+ keys_by_signature = {
2646
+ fk_sig(
2647
+ fk["constrained_columns"],
2648
+ fk["referred_table"],
2649
+ fk["referred_columns"],
2650
+ ): fk
2651
+ for fk in fks.values()
2652
+ }
2653
+
2654
+ table_data = self._get_table_sql(connection, table_name, schema=schema)
2655
+
2656
+ def parse_fks():
2657
+ if table_data is None:
2658
+ # system tables, etc.
2659
+ return
2660
+
2661
+ # note that we already have the FKs from PRAGMA above. This whole
2662
+ # regexp thing is trying to locate additional detail about the
2663
+ # FKs, namely the name of the constraint and other options.
2664
+ # so parsing the columns is really about matching it up to what
2665
+ # we already have.
2666
+ FK_PATTERN = (
2667
+ r'(?:CONSTRAINT +(?:"(.+?)"|(\w+)) +)?'
2668
+ r"FOREIGN KEY *\( *(.+?) *\) +"
2669
+ r'REFERENCES +(?:(?:"(.+?)")|([a-z0-9_]+)) *\( *((?:(?:"[^"]+"|[a-z0-9_]+) *(?:, *)?)+)\) *' # noqa: E501
2670
+ r"((?:ON (?:DELETE|UPDATE) "
2671
+ r"(?:SET NULL|SET DEFAULT|CASCADE|RESTRICT|NO ACTION) *)*)"
2672
+ r"((?:NOT +)?DEFERRABLE)?"
2673
+ r"(?: +INITIALLY +(DEFERRED|IMMEDIATE))?"
2674
+ )
2675
+ for match in re.finditer(FK_PATTERN, table_data, re.I):
2676
+ (
2677
+ constraint_quoted_name,
2678
+ constraint_name,
2679
+ constrained_columns,
2680
+ referred_quoted_name,
2681
+ referred_name,
2682
+ referred_columns,
2683
+ onupdatedelete,
2684
+ deferrable,
2685
+ initially,
2686
+ ) = match.group(1, 2, 3, 4, 5, 6, 7, 8, 9)
2687
+ constraint_name = constraint_quoted_name or constraint_name
2688
+ constrained_columns = list(
2689
+ self._find_cols_in_sig(constrained_columns)
2690
+ )
2691
+ if not referred_columns:
2692
+ referred_columns = constrained_columns
2693
+ else:
2694
+ referred_columns = list(
2695
+ self._find_cols_in_sig(referred_columns)
2696
+ )
2697
+ referred_name = referred_quoted_name or referred_name
2698
+ options = {}
2699
+
2700
+ for token in re.split(r" *\bON\b *", onupdatedelete.upper()):
2701
+ if token.startswith("DELETE"):
2702
+ ondelete = token[6:].strip()
2703
+ if ondelete and ondelete != "NO ACTION":
2704
+ options["ondelete"] = ondelete
2705
+ elif token.startswith("UPDATE"):
2706
+ onupdate = token[6:].strip()
2707
+ if onupdate and onupdate != "NO ACTION":
2708
+ options["onupdate"] = onupdate
2709
+
2710
+ if deferrable:
2711
+ options["deferrable"] = "NOT" not in deferrable.upper()
2712
+ if initially:
2713
+ options["initially"] = initially.upper()
2714
+
2715
+ yield (
2716
+ constraint_name,
2717
+ constrained_columns,
2718
+ referred_name,
2719
+ referred_columns,
2720
+ options,
2721
+ )
2722
+
2723
+ fkeys = []
2724
+
2725
+ for (
2726
+ constraint_name,
2727
+ constrained_columns,
2728
+ referred_name,
2729
+ referred_columns,
2730
+ options,
2731
+ ) in parse_fks():
2732
+ sig = fk_sig(constrained_columns, referred_name, referred_columns)
2733
+ if sig not in keys_by_signature:
2734
+ util.warn(
2735
+ "WARNING: SQL-parsed foreign key constraint "
2736
+ "'%s' could not be located in PRAGMA "
2737
+ "foreign_keys for table %s" % (sig, table_name)
2738
+ )
2739
+ continue
2740
+ key = keys_by_signature.pop(sig)
2741
+ key["name"] = constraint_name
2742
+ key["options"] = options
2743
+ fkeys.append(key)
2744
+ # assume the remainders are the unnamed, inline constraints, just
2745
+ # use them as is as it's extremely difficult to parse inline
2746
+ # constraints
2747
+ fkeys.extend(keys_by_signature.values())
2748
+ if fkeys:
2749
+ return fkeys
2750
+ else:
2751
+ return ReflectionDefaults.foreign_keys()
2752
+
2753
+ def _find_cols_in_sig(self, sig):
2754
+ for match in re.finditer(r'(?:"(.+?)")|([a-z0-9_]+)', sig, re.I):
2755
+ yield match.group(1) or match.group(2)
2756
+
2757
+ @reflection.cache
2758
+ def get_unique_constraints(
2759
+ self, connection, table_name, schema=None, **kw
2760
+ ):
2761
+ auto_index_by_sig = {}
2762
+ for idx in self.get_indexes(
2763
+ connection,
2764
+ table_name,
2765
+ schema=schema,
2766
+ include_auto_indexes=True,
2767
+ **kw,
2768
+ ):
2769
+ if not idx["name"].startswith("sqlite_autoindex"):
2770
+ continue
2771
+ sig = tuple(idx["column_names"])
2772
+ auto_index_by_sig[sig] = idx
2773
+
2774
+ table_data = self._get_table_sql(
2775
+ connection, table_name, schema=schema, **kw
2776
+ )
2777
+ unique_constraints = []
2778
+
2779
+ def parse_uqs():
2780
+ if table_data is None:
2781
+ return
2782
+ UNIQUE_PATTERN = (
2783
+ r'(?:CONSTRAINT +(?:"(.+?)"|(\w+)) +)?UNIQUE *\((.+?)\)'
2784
+ )
2785
+ INLINE_UNIQUE_PATTERN = (
2786
+ r'(?:(".+?")|(?:[\[`])?([a-z0-9_]+)(?:[\]`])?)[\t ]'
2787
+ r"+[a-z0-9_ ]+?[\t ]+UNIQUE"
2788
+ )
2789
+
2790
+ for match in re.finditer(UNIQUE_PATTERN, table_data, re.I):
2791
+ quoted_name, unquoted_name, cols = match.group(1, 2, 3)
2792
+ name = quoted_name or unquoted_name
2793
+ yield name, list(self._find_cols_in_sig(cols))
2794
+
2795
+ # we need to match inlines as well, as we seek to differentiate
2796
+ # a UNIQUE constraint from a UNIQUE INDEX, even though these
2797
+ # are kind of the same thing :)
2798
+ for match in re.finditer(INLINE_UNIQUE_PATTERN, table_data, re.I):
2799
+ cols = list(
2800
+ self._find_cols_in_sig(match.group(1) or match.group(2))
2801
+ )
2802
+ yield None, cols
2803
+
2804
+ for name, cols in parse_uqs():
2805
+ sig = tuple(cols)
2806
+ if sig in auto_index_by_sig:
2807
+ auto_index_by_sig.pop(sig)
2808
+ parsed_constraint = {"name": name, "column_names": cols}
2809
+ unique_constraints.append(parsed_constraint)
2810
+ # NOTE: auto_index_by_sig might not be empty here,
2811
+ # the PRIMARY KEY may have an entry.
2812
+ if unique_constraints:
2813
+ return unique_constraints
2814
+ else:
2815
+ return ReflectionDefaults.unique_constraints()
2816
+
2817
+ @reflection.cache
2818
+ def get_check_constraints(self, connection, table_name, schema=None, **kw):
2819
+ table_data = self._get_table_sql(
2820
+ connection, table_name, schema=schema, **kw
2821
+ )
2822
+
2823
+ # Extract CHECK constraints by properly handling balanced parentheses
2824
+ # and avoiding false matches when CHECK/CONSTRAINT appear in table
2825
+ # names. See #12924 for context.
2826
+ #
2827
+ # SQLite supports 4 identifier quote styles (see
2828
+ # sqlite.org/lang_keywords.html):
2829
+ # - Double quotes "..." (standard SQL)
2830
+ # - Brackets [...] (MS Access/SQL Server compatibility)
2831
+ # - Backticks `...` (MySQL compatibility)
2832
+ # - Single quotes '...' (SQLite extension)
2833
+ #
2834
+ # NOTE: there is not currently a way to parse CHECK constraints that
2835
+ # contain newlines as the approach here relies upon each individual
2836
+ # CHECK constraint being on a single line by itself. This necessarily
2837
+ # makes assumptions as to how the CREATE TABLE was emitted.
2838
+ CHECK_PATTERN = re.compile(
2839
+ r"""
2840
+ (?<![A-Za-z0-9_]) # Negative lookbehind: ensure CHECK is not
2841
+ # part of an identifier (e.g., table name
2842
+ # like "tableCHECK")
2843
+
2844
+ (?: # Optional CONSTRAINT clause
2845
+ CONSTRAINT\s+
2846
+ ( # Group 1: Constraint name (quoted or unquoted)
2847
+ "(?:[^"]|"")+" # Double-quoted: "name" or "na""me"
2848
+ |'(?:[^']|'')+' # Single-quoted: 'name' or 'na''me'
2849
+ |\[(?:[^\]]|\]\])+\] # Bracket-quoted: [name] or [na]]me]
2850
+ |`(?:[^`]|``)+` # Backtick-quoted: `name` or `na``me`
2851
+ |\S+ # Unquoted: simple_name
2852
+ )
2853
+ \s+
2854
+ )?
2855
+
2856
+ CHECK\s*\( # CHECK keyword followed by opening paren
2857
+ """,
2858
+ re.VERBOSE | re.IGNORECASE,
2859
+ )
2860
+ cks = []
2861
+
2862
+ for match in re.finditer(CHECK_PATTERN, table_data or ""):
2863
+ constraint_name = match.group(1)
2864
+
2865
+ if constraint_name:
2866
+ # Remove surrounding quotes if present
2867
+ # Double quotes: "name" -> name
2868
+ # Single quotes: 'name' -> name
2869
+ # Brackets: [name] -> name
2870
+ # Backticks: `name` -> name
2871
+ constraint_name = re.sub(
2872
+ r'^(["\'`])(.+)\1$|^\[(.+)\]$',
2873
+ lambda m: m.group(2) or m.group(3),
2874
+ constraint_name,
2875
+ flags=re.DOTALL,
2876
+ )
2877
+
2878
+ # Find the matching closing parenthesis by counting balanced parens
2879
+ # Must track string context to ignore parens inside string literals
2880
+ start = match.end() # Position after 'CHECK ('
2881
+ paren_count = 1
2882
+ in_single_quote = False
2883
+ in_double_quote = False
2884
+
2885
+ for pos, char in enumerate(table_data[start:], start):
2886
+ # Track string literal context
2887
+ if char == "'" and not in_double_quote:
2888
+ in_single_quote = not in_single_quote
2889
+ elif char == '"' and not in_single_quote:
2890
+ in_double_quote = not in_double_quote
2891
+ # Only count parens when not inside a string literal
2892
+ elif not in_single_quote and not in_double_quote:
2893
+ if char == "(":
2894
+ paren_count += 1
2895
+ elif char == ")":
2896
+ paren_count -= 1
2897
+ if paren_count == 0:
2898
+ # Successfully found matching closing parenthesis
2899
+ sqltext = table_data[start:pos].strip()
2900
+ cks.append(
2901
+ {"sqltext": sqltext, "name": constraint_name}
2902
+ )
2903
+ break
2904
+
2905
+ cks.sort(key=lambda d: d["name"] or "~") # sort None as last
2906
+ if cks:
2907
+ return cks
2908
+ else:
2909
+ return ReflectionDefaults.check_constraints()
2910
+
2911
+ @reflection.cache
2912
+ def get_indexes(self, connection, table_name, schema=None, **kw):
2913
+ pragma_indexes = self._get_table_pragma(
2914
+ connection, "index_list", table_name, schema=schema
2915
+ )
2916
+ indexes = []
2917
+
2918
+ # regular expression to extract the filter predicate of a partial
2919
+ # index. this could fail to extract the predicate correctly on
2920
+ # indexes created like
2921
+ # CREATE INDEX i ON t (col || ') where') WHERE col <> ''
2922
+ # but as this function does not support expression-based indexes
2923
+ # this case does not occur.
2924
+ partial_pred_re = re.compile(r"\)\s+where\s+(.+)", re.IGNORECASE)
2925
+
2926
+ if schema:
2927
+ schema_expr = "%s." % self.identifier_preparer.quote_identifier(
2928
+ schema
2929
+ )
2930
+ else:
2931
+ schema_expr = ""
2932
+
2933
+ include_auto_indexes = kw.pop("include_auto_indexes", False)
2934
+ for row in pragma_indexes:
2935
+ # ignore implicit primary key index.
2936
+ # https://www.mail-archive.com/sqlite-users@sqlite.org/msg30517.html
2937
+ if not include_auto_indexes and row[1].startswith(
2938
+ "sqlite_autoindex"
2939
+ ):
2940
+ continue
2941
+ indexes.append(
2942
+ dict(
2943
+ name=row[1],
2944
+ column_names=[],
2945
+ unique=row[2],
2946
+ dialect_options={},
2947
+ )
2948
+ )
2949
+
2950
+ # check partial indexes
2951
+ if len(row) >= 5 and row[4]:
2952
+ s = (
2953
+ "SELECT sql FROM %(schema)ssqlite_master "
2954
+ "WHERE name = ? "
2955
+ "AND type = 'index'" % {"schema": schema_expr}
2956
+ )
2957
+ rs = connection.exec_driver_sql(s, (row[1],))
2958
+ index_sql = rs.scalar()
2959
+ predicate_match = partial_pred_re.search(index_sql)
2960
+ if predicate_match is None:
2961
+ # unless the regex is broken this case shouldn't happen
2962
+ # because we know this is a partial index, so the
2963
+ # definition sql should match the regex
2964
+ util.warn(
2965
+ "Failed to look up filter predicate of "
2966
+ "partial index %s" % row[1]
2967
+ )
2968
+ else:
2969
+ predicate = predicate_match.group(1)
2970
+ indexes[-1]["dialect_options"]["sqlite_where"] = text(
2971
+ predicate
2972
+ )
2973
+
2974
+ # loop thru unique indexes to get the column names.
2975
+ for idx in list(indexes):
2976
+ pragma_index = self._get_table_pragma(
2977
+ connection, "index_info", idx["name"], schema=schema
2978
+ )
2979
+
2980
+ for row in pragma_index:
2981
+ if row[2] is None:
2982
+ util.warn(
2983
+ "Skipped unsupported reflection of "
2984
+ "expression-based index %s" % idx["name"]
2985
+ )
2986
+ indexes.remove(idx)
2987
+ break
2988
+ else:
2989
+ idx["column_names"].append(row[2])
2990
+
2991
+ indexes.sort(key=lambda d: d["name"] or "~") # sort None as last
2992
+ if indexes:
2993
+ return indexes
2994
+ elif not self.has_table(connection, table_name, schema):
2995
+ raise exc.NoSuchTableError(
2996
+ f"{schema}.{table_name}" if schema else table_name
2997
+ )
2998
+ else:
2999
+ return ReflectionDefaults.indexes()
3000
+
3001
+ def _is_sys_table(self, table_name):
3002
+ return table_name in {
3003
+ "sqlite_schema",
3004
+ "sqlite_master",
3005
+ "sqlite_temp_schema",
3006
+ "sqlite_temp_master",
3007
+ }
3008
+
3009
+ @reflection.cache
3010
+ def _get_table_sql(self, connection, table_name, schema=None, **kw):
3011
+ if schema:
3012
+ schema_expr = "%s." % (
3013
+ self.identifier_preparer.quote_identifier(schema)
3014
+ )
3015
+ else:
3016
+ schema_expr = ""
3017
+ try:
3018
+ s = (
3019
+ "SELECT sql FROM "
3020
+ " (SELECT * FROM %(schema)ssqlite_master UNION ALL "
3021
+ " SELECT * FROM %(schema)ssqlite_temp_master) "
3022
+ "WHERE name = ? "
3023
+ "AND type in ('table', 'view')" % {"schema": schema_expr}
3024
+ )
3025
+ rs = connection.exec_driver_sql(s, (table_name,))
3026
+ except exc.DBAPIError:
3027
+ s = (
3028
+ "SELECT sql FROM %(schema)ssqlite_master "
3029
+ "WHERE name = ? "
3030
+ "AND type in ('table', 'view')" % {"schema": schema_expr}
3031
+ )
3032
+ rs = connection.exec_driver_sql(s, (table_name,))
3033
+ value = rs.scalar()
3034
+ if value is None and not self._is_sys_table(table_name):
3035
+ raise exc.NoSuchTableError(f"{schema_expr}{table_name}")
3036
+ return value
3037
+
3038
+ def _get_table_pragma(self, connection, pragma, table_name, schema=None):
3039
+ quote = self.identifier_preparer.quote_identifier
3040
+ if schema is not None:
3041
+ statements = [f"PRAGMA {quote(schema)}."]
3042
+ else:
3043
+ # because PRAGMA looks in all attached databases if no schema
3044
+ # given, need to specify "main" schema, however since we want
3045
+ # 'temp' tables in the same namespace as 'main', need to run
3046
+ # the PRAGMA twice
3047
+ statements = ["PRAGMA main.", "PRAGMA temp."]
3048
+
3049
+ qtable = quote(table_name)
3050
+ for statement in statements:
3051
+ statement = f"{statement}{pragma}({qtable})"
3052
+ cursor = connection.exec_driver_sql(statement)
3053
+ if not cursor._soft_closed:
3054
+ # work around SQLite issue whereby cursor.description
3055
+ # is blank when PRAGMA returns no rows:
3056
+ # https://www.sqlite.org/cvstrac/tktview?tn=1884
3057
+ result = cursor.fetchall()
3058
+ else:
3059
+ result = []
3060
+ if result:
3061
+ return result
3062
+ else:
3063
+ return []