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