SQLAlchemy 2.0.36__cp313-cp313-win32.whl

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