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,724 @@
1
+ # dialects/mysql/reflection.py
2
+ # Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3
+ # <see AUTHORS file>
4
+ #
5
+ # This module is part of SQLAlchemy and is released under
6
+ # the MIT License: https://www.opensource.org/licenses/mit-license.php
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ from typing import Any
11
+ from typing import Callable
12
+ from typing import Literal
13
+ from typing import Optional
14
+ from typing import overload
15
+ from typing import Sequence
16
+ from typing import TYPE_CHECKING
17
+ from typing import Union
18
+
19
+ from .enumerated import ENUM
20
+ from .enumerated import SET
21
+ from .types import DATETIME
22
+ from .types import TIME
23
+ from .types import TIMESTAMP
24
+ from ... import types as sqltypes
25
+ from ... import util
26
+
27
+ if TYPE_CHECKING:
28
+ from .base import MySQLDialect
29
+ from .base import MySQLIdentifierPreparer
30
+ from ...engine.interfaces import ReflectedColumn
31
+
32
+
33
+ class ReflectedState:
34
+ """Stores raw information about a SHOW CREATE TABLE statement."""
35
+
36
+ charset: Optional[str]
37
+
38
+ def __init__(self) -> None:
39
+ self.columns: list[ReflectedColumn] = []
40
+ self.table_options: dict[str, str] = {}
41
+ self.table_name: Optional[str] = None
42
+ self.keys: list[dict[str, Any]] = []
43
+ self.fk_constraints: list[dict[str, Any]] = []
44
+ self.ck_constraints: list[dict[str, Any]] = []
45
+
46
+
47
+ class MySQLTableDefinitionParser:
48
+ """Parses the results of a SHOW CREATE TABLE statement."""
49
+
50
+ def __init__(
51
+ self, dialect: MySQLDialect, preparer: MySQLIdentifierPreparer
52
+ ):
53
+ self.dialect = dialect
54
+ self.preparer = preparer
55
+ self._prep_regexes()
56
+
57
+ def parse(
58
+ self, show_create: str, charset: Optional[str]
59
+ ) -> ReflectedState:
60
+ state = ReflectedState()
61
+ state.charset = charset
62
+ for line in re.split(r"\r?\n", show_create):
63
+ if line.startswith(" " + self.preparer.initial_quote):
64
+ self._parse_column(line, state)
65
+ # a regular table options line
66
+ elif line.startswith(") "):
67
+ self._parse_table_options(line, state)
68
+ # an ANSI-mode table options line
69
+ elif line == ")":
70
+ pass
71
+ elif line.startswith("CREATE "):
72
+ self._parse_table_name(line, state)
73
+ elif "PARTITION" in line:
74
+ self._parse_partition_options(line, state)
75
+ # Not present in real reflection, but may be if
76
+ # loading from a file.
77
+ elif not line:
78
+ pass
79
+ else:
80
+ type_, spec = self._parse_constraints(line)
81
+ if type_ is None:
82
+ util.warn("Unknown schema content: %r" % line)
83
+ elif type_ == "key":
84
+ state.keys.append(spec) # type: ignore[arg-type]
85
+ elif type_ == "fk_constraint":
86
+ state.fk_constraints.append(spec) # type: ignore[arg-type]
87
+ elif type_ == "ck_constraint":
88
+ state.ck_constraints.append(spec) # type: ignore[arg-type]
89
+ else:
90
+ pass
91
+ return state
92
+
93
+ def _check_view(self, sql: str) -> bool:
94
+ return bool(self._re_is_view.match(sql))
95
+
96
+ def _parse_constraints(self, line: str) -> Union[
97
+ tuple[None, str],
98
+ tuple[Literal["partition"], str],
99
+ tuple[
100
+ Literal["ck_constraint", "fk_constraint", "key"], dict[str, str]
101
+ ],
102
+ ]:
103
+ """Parse a KEY or CONSTRAINT line.
104
+
105
+ :param line: A line of SHOW CREATE TABLE output
106
+ """
107
+
108
+ # KEY
109
+ m = self._re_key.match(line)
110
+ if m:
111
+ spec = m.groupdict()
112
+ # convert columns into name, length pairs
113
+ # NOTE: we may want to consider SHOW INDEX as the
114
+ # format of indexes in MySQL becomes more complex
115
+ spec["columns"] = self._parse_keyexprs(spec["columns"])
116
+ if spec["version_sql"]:
117
+ m2 = self._re_key_version_sql.match(spec["version_sql"])
118
+ if m2 and m2.groupdict()["parser"]:
119
+ spec["parser"] = m2.groupdict()["parser"]
120
+ if spec["parser"]:
121
+ spec["parser"] = self.preparer.unformat_identifiers(
122
+ spec["parser"]
123
+ )[0]
124
+ return "key", spec
125
+
126
+ # FOREIGN KEY CONSTRAINT
127
+ m = self._re_fk_constraint.match(line)
128
+ if m:
129
+ spec = m.groupdict()
130
+ spec["table"] = self.preparer.unformat_identifiers(spec["table"])
131
+ spec["local"] = [c[0] for c in self._parse_keyexprs(spec["local"])]
132
+ spec["foreign"] = [
133
+ c[0] for c in self._parse_keyexprs(spec["foreign"])
134
+ ]
135
+ return "fk_constraint", spec
136
+
137
+ # CHECK constraint
138
+ m = self._re_ck_constraint.match(line)
139
+ if m:
140
+ spec = m.groupdict()
141
+ return "ck_constraint", spec
142
+
143
+ # PARTITION and SUBPARTITION
144
+ m = self._re_partition.match(line)
145
+ if m:
146
+ # Punt!
147
+ return "partition", line
148
+
149
+ # No match.
150
+ return (None, line)
151
+
152
+ def _parse_table_name(self, line: str, state: ReflectedState) -> None:
153
+ """Extract the table name.
154
+
155
+ :param line: The first line of SHOW CREATE TABLE
156
+ """
157
+
158
+ regex, cleanup = self._pr_name
159
+ m = regex.match(line)
160
+ if m:
161
+ state.table_name = cleanup(m.group("name"))
162
+
163
+ def _parse_table_options(self, line: str, state: ReflectedState) -> None:
164
+ """Build a dictionary of all reflected table-level options.
165
+
166
+ :param line: The final line of SHOW CREATE TABLE output.
167
+ """
168
+
169
+ options = {}
170
+
171
+ if line and line != ")":
172
+ rest_of_line = line
173
+ for regex, cleanup in self._pr_options:
174
+ m = regex.search(rest_of_line)
175
+ if not m:
176
+ continue
177
+ directive, value = m.group("directive"), m.group("val")
178
+ if cleanup:
179
+ value = cleanup(value)
180
+ options[directive.lower()] = value
181
+ rest_of_line = regex.sub("", rest_of_line)
182
+
183
+ for nope in ("auto_increment", "data directory", "index directory"):
184
+ options.pop(nope, None)
185
+
186
+ for opt, val in options.items():
187
+ state.table_options["%s_%s" % (self.dialect.name, opt)] = val
188
+
189
+ def _parse_partition_options(
190
+ self, line: str, state: ReflectedState
191
+ ) -> None:
192
+ options = {}
193
+ new_line = line[:]
194
+
195
+ while new_line.startswith("(") or new_line.startswith(" "):
196
+ new_line = new_line[1:]
197
+
198
+ for regex, cleanup in self._pr_options:
199
+ m = regex.search(new_line)
200
+ if not m or "PARTITION" not in regex.pattern:
201
+ continue
202
+
203
+ directive = m.group("directive")
204
+ directive = directive.lower()
205
+ is_subpartition = directive == "subpartition"
206
+
207
+ if directive == "partition" or is_subpartition:
208
+ new_line = new_line.replace(") */", "")
209
+ new_line = new_line.replace(",", "")
210
+ if is_subpartition and new_line.endswith(")"):
211
+ new_line = new_line[:-1]
212
+ if self.dialect.name == "mariadb" and new_line.endswith(")"):
213
+ if (
214
+ "MAXVALUE" in new_line
215
+ or "MINVALUE" in new_line
216
+ or "ENGINE" in new_line
217
+ ):
218
+ # final line of MariaDB partition endswith ")"
219
+ new_line = new_line[:-1]
220
+
221
+ defs = "%s_%s_definitions" % (self.dialect.name, directive)
222
+ options[defs] = new_line
223
+
224
+ else:
225
+ directive = directive.replace(" ", "_")
226
+ value = m.group("val")
227
+ if cleanup:
228
+ value = cleanup(value)
229
+ options[directive] = value
230
+ break
231
+
232
+ for opt, val in options.items():
233
+ part_def = "%s_partition_definitions" % (self.dialect.name)
234
+ subpart_def = "%s_subpartition_definitions" % (self.dialect.name)
235
+ if opt == part_def or opt == subpart_def:
236
+ # builds a string of definitions
237
+ if opt not in state.table_options:
238
+ state.table_options[opt] = val
239
+ else:
240
+ state.table_options[opt] = "%s, %s" % (
241
+ state.table_options[opt],
242
+ val,
243
+ )
244
+ else:
245
+ state.table_options["%s_%s" % (self.dialect.name, opt)] = val
246
+
247
+ def _parse_column(self, line: str, state: ReflectedState) -> None:
248
+ """Extract column details.
249
+
250
+ Falls back to a 'minimal support' variant if full parse fails.
251
+
252
+ :param line: Any column-bearing line from SHOW CREATE TABLE
253
+ """
254
+
255
+ spec = None
256
+ m = self._re_column.match(line)
257
+ if m:
258
+ spec = m.groupdict()
259
+ spec["full"] = True
260
+ else:
261
+ m = self._re_column_loose.match(line)
262
+ if m:
263
+ spec = m.groupdict()
264
+ spec["full"] = False
265
+ if not spec:
266
+ util.warn("Unknown column definition %r" % line)
267
+ return
268
+ if not spec["full"]:
269
+ util.warn("Incomplete reflection of column definition %r" % line)
270
+
271
+ name, type_, args = spec["name"], spec["coltype"], spec["arg"]
272
+
273
+ try:
274
+ col_type = self.dialect.ischema_names[type_]
275
+ except KeyError:
276
+ util.warn(
277
+ "Did not recognize type '%s' of column '%s'" % (type_, name)
278
+ )
279
+ col_type = sqltypes.NullType
280
+
281
+ # Column type positional arguments eg. varchar(32)
282
+ if args is None or args == "":
283
+ type_args = []
284
+ elif args[0] == "'" and args[-1] == "'":
285
+ type_args = self._re_csv_str.findall(args)
286
+ else:
287
+ type_args = [int(v) for v in self._re_csv_int.findall(args)]
288
+
289
+ # Column type keyword options
290
+ type_kw = {}
291
+
292
+ if issubclass(col_type, (DATETIME, TIME, TIMESTAMP)):
293
+ if type_args:
294
+ type_kw["fsp"] = type_args.pop(0)
295
+
296
+ for kw in ("unsigned", "zerofill"):
297
+ if spec.get(kw, False):
298
+ type_kw[kw] = True
299
+ for kw in ("charset", "collate"):
300
+ if spec.get(kw, False):
301
+ type_kw[kw] = spec[kw]
302
+ if issubclass(col_type, (ENUM, SET)):
303
+ type_args = _strip_values(type_args)
304
+
305
+ if issubclass(col_type, SET) and "" in type_args:
306
+ type_kw["retrieve_as_bitwise"] = True
307
+
308
+ type_instance = col_type(*type_args, **type_kw)
309
+
310
+ col_kw: dict[str, Any] = {}
311
+
312
+ # NOT NULL
313
+ col_kw["nullable"] = True
314
+ # this can be "NULL" in the case of TIMESTAMP
315
+ if spec.get("notnull", False) == "NOT NULL":
316
+ col_kw["nullable"] = False
317
+ # For generated columns, the nullability is marked in a different place
318
+ if spec.get("notnull_generated", False) == "NOT NULL":
319
+ col_kw["nullable"] = False
320
+
321
+ # AUTO_INCREMENT
322
+ if spec.get("autoincr", False):
323
+ col_kw["autoincrement"] = True
324
+ elif issubclass(col_type, sqltypes.Integer):
325
+ col_kw["autoincrement"] = False
326
+
327
+ # DEFAULT
328
+ default = spec.get("default", None)
329
+
330
+ if default == "NULL":
331
+ # eliminates the need to deal with this later.
332
+ default = None
333
+
334
+ comment = spec.get("comment", None)
335
+
336
+ if comment is not None:
337
+ comment = cleanup_text(comment)
338
+
339
+ sqltext = spec.get("generated")
340
+ if sqltext is not None:
341
+ computed = dict(sqltext=sqltext)
342
+ persisted = spec.get("persistence")
343
+ if persisted is not None:
344
+ computed["persisted"] = persisted == "STORED"
345
+ col_kw["computed"] = computed
346
+
347
+ col_d = dict(
348
+ name=name, type=type_instance, default=default, comment=comment
349
+ )
350
+ col_d.update(col_kw)
351
+ state.columns.append(col_d) # type: ignore[arg-type]
352
+
353
+ def _describe_to_create(
354
+ self,
355
+ table_name: str,
356
+ columns: Sequence[tuple[str, str, str, str, str, str]],
357
+ ) -> str:
358
+ """Re-format DESCRIBE output as a SHOW CREATE TABLE string.
359
+
360
+ DESCRIBE is a much simpler reflection and is sufficient for
361
+ reflecting views for runtime use. This method formats DDL
362
+ for columns only- keys are omitted.
363
+
364
+ :param columns: A sequence of DESCRIBE or SHOW COLUMNS 6-tuples.
365
+ SHOW FULL COLUMNS FROM rows must be rearranged for use with
366
+ this function.
367
+ """
368
+
369
+ buffer = []
370
+ for row in columns:
371
+ (name, col_type, nullable, default, extra) = (
372
+ row[i] for i in (0, 1, 2, 4, 5)
373
+ )
374
+
375
+ line = [" "]
376
+ line.append(self.preparer.quote_identifier(name))
377
+ line.append(col_type)
378
+ if not nullable:
379
+ line.append("NOT NULL")
380
+ if default:
381
+ if "auto_increment" in default:
382
+ pass
383
+ elif col_type.startswith("timestamp") and default.startswith(
384
+ "C"
385
+ ):
386
+ line.append("DEFAULT")
387
+ line.append(default)
388
+ elif default == "NULL":
389
+ line.append("DEFAULT")
390
+ line.append(default)
391
+ else:
392
+ line.append("DEFAULT")
393
+ line.append("'%s'" % default.replace("'", "''"))
394
+ if extra:
395
+ line.append(extra)
396
+
397
+ buffer.append(" ".join(line))
398
+
399
+ return "".join(
400
+ [
401
+ (
402
+ "CREATE TABLE %s (\n"
403
+ % self.preparer.quote_identifier(table_name)
404
+ ),
405
+ ",\n".join(buffer),
406
+ "\n) ",
407
+ ]
408
+ )
409
+
410
+ def _parse_keyexprs(
411
+ self, identifiers: str
412
+ ) -> list[tuple[str, Optional[int], str]]:
413
+ """Unpack '"col"(2),"col" ASC'-ish strings into components."""
414
+
415
+ return [
416
+ (colname, int(length) if length else None, modifiers)
417
+ for colname, length, modifiers in self._re_keyexprs.findall(
418
+ identifiers
419
+ )
420
+ ]
421
+
422
+ def _prep_regexes(self) -> None:
423
+ """Pre-compile regular expressions."""
424
+
425
+ self._pr_options: list[
426
+ tuple[re.Pattern[Any], Optional[Callable[[str], str]]]
427
+ ] = []
428
+
429
+ _final = self.preparer.final_quote
430
+
431
+ quotes = dict(
432
+ zip(
433
+ ("iq", "fq", "esc_fq"),
434
+ [
435
+ re.escape(s)
436
+ for s in (
437
+ self.preparer.initial_quote,
438
+ _final,
439
+ self.preparer._escape_identifier(_final),
440
+ )
441
+ ],
442
+ )
443
+ )
444
+
445
+ self._pr_name = _pr_compile(
446
+ r"^CREATE (?:\w+ +)?TABLE +"
447
+ r"%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +\($" % quotes,
448
+ self.preparer._unescape_identifier,
449
+ )
450
+
451
+ self._re_is_view = _re_compile(r"^CREATE(?! TABLE)(\s.*)?\sVIEW")
452
+
453
+ # `col`,`col2`(32),`col3`(15) DESC
454
+ #
455
+ self._re_keyexprs = _re_compile(
456
+ r"(?:"
457
+ r"(?:%(iq)s((?:%(esc_fq)s|[^%(fq)s])+)%(fq)s)"
458
+ r"(?:\((\d+)\))?(?: +(ASC|DESC))?(?=\,|$))+" % quotes
459
+ )
460
+
461
+ # 'foo' or 'foo','bar' or 'fo,o','ba''a''r'
462
+ self._re_csv_str = _re_compile(r"\x27(?:\x27\x27|[^\x27])*\x27")
463
+
464
+ # 123 or 123,456
465
+ self._re_csv_int = _re_compile(r"\d+")
466
+
467
+ # `colname` <type> [type opts]
468
+ # (NOT NULL | NULL)
469
+ # DEFAULT ('value' | CURRENT_TIMESTAMP...)
470
+ # COMMENT 'comment'
471
+ # COLUMN_FORMAT (FIXED|DYNAMIC|DEFAULT)
472
+ # STORAGE (DISK|MEMORY)
473
+ self._re_column = _re_compile(
474
+ r" "
475
+ r"%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +"
476
+ r"(?P<coltype>\w+)"
477
+ r"(?:\((?P<arg>(?:\d+|\d+,\d+|"
478
+ r"(?:'(?:''|[^'])*',?)+))\))?"
479
+ r"(?: +(?P<unsigned>UNSIGNED))?"
480
+ r"(?: +(?P<zerofill>ZEROFILL))?"
481
+ r"(?: +CHARACTER SET +(?P<charset>[\w_]+))?"
482
+ r"(?: +COLLATE +(?P<collate>[\w_]+))?"
483
+ r"(?: +(?P<notnull>(?:NOT )?NULL))?"
484
+ r"(?: +DEFAULT +(?P<default>"
485
+ r"(?:NULL|'(?:''|[^'])*'|\(.+?\)|[\-\w\.\(\)]+"
486
+ r"(?: +ON UPDATE [\-\w\.\(\)]+)?)"
487
+ r"))?"
488
+ r"(?: +(?:GENERATED ALWAYS)? ?AS +(?P<generated>\("
489
+ r".*\))? ?(?P<persistence>VIRTUAL|STORED)?"
490
+ r"(?: +(?P<notnull_generated>(?:NOT )?NULL))?"
491
+ r")?"
492
+ r"(?: +(?P<autoincr>AUTO_INCREMENT))?"
493
+ r"(?: +COMMENT +'(?P<comment>(?:''|[^'])*)')?"
494
+ r"(?: +COLUMN_FORMAT +(?P<colfmt>\w+))?"
495
+ r"(?: +STORAGE +(?P<storage>\w+))?"
496
+ r"(?: +(?P<extra>.*))?"
497
+ r",?$" % quotes
498
+ )
499
+
500
+ # Fallback, try to parse as little as possible
501
+ self._re_column_loose = _re_compile(
502
+ r" "
503
+ r"%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +"
504
+ r"(?P<coltype>\w+)"
505
+ r"(?:\((?P<arg>(?:\d+|\d+,\d+|\x27(?:\x27\x27|[^\x27])+\x27))\))?"
506
+ r".*?(?P<notnull>(?:NOT )NULL)?" % quotes
507
+ )
508
+
509
+ # (PRIMARY|UNIQUE|FULLTEXT|SPATIAL) INDEX `name` (USING (BTREE|HASH))?
510
+ # (`col` (ASC|DESC)?, `col` (ASC|DESC)?)
511
+ # KEY_BLOCK_SIZE size | WITH PARSER name /*!50100 WITH PARSER name */
512
+ self._re_key = _re_compile(
513
+ r" "
514
+ r"(?:(?P<type>\S+) )?KEY"
515
+ r"(?: +%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s)?"
516
+ r"(?: +USING +(?P<using_pre>\S+))?"
517
+ r" +\((?P<columns>.+?)\)"
518
+ r"(?: +USING +(?P<using_post>\S+))?"
519
+ r"(?: +KEY_BLOCK_SIZE *[ =]? *(?P<keyblock>\S+))?"
520
+ r"(?: +WITH PARSER +(?P<parser>\S+))?"
521
+ r"(?: +COMMENT +(?P<comment>(\x27\x27|\x27([^\x27])*?\x27)+))?"
522
+ r"(?: +/\*(?P<version_sql>.+)\*/ *)?"
523
+ r",?$" % quotes
524
+ )
525
+
526
+ # https://forums.mysql.com/read.php?20,567102,567111#msg-567111
527
+ # It means if the MySQL version >= \d+, execute what's in the comment
528
+ self._re_key_version_sql = _re_compile(
529
+ r"\!\d+ " r"(?: *WITH PARSER +(?P<parser>\S+) *)?"
530
+ )
531
+
532
+ # CONSTRAINT `name` FOREIGN KEY (`local_col`)
533
+ # REFERENCES `remote` (`remote_col`)
534
+ # MATCH FULL | MATCH PARTIAL | MATCH SIMPLE
535
+ # ON DELETE CASCADE ON UPDATE RESTRICT
536
+ #
537
+ # unique constraints come back as KEYs
538
+ kw = quotes.copy()
539
+ kw["on"] = "RESTRICT|CASCADE|SET NULL|NO ACTION|SET DEFAULT"
540
+ self._re_fk_constraint = _re_compile(
541
+ r" "
542
+ r"CONSTRAINT +"
543
+ r"%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +"
544
+ r"FOREIGN KEY +"
545
+ r"\((?P<local>[^\)]+?)\) REFERENCES +"
546
+ r"(?P<table>%(iq)s[^%(fq)s]+%(fq)s"
547
+ r"(?:\.%(iq)s[^%(fq)s]+%(fq)s)?) +"
548
+ r"\((?P<foreign>(?:%(iq)s[^%(fq)s]+%(fq)s(?: *, *)?)+)\)"
549
+ r"(?: +(?P<match>MATCH \w+))?"
550
+ r"(?: +ON DELETE (?P<ondelete>%(on)s))?"
551
+ r"(?: +ON UPDATE (?P<onupdate>%(on)s))?" % kw
552
+ )
553
+
554
+ # CONSTRAINT `CONSTRAINT_1` CHECK (`x` > 5)'
555
+ # testing on MariaDB 10.2 shows that the CHECK constraint
556
+ # is returned on a line by itself, so to match without worrying
557
+ # about parenthesis in the expression we go to the end of the line
558
+ self._re_ck_constraint = _re_compile(
559
+ r" "
560
+ r"CONSTRAINT +"
561
+ r"%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +"
562
+ r"CHECK +"
563
+ r"\((?P<sqltext>.+)\),?" % kw
564
+ )
565
+
566
+ # PARTITION
567
+ #
568
+ # punt!
569
+ self._re_partition = _re_compile(r"(?:.*)(?:SUB)?PARTITION(?:.*)")
570
+
571
+ # Table-level options (COLLATE, ENGINE, etc.)
572
+ # Do the string options first, since they have quoted
573
+ # strings we need to get rid of.
574
+ for option in _options_of_type_string:
575
+ self._add_option_string(option)
576
+
577
+ for option in (
578
+ "ENGINE",
579
+ "TYPE",
580
+ "AUTO_INCREMENT",
581
+ "AVG_ROW_LENGTH",
582
+ "CHARACTER SET",
583
+ "DEFAULT CHARSET",
584
+ "CHECKSUM",
585
+ "COLLATE",
586
+ "DELAY_KEY_WRITE",
587
+ "INSERT_METHOD",
588
+ "MAX_ROWS",
589
+ "MIN_ROWS",
590
+ "PACK_KEYS",
591
+ "ROW_FORMAT",
592
+ "KEY_BLOCK_SIZE",
593
+ "STATS_SAMPLE_PAGES",
594
+ ):
595
+ self._add_option_word(option)
596
+
597
+ for option in (
598
+ "PARTITION BY",
599
+ "SUBPARTITION BY",
600
+ "PARTITIONS",
601
+ "SUBPARTITIONS",
602
+ "PARTITION",
603
+ "SUBPARTITION",
604
+ ):
605
+ self._add_partition_option_word(option)
606
+
607
+ self._add_option_regex("UNION", r"\([^\)]+\)")
608
+ self._add_option_regex("TABLESPACE", r".*? STORAGE DISK")
609
+ self._add_option_regex(
610
+ "RAID_TYPE",
611
+ r"\w+\s+RAID_CHUNKS\s*\=\s*\w+RAID_CHUNKSIZE\s*=\s*\w+",
612
+ )
613
+
614
+ _optional_equals = r"(?:\s*(?:=\s*)|\s+)"
615
+
616
+ def _add_option_string(self, directive: str) -> None:
617
+ regex = r"(?P<directive>%s)%s" r"'(?P<val>(?:[^']|'')*?)'(?!')" % (
618
+ re.escape(directive),
619
+ self._optional_equals,
620
+ )
621
+ self._pr_options.append(_pr_compile(regex, cleanup_text))
622
+
623
+ def _add_option_word(self, directive: str) -> None:
624
+ regex = r"(?P<directive>%s)%s" r"(?P<val>\w+)" % (
625
+ re.escape(directive),
626
+ self._optional_equals,
627
+ )
628
+ self._pr_options.append(_pr_compile(regex))
629
+
630
+ def _add_partition_option_word(self, directive: str) -> None:
631
+ if directive == "PARTITION BY" or directive == "SUBPARTITION BY":
632
+ regex = r"(?<!\S)(?P<directive>%s)%s" r"(?P<val>\w+.*)" % (
633
+ re.escape(directive),
634
+ self._optional_equals,
635
+ )
636
+ elif directive == "SUBPARTITIONS" or directive == "PARTITIONS":
637
+ regex = r"(?<!\S)(?P<directive>%s)%s" r"(?P<val>\d+)" % (
638
+ re.escape(directive),
639
+ self._optional_equals,
640
+ )
641
+ else:
642
+ regex = r"(?<!\S)(?P<directive>%s)(?!\S)" % (re.escape(directive),)
643
+ self._pr_options.append(_pr_compile(regex))
644
+
645
+ def _add_option_regex(self, directive: str, regex: str) -> None:
646
+ regex = r"(?P<directive>%s)%s" r"(?P<val>%s)" % (
647
+ re.escape(directive),
648
+ self._optional_equals,
649
+ regex,
650
+ )
651
+ self._pr_options.append(_pr_compile(regex))
652
+
653
+
654
+ _options_of_type_string = (
655
+ "COMMENT",
656
+ "DATA DIRECTORY",
657
+ "INDEX DIRECTORY",
658
+ "PASSWORD",
659
+ "CONNECTION",
660
+ )
661
+
662
+
663
+ @overload
664
+ def _pr_compile(
665
+ regex: str, cleanup: Callable[[str], str]
666
+ ) -> tuple[re.Pattern[Any], Callable[[str], str]]: ...
667
+
668
+
669
+ @overload
670
+ def _pr_compile(
671
+ regex: str, cleanup: None = None
672
+ ) -> tuple[re.Pattern[Any], None]: ...
673
+
674
+
675
+ def _pr_compile(
676
+ regex: str, cleanup: Optional[Callable[[str], str]] = None
677
+ ) -> tuple[re.Pattern[Any], Optional[Callable[[str], str]]]:
678
+ """Prepare a 2-tuple of compiled regex and callable."""
679
+
680
+ return (_re_compile(regex), cleanup)
681
+
682
+
683
+ def _re_compile(regex: str) -> re.Pattern[Any]:
684
+ """Compile a string to regex, I and UNICODE."""
685
+
686
+ return re.compile(regex, re.I | re.UNICODE)
687
+
688
+
689
+ def _strip_values(values: Sequence[str]) -> list[str]:
690
+ "Strip reflected values quotes"
691
+ strip_values: list[str] = []
692
+ for a in values:
693
+ if a[0:1] == '"' or a[0:1] == "'":
694
+ # strip enclosing quotes and unquote interior
695
+ a = a[1:-1].replace(a[0] * 2, a[0])
696
+ strip_values.append(a)
697
+ return strip_values
698
+
699
+
700
+ def cleanup_text(raw_text: str) -> str:
701
+ if "\\" in raw_text:
702
+ raw_text = re.sub(
703
+ _control_char_regexp,
704
+ lambda s: _control_char_map[s[0]], # type: ignore[index]
705
+ raw_text,
706
+ )
707
+ return raw_text.replace("''", "'")
708
+
709
+
710
+ _control_char_map = {
711
+ "\\\\": "\\",
712
+ "\\0": "\0",
713
+ "\\a": "\a",
714
+ "\\b": "\b",
715
+ "\\t": "\t",
716
+ "\\n": "\n",
717
+ "\\v": "\v",
718
+ "\\f": "\f",
719
+ "\\r": "\r",
720
+ # '\\e':'\e',
721
+ }
722
+ _control_char_regexp = re.compile(
723
+ "|".join(re.escape(k) for k in _control_char_map)
724
+ )