sqlspec 0.36.0__cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.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 (531) hide show
  1. ac8f31065839703b4e70__mypyc.cpython-310-aarch64-linux-gnu.so +0 -0
  2. sqlspec/__init__.py +140 -0
  3. sqlspec/__main__.py +12 -0
  4. sqlspec/__metadata__.py +14 -0
  5. sqlspec/_serialization.py +315 -0
  6. sqlspec/_typing.py +700 -0
  7. sqlspec/adapters/__init__.py +0 -0
  8. sqlspec/adapters/adbc/__init__.py +5 -0
  9. sqlspec/adapters/adbc/_typing.py +82 -0
  10. sqlspec/adapters/adbc/adk/__init__.py +5 -0
  11. sqlspec/adapters/adbc/adk/store.py +1273 -0
  12. sqlspec/adapters/adbc/config.py +295 -0
  13. sqlspec/adapters/adbc/core.cpython-310-aarch64-linux-gnu.so +0 -0
  14. sqlspec/adapters/adbc/core.py +735 -0
  15. sqlspec/adapters/adbc/data_dictionary.py +334 -0
  16. sqlspec/adapters/adbc/driver.py +529 -0
  17. sqlspec/adapters/adbc/events/__init__.py +5 -0
  18. sqlspec/adapters/adbc/events/store.py +285 -0
  19. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  20. sqlspec/adapters/adbc/litestar/store.py +502 -0
  21. sqlspec/adapters/adbc/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  22. sqlspec/adapters/adbc/type_converter.py +140 -0
  23. sqlspec/adapters/aiosqlite/__init__.py +25 -0
  24. sqlspec/adapters/aiosqlite/_typing.py +82 -0
  25. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  26. sqlspec/adapters/aiosqlite/adk/store.py +818 -0
  27. sqlspec/adapters/aiosqlite/config.py +334 -0
  28. sqlspec/adapters/aiosqlite/core.cpython-310-aarch64-linux-gnu.so +0 -0
  29. sqlspec/adapters/aiosqlite/core.py +315 -0
  30. sqlspec/adapters/aiosqlite/data_dictionary.py +208 -0
  31. sqlspec/adapters/aiosqlite/driver.py +313 -0
  32. sqlspec/adapters/aiosqlite/events/__init__.py +5 -0
  33. sqlspec/adapters/aiosqlite/events/store.py +20 -0
  34. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  35. sqlspec/adapters/aiosqlite/litestar/store.py +279 -0
  36. sqlspec/adapters/aiosqlite/pool.py +533 -0
  37. sqlspec/adapters/asyncmy/__init__.py +21 -0
  38. sqlspec/adapters/asyncmy/_typing.py +87 -0
  39. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  40. sqlspec/adapters/asyncmy/adk/store.py +703 -0
  41. sqlspec/adapters/asyncmy/config.py +302 -0
  42. sqlspec/adapters/asyncmy/core.cpython-310-aarch64-linux-gnu.so +0 -0
  43. sqlspec/adapters/asyncmy/core.py +360 -0
  44. sqlspec/adapters/asyncmy/data_dictionary.py +124 -0
  45. sqlspec/adapters/asyncmy/driver.py +383 -0
  46. sqlspec/adapters/asyncmy/events/__init__.py +5 -0
  47. sqlspec/adapters/asyncmy/events/store.py +104 -0
  48. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  49. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  50. sqlspec/adapters/asyncpg/__init__.py +19 -0
  51. sqlspec/adapters/asyncpg/_typing.py +88 -0
  52. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  53. sqlspec/adapters/asyncpg/adk/store.py +748 -0
  54. sqlspec/adapters/asyncpg/config.py +569 -0
  55. sqlspec/adapters/asyncpg/core.cpython-310-aarch64-linux-gnu.so +0 -0
  56. sqlspec/adapters/asyncpg/core.py +367 -0
  57. sqlspec/adapters/asyncpg/data_dictionary.py +162 -0
  58. sqlspec/adapters/asyncpg/driver.py +487 -0
  59. sqlspec/adapters/asyncpg/events/__init__.py +6 -0
  60. sqlspec/adapters/asyncpg/events/backend.py +286 -0
  61. sqlspec/adapters/asyncpg/events/store.py +40 -0
  62. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  63. sqlspec/adapters/asyncpg/litestar/store.py +251 -0
  64. sqlspec/adapters/bigquery/__init__.py +14 -0
  65. sqlspec/adapters/bigquery/_typing.py +86 -0
  66. sqlspec/adapters/bigquery/adk/__init__.py +5 -0
  67. sqlspec/adapters/bigquery/adk/store.py +827 -0
  68. sqlspec/adapters/bigquery/config.py +353 -0
  69. sqlspec/adapters/bigquery/core.cpython-310-aarch64-linux-gnu.so +0 -0
  70. sqlspec/adapters/bigquery/core.py +715 -0
  71. sqlspec/adapters/bigquery/data_dictionary.py +128 -0
  72. sqlspec/adapters/bigquery/driver.py +548 -0
  73. sqlspec/adapters/bigquery/events/__init__.py +5 -0
  74. sqlspec/adapters/bigquery/events/store.py +139 -0
  75. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  76. sqlspec/adapters/bigquery/litestar/store.py +325 -0
  77. sqlspec/adapters/bigquery/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  78. sqlspec/adapters/bigquery/type_converter.py +107 -0
  79. sqlspec/adapters/cockroach_asyncpg/__init__.py +24 -0
  80. sqlspec/adapters/cockroach_asyncpg/_typing.py +72 -0
  81. sqlspec/adapters/cockroach_asyncpg/adk/__init__.py +3 -0
  82. sqlspec/adapters/cockroach_asyncpg/adk/store.py +410 -0
  83. sqlspec/adapters/cockroach_asyncpg/config.py +238 -0
  84. sqlspec/adapters/cockroach_asyncpg/core.cpython-310-aarch64-linux-gnu.so +0 -0
  85. sqlspec/adapters/cockroach_asyncpg/core.py +55 -0
  86. sqlspec/adapters/cockroach_asyncpg/data_dictionary.py +107 -0
  87. sqlspec/adapters/cockroach_asyncpg/driver.py +144 -0
  88. sqlspec/adapters/cockroach_asyncpg/events/__init__.py +3 -0
  89. sqlspec/adapters/cockroach_asyncpg/events/store.py +20 -0
  90. sqlspec/adapters/cockroach_asyncpg/litestar/__init__.py +3 -0
  91. sqlspec/adapters/cockroach_asyncpg/litestar/store.py +142 -0
  92. sqlspec/adapters/cockroach_psycopg/__init__.py +38 -0
  93. sqlspec/adapters/cockroach_psycopg/_typing.py +129 -0
  94. sqlspec/adapters/cockroach_psycopg/adk/__init__.py +13 -0
  95. sqlspec/adapters/cockroach_psycopg/adk/store.py +868 -0
  96. sqlspec/adapters/cockroach_psycopg/config.py +484 -0
  97. sqlspec/adapters/cockroach_psycopg/core.cpython-310-aarch64-linux-gnu.so +0 -0
  98. sqlspec/adapters/cockroach_psycopg/core.py +63 -0
  99. sqlspec/adapters/cockroach_psycopg/data_dictionary.py +215 -0
  100. sqlspec/adapters/cockroach_psycopg/driver.py +284 -0
  101. sqlspec/adapters/cockroach_psycopg/events/__init__.py +6 -0
  102. sqlspec/adapters/cockroach_psycopg/events/store.py +34 -0
  103. sqlspec/adapters/cockroach_psycopg/litestar/__init__.py +3 -0
  104. sqlspec/adapters/cockroach_psycopg/litestar/store.py +325 -0
  105. sqlspec/adapters/duckdb/__init__.py +25 -0
  106. sqlspec/adapters/duckdb/_typing.py +81 -0
  107. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  108. sqlspec/adapters/duckdb/adk/store.py +850 -0
  109. sqlspec/adapters/duckdb/config.py +463 -0
  110. sqlspec/adapters/duckdb/core.cpython-310-aarch64-linux-gnu.so +0 -0
  111. sqlspec/adapters/duckdb/core.py +257 -0
  112. sqlspec/adapters/duckdb/data_dictionary.py +140 -0
  113. sqlspec/adapters/duckdb/driver.py +430 -0
  114. sqlspec/adapters/duckdb/events/__init__.py +5 -0
  115. sqlspec/adapters/duckdb/events/store.py +57 -0
  116. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  117. sqlspec/adapters/duckdb/litestar/store.py +330 -0
  118. sqlspec/adapters/duckdb/pool.py +293 -0
  119. sqlspec/adapters/duckdb/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  120. sqlspec/adapters/duckdb/type_converter.py +118 -0
  121. sqlspec/adapters/mock/__init__.py +72 -0
  122. sqlspec/adapters/mock/_typing.py +147 -0
  123. sqlspec/adapters/mock/config.py +483 -0
  124. sqlspec/adapters/mock/core.py +319 -0
  125. sqlspec/adapters/mock/data_dictionary.py +366 -0
  126. sqlspec/adapters/mock/driver.py +721 -0
  127. sqlspec/adapters/mysqlconnector/__init__.py +36 -0
  128. sqlspec/adapters/mysqlconnector/_typing.py +141 -0
  129. sqlspec/adapters/mysqlconnector/adk/__init__.py +15 -0
  130. sqlspec/adapters/mysqlconnector/adk/store.py +1060 -0
  131. sqlspec/adapters/mysqlconnector/config.py +394 -0
  132. sqlspec/adapters/mysqlconnector/core.cpython-310-aarch64-linux-gnu.so +0 -0
  133. sqlspec/adapters/mysqlconnector/core.py +303 -0
  134. sqlspec/adapters/mysqlconnector/data_dictionary.py +235 -0
  135. sqlspec/adapters/mysqlconnector/driver.py +483 -0
  136. sqlspec/adapters/mysqlconnector/events/__init__.py +8 -0
  137. sqlspec/adapters/mysqlconnector/events/store.py +98 -0
  138. sqlspec/adapters/mysqlconnector/litestar/__init__.py +5 -0
  139. sqlspec/adapters/mysqlconnector/litestar/store.py +426 -0
  140. sqlspec/adapters/oracledb/__init__.py +60 -0
  141. sqlspec/adapters/oracledb/_numpy_handlers.py +141 -0
  142. sqlspec/adapters/oracledb/_typing.py +182 -0
  143. sqlspec/adapters/oracledb/_uuid_handlers.py +166 -0
  144. sqlspec/adapters/oracledb/adk/__init__.py +10 -0
  145. sqlspec/adapters/oracledb/adk/store.py +2369 -0
  146. sqlspec/adapters/oracledb/config.py +550 -0
  147. sqlspec/adapters/oracledb/core.cpython-310-aarch64-linux-gnu.so +0 -0
  148. sqlspec/adapters/oracledb/core.py +543 -0
  149. sqlspec/adapters/oracledb/data_dictionary.py +536 -0
  150. sqlspec/adapters/oracledb/driver.py +1229 -0
  151. sqlspec/adapters/oracledb/events/__init__.py +16 -0
  152. sqlspec/adapters/oracledb/events/backend.py +347 -0
  153. sqlspec/adapters/oracledb/events/store.py +420 -0
  154. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  155. sqlspec/adapters/oracledb/litestar/store.py +781 -0
  156. sqlspec/adapters/oracledb/migrations.py +535 -0
  157. sqlspec/adapters/oracledb/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  158. sqlspec/adapters/oracledb/type_converter.py +211 -0
  159. sqlspec/adapters/psqlpy/__init__.py +17 -0
  160. sqlspec/adapters/psqlpy/_typing.py +79 -0
  161. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  162. sqlspec/adapters/psqlpy/adk/store.py +766 -0
  163. sqlspec/adapters/psqlpy/config.py +304 -0
  164. sqlspec/adapters/psqlpy/core.cpython-310-aarch64-linux-gnu.so +0 -0
  165. sqlspec/adapters/psqlpy/core.py +480 -0
  166. sqlspec/adapters/psqlpy/data_dictionary.py +126 -0
  167. sqlspec/adapters/psqlpy/driver.py +438 -0
  168. sqlspec/adapters/psqlpy/events/__init__.py +6 -0
  169. sqlspec/adapters/psqlpy/events/backend.py +310 -0
  170. sqlspec/adapters/psqlpy/events/store.py +20 -0
  171. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  172. sqlspec/adapters/psqlpy/litestar/store.py +270 -0
  173. sqlspec/adapters/psqlpy/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  174. sqlspec/adapters/psqlpy/type_converter.py +113 -0
  175. sqlspec/adapters/psycopg/__init__.py +32 -0
  176. sqlspec/adapters/psycopg/_typing.py +164 -0
  177. sqlspec/adapters/psycopg/adk/__init__.py +10 -0
  178. sqlspec/adapters/psycopg/adk/store.py +1387 -0
  179. sqlspec/adapters/psycopg/config.py +576 -0
  180. sqlspec/adapters/psycopg/core.cpython-310-aarch64-linux-gnu.so +0 -0
  181. sqlspec/adapters/psycopg/core.py +450 -0
  182. sqlspec/adapters/psycopg/data_dictionary.py +289 -0
  183. sqlspec/adapters/psycopg/driver.py +975 -0
  184. sqlspec/adapters/psycopg/events/__init__.py +20 -0
  185. sqlspec/adapters/psycopg/events/backend.py +458 -0
  186. sqlspec/adapters/psycopg/events/store.py +42 -0
  187. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  188. sqlspec/adapters/psycopg/litestar/store.py +552 -0
  189. sqlspec/adapters/psycopg/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  190. sqlspec/adapters/psycopg/type_converter.py +93 -0
  191. sqlspec/adapters/pymysql/__init__.py +21 -0
  192. sqlspec/adapters/pymysql/_typing.py +71 -0
  193. sqlspec/adapters/pymysql/adk/__init__.py +5 -0
  194. sqlspec/adapters/pymysql/adk/store.py +540 -0
  195. sqlspec/adapters/pymysql/config.py +195 -0
  196. sqlspec/adapters/pymysql/core.cpython-310-aarch64-linux-gnu.so +0 -0
  197. sqlspec/adapters/pymysql/core.py +299 -0
  198. sqlspec/adapters/pymysql/data_dictionary.py +122 -0
  199. sqlspec/adapters/pymysql/driver.py +259 -0
  200. sqlspec/adapters/pymysql/events/__init__.py +5 -0
  201. sqlspec/adapters/pymysql/events/store.py +50 -0
  202. sqlspec/adapters/pymysql/litestar/__init__.py +5 -0
  203. sqlspec/adapters/pymysql/litestar/store.py +232 -0
  204. sqlspec/adapters/pymysql/pool.py +137 -0
  205. sqlspec/adapters/spanner/__init__.py +40 -0
  206. sqlspec/adapters/spanner/_typing.py +86 -0
  207. sqlspec/adapters/spanner/adk/__init__.py +5 -0
  208. sqlspec/adapters/spanner/adk/store.py +732 -0
  209. sqlspec/adapters/spanner/config.py +352 -0
  210. sqlspec/adapters/spanner/core.cpython-310-aarch64-linux-gnu.so +0 -0
  211. sqlspec/adapters/spanner/core.py +188 -0
  212. sqlspec/adapters/spanner/data_dictionary.py +120 -0
  213. sqlspec/adapters/spanner/dialect/__init__.py +6 -0
  214. sqlspec/adapters/spanner/dialect/_spangres.py +57 -0
  215. sqlspec/adapters/spanner/dialect/_spanner.py +130 -0
  216. sqlspec/adapters/spanner/driver.py +373 -0
  217. sqlspec/adapters/spanner/events/__init__.py +5 -0
  218. sqlspec/adapters/spanner/events/store.py +187 -0
  219. sqlspec/adapters/spanner/litestar/__init__.py +5 -0
  220. sqlspec/adapters/spanner/litestar/store.py +291 -0
  221. sqlspec/adapters/spanner/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  222. sqlspec/adapters/spanner/type_converter.py +331 -0
  223. sqlspec/adapters/sqlite/__init__.py +19 -0
  224. sqlspec/adapters/sqlite/_typing.py +80 -0
  225. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  226. sqlspec/adapters/sqlite/adk/store.py +958 -0
  227. sqlspec/adapters/sqlite/config.py +280 -0
  228. sqlspec/adapters/sqlite/core.cpython-310-aarch64-linux-gnu.so +0 -0
  229. sqlspec/adapters/sqlite/core.py +312 -0
  230. sqlspec/adapters/sqlite/data_dictionary.py +202 -0
  231. sqlspec/adapters/sqlite/driver.py +359 -0
  232. sqlspec/adapters/sqlite/events/__init__.py +5 -0
  233. sqlspec/adapters/sqlite/events/store.py +20 -0
  234. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  235. sqlspec/adapters/sqlite/litestar/store.py +316 -0
  236. sqlspec/adapters/sqlite/pool.py +198 -0
  237. sqlspec/adapters/sqlite/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  238. sqlspec/adapters/sqlite/type_converter.py +114 -0
  239. sqlspec/base.py +747 -0
  240. sqlspec/builder/__init__.py +179 -0
  241. sqlspec/builder/_base.cpython-310-aarch64-linux-gnu.so +0 -0
  242. sqlspec/builder/_base.py +1022 -0
  243. sqlspec/builder/_column.cpython-310-aarch64-linux-gnu.so +0 -0
  244. sqlspec/builder/_column.py +521 -0
  245. sqlspec/builder/_ddl.cpython-310-aarch64-linux-gnu.so +0 -0
  246. sqlspec/builder/_ddl.py +1642 -0
  247. sqlspec/builder/_delete.cpython-310-aarch64-linux-gnu.so +0 -0
  248. sqlspec/builder/_delete.py +95 -0
  249. sqlspec/builder/_dml.cpython-310-aarch64-linux-gnu.so +0 -0
  250. sqlspec/builder/_dml.py +365 -0
  251. sqlspec/builder/_explain.cpython-310-aarch64-linux-gnu.so +0 -0
  252. sqlspec/builder/_explain.py +579 -0
  253. sqlspec/builder/_expression_wrappers.cpython-310-aarch64-linux-gnu.so +0 -0
  254. sqlspec/builder/_expression_wrappers.py +46 -0
  255. sqlspec/builder/_factory.cpython-310-aarch64-linux-gnu.so +0 -0
  256. sqlspec/builder/_factory.py +1697 -0
  257. sqlspec/builder/_insert.cpython-310-aarch64-linux-gnu.so +0 -0
  258. sqlspec/builder/_insert.py +328 -0
  259. sqlspec/builder/_join.cpython-310-aarch64-linux-gnu.so +0 -0
  260. sqlspec/builder/_join.py +499 -0
  261. sqlspec/builder/_merge.cpython-310-aarch64-linux-gnu.so +0 -0
  262. sqlspec/builder/_merge.py +821 -0
  263. sqlspec/builder/_parsing_utils.cpython-310-aarch64-linux-gnu.so +0 -0
  264. sqlspec/builder/_parsing_utils.py +297 -0
  265. sqlspec/builder/_select.cpython-310-aarch64-linux-gnu.so +0 -0
  266. sqlspec/builder/_select.py +1660 -0
  267. sqlspec/builder/_temporal.cpython-310-aarch64-linux-gnu.so +0 -0
  268. sqlspec/builder/_temporal.py +139 -0
  269. sqlspec/builder/_update.cpython-310-aarch64-linux-gnu.so +0 -0
  270. sqlspec/builder/_update.py +173 -0
  271. sqlspec/builder/_vector_expressions.py +267 -0
  272. sqlspec/cli.py +911 -0
  273. sqlspec/config.py +1755 -0
  274. sqlspec/core/__init__.py +374 -0
  275. sqlspec/core/_correlation.cpython-310-aarch64-linux-gnu.so +0 -0
  276. sqlspec/core/_correlation.py +176 -0
  277. sqlspec/core/cache.cpython-310-aarch64-linux-gnu.so +0 -0
  278. sqlspec/core/cache.py +1069 -0
  279. sqlspec/core/compiler.cpython-310-aarch64-linux-gnu.so +0 -0
  280. sqlspec/core/compiler.py +954 -0
  281. sqlspec/core/explain.cpython-310-aarch64-linux-gnu.so +0 -0
  282. sqlspec/core/explain.py +275 -0
  283. sqlspec/core/filters.cpython-310-aarch64-linux-gnu.so +0 -0
  284. sqlspec/core/filters.py +952 -0
  285. sqlspec/core/hashing.cpython-310-aarch64-linux-gnu.so +0 -0
  286. sqlspec/core/hashing.py +262 -0
  287. sqlspec/core/metrics.cpython-310-aarch64-linux-gnu.so +0 -0
  288. sqlspec/core/metrics.py +83 -0
  289. sqlspec/core/parameters/__init__.py +71 -0
  290. sqlspec/core/parameters/_alignment.cpython-310-aarch64-linux-gnu.so +0 -0
  291. sqlspec/core/parameters/_alignment.py +270 -0
  292. sqlspec/core/parameters/_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  293. sqlspec/core/parameters/_converter.py +543 -0
  294. sqlspec/core/parameters/_processor.cpython-310-aarch64-linux-gnu.so +0 -0
  295. sqlspec/core/parameters/_processor.py +505 -0
  296. sqlspec/core/parameters/_registry.cpython-310-aarch64-linux-gnu.so +0 -0
  297. sqlspec/core/parameters/_registry.py +206 -0
  298. sqlspec/core/parameters/_transformers.cpython-310-aarch64-linux-gnu.so +0 -0
  299. sqlspec/core/parameters/_transformers.py +292 -0
  300. sqlspec/core/parameters/_types.cpython-310-aarch64-linux-gnu.so +0 -0
  301. sqlspec/core/parameters/_types.py +499 -0
  302. sqlspec/core/parameters/_validator.cpython-310-aarch64-linux-gnu.so +0 -0
  303. sqlspec/core/parameters/_validator.py +180 -0
  304. sqlspec/core/pipeline.cpython-310-aarch64-linux-gnu.so +0 -0
  305. sqlspec/core/pipeline.py +319 -0
  306. sqlspec/core/query_modifiers.cpython-310-aarch64-linux-gnu.so +0 -0
  307. sqlspec/core/query_modifiers.py +437 -0
  308. sqlspec/core/result/__init__.py +23 -0
  309. sqlspec/core/result/_base.cpython-310-aarch64-linux-gnu.so +0 -0
  310. sqlspec/core/result/_base.py +1121 -0
  311. sqlspec/core/result/_io.cpython-310-aarch64-linux-gnu.so +0 -0
  312. sqlspec/core/result/_io.py +28 -0
  313. sqlspec/core/splitter.cpython-310-aarch64-linux-gnu.so +0 -0
  314. sqlspec/core/splitter.py +966 -0
  315. sqlspec/core/stack.cpython-310-aarch64-linux-gnu.so +0 -0
  316. sqlspec/core/stack.py +163 -0
  317. sqlspec/core/statement.cpython-310-aarch64-linux-gnu.so +0 -0
  318. sqlspec/core/statement.py +1503 -0
  319. sqlspec/core/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
  320. sqlspec/core/type_converter.py +339 -0
  321. sqlspec/data_dictionary/__init__.py +22 -0
  322. sqlspec/data_dictionary/_loader.py +123 -0
  323. sqlspec/data_dictionary/_registry.cpython-310-aarch64-linux-gnu.so +0 -0
  324. sqlspec/data_dictionary/_registry.py +74 -0
  325. sqlspec/data_dictionary/_types.cpython-310-aarch64-linux-gnu.so +0 -0
  326. sqlspec/data_dictionary/_types.py +121 -0
  327. sqlspec/data_dictionary/dialects/__init__.py +21 -0
  328. sqlspec/data_dictionary/dialects/bigquery.cpython-310-aarch64-linux-gnu.so +0 -0
  329. sqlspec/data_dictionary/dialects/bigquery.py +49 -0
  330. sqlspec/data_dictionary/dialects/cockroachdb.cpython-310-aarch64-linux-gnu.so +0 -0
  331. sqlspec/data_dictionary/dialects/cockroachdb.py +43 -0
  332. sqlspec/data_dictionary/dialects/duckdb.cpython-310-aarch64-linux-gnu.so +0 -0
  333. sqlspec/data_dictionary/dialects/duckdb.py +47 -0
  334. sqlspec/data_dictionary/dialects/mysql.cpython-310-aarch64-linux-gnu.so +0 -0
  335. sqlspec/data_dictionary/dialects/mysql.py +42 -0
  336. sqlspec/data_dictionary/dialects/oracle.cpython-310-aarch64-linux-gnu.so +0 -0
  337. sqlspec/data_dictionary/dialects/oracle.py +34 -0
  338. sqlspec/data_dictionary/dialects/postgres.cpython-310-aarch64-linux-gnu.so +0 -0
  339. sqlspec/data_dictionary/dialects/postgres.py +46 -0
  340. sqlspec/data_dictionary/dialects/spanner.cpython-310-aarch64-linux-gnu.so +0 -0
  341. sqlspec/data_dictionary/dialects/spanner.py +37 -0
  342. sqlspec/data_dictionary/dialects/sqlite.cpython-310-aarch64-linux-gnu.so +0 -0
  343. sqlspec/data_dictionary/dialects/sqlite.py +42 -0
  344. sqlspec/data_dictionary/sql/.gitkeep +0 -0
  345. sqlspec/data_dictionary/sql/bigquery/columns.sql +23 -0
  346. sqlspec/data_dictionary/sql/bigquery/foreign_keys.sql +34 -0
  347. sqlspec/data_dictionary/sql/bigquery/indexes.sql +19 -0
  348. sqlspec/data_dictionary/sql/bigquery/tables.sql +33 -0
  349. sqlspec/data_dictionary/sql/bigquery/version.sql +3 -0
  350. sqlspec/data_dictionary/sql/cockroachdb/columns.sql +34 -0
  351. sqlspec/data_dictionary/sql/cockroachdb/foreign_keys.sql +40 -0
  352. sqlspec/data_dictionary/sql/cockroachdb/indexes.sql +32 -0
  353. sqlspec/data_dictionary/sql/cockroachdb/tables.sql +44 -0
  354. sqlspec/data_dictionary/sql/cockroachdb/version.sql +3 -0
  355. sqlspec/data_dictionary/sql/duckdb/columns.sql +23 -0
  356. sqlspec/data_dictionary/sql/duckdb/foreign_keys.sql +36 -0
  357. sqlspec/data_dictionary/sql/duckdb/indexes.sql +19 -0
  358. sqlspec/data_dictionary/sql/duckdb/tables.sql +38 -0
  359. sqlspec/data_dictionary/sql/duckdb/version.sql +3 -0
  360. sqlspec/data_dictionary/sql/mysql/columns.sql +23 -0
  361. sqlspec/data_dictionary/sql/mysql/foreign_keys.sql +28 -0
  362. sqlspec/data_dictionary/sql/mysql/indexes.sql +26 -0
  363. sqlspec/data_dictionary/sql/mysql/tables.sql +33 -0
  364. sqlspec/data_dictionary/sql/mysql/version.sql +3 -0
  365. sqlspec/data_dictionary/sql/oracle/columns.sql +23 -0
  366. sqlspec/data_dictionary/sql/oracle/foreign_keys.sql +48 -0
  367. sqlspec/data_dictionary/sql/oracle/indexes.sql +44 -0
  368. sqlspec/data_dictionary/sql/oracle/tables.sql +25 -0
  369. sqlspec/data_dictionary/sql/oracle/version.sql +20 -0
  370. sqlspec/data_dictionary/sql/postgres/columns.sql +34 -0
  371. sqlspec/data_dictionary/sql/postgres/foreign_keys.sql +40 -0
  372. sqlspec/data_dictionary/sql/postgres/indexes.sql +56 -0
  373. sqlspec/data_dictionary/sql/postgres/tables.sql +44 -0
  374. sqlspec/data_dictionary/sql/postgres/version.sql +3 -0
  375. sqlspec/data_dictionary/sql/spanner/columns.sql +23 -0
  376. sqlspec/data_dictionary/sql/spanner/foreign_keys.sql +70 -0
  377. sqlspec/data_dictionary/sql/spanner/indexes.sql +30 -0
  378. sqlspec/data_dictionary/sql/spanner/tables.sql +9 -0
  379. sqlspec/data_dictionary/sql/spanner/version.sql +3 -0
  380. sqlspec/data_dictionary/sql/sqlite/columns.sql +23 -0
  381. sqlspec/data_dictionary/sql/sqlite/foreign_keys.sql +22 -0
  382. sqlspec/data_dictionary/sql/sqlite/indexes.sql +7 -0
  383. sqlspec/data_dictionary/sql/sqlite/tables.sql +28 -0
  384. sqlspec/data_dictionary/sql/sqlite/version.sql +3 -0
  385. sqlspec/driver/__init__.py +32 -0
  386. sqlspec/driver/_async.cpython-310-aarch64-linux-gnu.so +0 -0
  387. sqlspec/driver/_async.py +1737 -0
  388. sqlspec/driver/_common.cpython-310-aarch64-linux-gnu.so +0 -0
  389. sqlspec/driver/_common.py +1478 -0
  390. sqlspec/driver/_sql_helpers.cpython-310-aarch64-linux-gnu.so +0 -0
  391. sqlspec/driver/_sql_helpers.py +148 -0
  392. sqlspec/driver/_storage_helpers.cpython-310-aarch64-linux-gnu.so +0 -0
  393. sqlspec/driver/_storage_helpers.py +144 -0
  394. sqlspec/driver/_sync.cpython-310-aarch64-linux-gnu.so +0 -0
  395. sqlspec/driver/_sync.py +1710 -0
  396. sqlspec/exceptions.py +338 -0
  397. sqlspec/extensions/__init__.py +0 -0
  398. sqlspec/extensions/adk/__init__.py +70 -0
  399. sqlspec/extensions/adk/_types.py +51 -0
  400. sqlspec/extensions/adk/converters.py +172 -0
  401. sqlspec/extensions/adk/memory/__init__.py +69 -0
  402. sqlspec/extensions/adk/memory/_types.py +30 -0
  403. sqlspec/extensions/adk/memory/converters.py +149 -0
  404. sqlspec/extensions/adk/memory/service.py +217 -0
  405. sqlspec/extensions/adk/memory/store.py +569 -0
  406. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +246 -0
  407. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  408. sqlspec/extensions/adk/service.py +225 -0
  409. sqlspec/extensions/adk/store.py +567 -0
  410. sqlspec/extensions/events/__init__.py +51 -0
  411. sqlspec/extensions/events/_channel.py +703 -0
  412. sqlspec/extensions/events/_hints.py +45 -0
  413. sqlspec/extensions/events/_models.py +23 -0
  414. sqlspec/extensions/events/_payload.py +69 -0
  415. sqlspec/extensions/events/_protocols.py +134 -0
  416. sqlspec/extensions/events/_queue.py +461 -0
  417. sqlspec/extensions/events/_store.py +209 -0
  418. sqlspec/extensions/events/migrations/0001_create_event_queue.py +59 -0
  419. sqlspec/extensions/events/migrations/__init__.py +3 -0
  420. sqlspec/extensions/fastapi/__init__.py +19 -0
  421. sqlspec/extensions/fastapi/extension.py +351 -0
  422. sqlspec/extensions/fastapi/providers.py +607 -0
  423. sqlspec/extensions/flask/__init__.py +37 -0
  424. sqlspec/extensions/flask/_state.py +76 -0
  425. sqlspec/extensions/flask/_utils.py +71 -0
  426. sqlspec/extensions/flask/extension.py +519 -0
  427. sqlspec/extensions/litestar/__init__.py +28 -0
  428. sqlspec/extensions/litestar/_utils.py +52 -0
  429. sqlspec/extensions/litestar/channels.py +165 -0
  430. sqlspec/extensions/litestar/cli.py +102 -0
  431. sqlspec/extensions/litestar/config.py +90 -0
  432. sqlspec/extensions/litestar/handlers.py +316 -0
  433. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  434. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  435. sqlspec/extensions/litestar/plugin.py +671 -0
  436. sqlspec/extensions/litestar/providers.py +526 -0
  437. sqlspec/extensions/litestar/store.py +296 -0
  438. sqlspec/extensions/otel/__init__.py +58 -0
  439. sqlspec/extensions/prometheus/__init__.py +113 -0
  440. sqlspec/extensions/starlette/__init__.py +19 -0
  441. sqlspec/extensions/starlette/_state.py +30 -0
  442. sqlspec/extensions/starlette/_utils.py +96 -0
  443. sqlspec/extensions/starlette/extension.py +346 -0
  444. sqlspec/extensions/starlette/middleware.py +235 -0
  445. sqlspec/loader.cpython-310-aarch64-linux-gnu.so +0 -0
  446. sqlspec/loader.py +702 -0
  447. sqlspec/migrations/__init__.py +36 -0
  448. sqlspec/migrations/base.py +731 -0
  449. sqlspec/migrations/commands.py +1232 -0
  450. sqlspec/migrations/context.py +157 -0
  451. sqlspec/migrations/fix.py +204 -0
  452. sqlspec/migrations/loaders.py +443 -0
  453. sqlspec/migrations/runner.py +1172 -0
  454. sqlspec/migrations/templates.py +234 -0
  455. sqlspec/migrations/tracker.py +611 -0
  456. sqlspec/migrations/utils.py +256 -0
  457. sqlspec/migrations/validation.py +207 -0
  458. sqlspec/migrations/version.py +446 -0
  459. sqlspec/observability/__init__.py +55 -0
  460. sqlspec/observability/_common.cpython-310-aarch64-linux-gnu.so +0 -0
  461. sqlspec/observability/_common.py +77 -0
  462. sqlspec/observability/_config.cpython-310-aarch64-linux-gnu.so +0 -0
  463. sqlspec/observability/_config.py +348 -0
  464. sqlspec/observability/_diagnostics.cpython-310-aarch64-linux-gnu.so +0 -0
  465. sqlspec/observability/_diagnostics.py +74 -0
  466. sqlspec/observability/_dispatcher.cpython-310-aarch64-linux-gnu.so +0 -0
  467. sqlspec/observability/_dispatcher.py +152 -0
  468. sqlspec/observability/_formatters/__init__.py +13 -0
  469. sqlspec/observability/_formatters/_aws.cpython-310-aarch64-linux-gnu.so +0 -0
  470. sqlspec/observability/_formatters/_aws.py +102 -0
  471. sqlspec/observability/_formatters/_azure.cpython-310-aarch64-linux-gnu.so +0 -0
  472. sqlspec/observability/_formatters/_azure.py +96 -0
  473. sqlspec/observability/_formatters/_base.cpython-310-aarch64-linux-gnu.so +0 -0
  474. sqlspec/observability/_formatters/_base.py +57 -0
  475. sqlspec/observability/_formatters/_gcp.cpython-310-aarch64-linux-gnu.so +0 -0
  476. sqlspec/observability/_formatters/_gcp.py +131 -0
  477. sqlspec/observability/_formatting.py +58 -0
  478. sqlspec/observability/_observer.cpython-310-aarch64-linux-gnu.so +0 -0
  479. sqlspec/observability/_observer.py +357 -0
  480. sqlspec/observability/_runtime.cpython-310-aarch64-linux-gnu.so +0 -0
  481. sqlspec/observability/_runtime.py +420 -0
  482. sqlspec/observability/_sampling.cpython-310-aarch64-linux-gnu.so +0 -0
  483. sqlspec/observability/_sampling.py +188 -0
  484. sqlspec/observability/_spans.cpython-310-aarch64-linux-gnu.so +0 -0
  485. sqlspec/observability/_spans.py +161 -0
  486. sqlspec/protocols.py +916 -0
  487. sqlspec/py.typed +0 -0
  488. sqlspec/storage/__init__.py +48 -0
  489. sqlspec/storage/_utils.py +104 -0
  490. sqlspec/storage/backends/__init__.py +1 -0
  491. sqlspec/storage/backends/base.py +253 -0
  492. sqlspec/storage/backends/fsspec.py +529 -0
  493. sqlspec/storage/backends/local.py +441 -0
  494. sqlspec/storage/backends/obstore.py +916 -0
  495. sqlspec/storage/errors.py +104 -0
  496. sqlspec/storage/pipeline.py +582 -0
  497. sqlspec/storage/registry.py +301 -0
  498. sqlspec/typing.py +395 -0
  499. sqlspec/utils/__init__.py +7 -0
  500. sqlspec/utils/arrow_helpers.py +318 -0
  501. sqlspec/utils/config_tools.py +332 -0
  502. sqlspec/utils/correlation.cpython-310-aarch64-linux-gnu.so +0 -0
  503. sqlspec/utils/correlation.py +134 -0
  504. sqlspec/utils/deprecation.py +190 -0
  505. sqlspec/utils/fixtures.cpython-310-aarch64-linux-gnu.so +0 -0
  506. sqlspec/utils/fixtures.py +258 -0
  507. sqlspec/utils/logging.py +222 -0
  508. sqlspec/utils/module_loader.py +306 -0
  509. sqlspec/utils/portal.cpython-310-aarch64-linux-gnu.so +0 -0
  510. sqlspec/utils/portal.py +375 -0
  511. sqlspec/utils/schema.cpython-310-aarch64-linux-gnu.so +0 -0
  512. sqlspec/utils/schema.py +485 -0
  513. sqlspec/utils/serializers.cpython-310-aarch64-linux-gnu.so +0 -0
  514. sqlspec/utils/serializers.py +408 -0
  515. sqlspec/utils/singleton.cpython-310-aarch64-linux-gnu.so +0 -0
  516. sqlspec/utils/singleton.py +41 -0
  517. sqlspec/utils/sync_tools.cpython-310-aarch64-linux-gnu.so +0 -0
  518. sqlspec/utils/sync_tools.py +311 -0
  519. sqlspec/utils/text.cpython-310-aarch64-linux-gnu.so +0 -0
  520. sqlspec/utils/text.py +108 -0
  521. sqlspec/utils/type_converters.cpython-310-aarch64-linux-gnu.so +0 -0
  522. sqlspec/utils/type_converters.py +128 -0
  523. sqlspec/utils/type_guards.cpython-310-aarch64-linux-gnu.so +0 -0
  524. sqlspec/utils/type_guards.py +1360 -0
  525. sqlspec/utils/uuids.cpython-310-aarch64-linux-gnu.so +0 -0
  526. sqlspec/utils/uuids.py +225 -0
  527. sqlspec-0.36.0.dist-info/METADATA +205 -0
  528. sqlspec-0.36.0.dist-info/RECORD +531 -0
  529. sqlspec-0.36.0.dist-info/WHEEL +7 -0
  530. sqlspec-0.36.0.dist-info/entry_points.txt +2 -0
  531. sqlspec-0.36.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,1232 @@
1
+ """Migration command implementations for SQLSpec.
2
+
3
+ This module provides the main command interface for database migrations.
4
+ """
5
+
6
+ import functools
7
+ import inspect
8
+ import logging
9
+ import time
10
+ from collections.abc import Awaitable, Callable
11
+ from typing import TYPE_CHECKING, Any, ParamSpec, TypeVar, cast
12
+
13
+ from rich.console import Console
14
+ from rich.table import Table
15
+
16
+ from sqlspec.builder import sql
17
+ from sqlspec.migrations.base import BaseMigrationCommands
18
+ from sqlspec.migrations.context import MigrationContext
19
+ from sqlspec.migrations.fix import MigrationFixer
20
+ from sqlspec.migrations.runner import AsyncMigrationRunner, SyncMigrationRunner
21
+ from sqlspec.migrations.utils import create_migration_file
22
+ from sqlspec.migrations.validation import validate_migration_order
23
+ from sqlspec.migrations.version import generate_conversion_map, generate_timestamp_version, parse_version
24
+ from sqlspec.observability import resolve_db_system
25
+ from sqlspec.utils.logging import get_logger, log_with_context
26
+
27
+ if TYPE_CHECKING:
28
+ from pathlib import Path
29
+
30
+ from sqlspec.config import AsyncConfigT, SyncConfigT
31
+
32
+ __all__ = ("AsyncMigrationCommands", "SyncMigrationCommands", "create_migration_commands")
33
+
34
+ logger = get_logger("sqlspec.migrations.commands")
35
+ console = Console()
36
+ P = ParamSpec("P")
37
+ R = TypeVar("R")
38
+
39
+
40
+ MetadataBuilder = Callable[[dict[str, Any]], tuple[str | None, dict[str, Any]]]
41
+
42
+
43
+ def _bind_arguments(signature: inspect.Signature, args: tuple[Any, ...], kwargs: dict[str, Any]) -> dict[str, Any]:
44
+ bound = signature.bind_partial(*args, **kwargs)
45
+ arguments = dict(bound.arguments)
46
+ arguments.pop("self", None)
47
+ return arguments
48
+
49
+
50
+ def _with_command_span(
51
+ event: str, metadata_fn: "MetadataBuilder | None" = None, *, dry_run_param: str | None = "dry_run"
52
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]:
53
+ """Attach span lifecycle and command metric management to command methods."""
54
+
55
+ metric_prefix = f"migrations.command.{event}"
56
+
57
+ def decorator(func: Callable[P, R]) -> Callable[P, R]:
58
+ signature = inspect.signature(func)
59
+
60
+ def _prepare(self: Any, args: tuple[Any, ...], kwargs: dict[str, Any]) -> tuple[Any, bool, Any]:
61
+ runtime = self._runtime
62
+ metadata_args = _bind_arguments(signature, args, kwargs)
63
+ dry_run = False
64
+ if dry_run_param is not None:
65
+ dry_run = bool(metadata_args.get(dry_run_param, False))
66
+ metadata: dict[str, Any] | None = None
67
+ version: str | None = None
68
+ span = None
69
+ if runtime is not None:
70
+ runtime.increment_metric(f"{metric_prefix}.invocations")
71
+ if dry_run_param is not None and dry_run:
72
+ runtime.increment_metric(f"{metric_prefix}.dry_run")
73
+ if metadata_fn is not None:
74
+ version, metadata = metadata_fn(metadata_args)
75
+ span = runtime.start_migration_span(f"command.{event}", version=version, metadata=metadata)
76
+ return runtime, dry_run, span
77
+
78
+ def _finalize(
79
+ self: Any,
80
+ runtime: Any,
81
+ span: Any,
82
+ start: float,
83
+ error: "Exception | None",
84
+ recorded_error: bool,
85
+ dry_run: bool,
86
+ ) -> None:
87
+ command_error = self._last_command_error
88
+ self._last_command_error = None
89
+ command_metrics = self._last_command_metrics
90
+ self._last_command_metrics = None
91
+ if runtime is None:
92
+ return
93
+ if command_error is not None and not recorded_error:
94
+ runtime.increment_metric(f"{metric_prefix}.errors")
95
+ if not dry_run and command_metrics:
96
+ for metric, value in command_metrics.items():
97
+ runtime.increment_metric(f"{metric_prefix}.{metric}", value)
98
+ duration_ms = int((time.perf_counter() - start) * 1000)
99
+ runtime.end_migration_span(span, duration_ms=duration_ms, error=error or command_error)
100
+
101
+ if inspect.iscoroutinefunction(func):
102
+
103
+ @functools.wraps(func)
104
+ async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
105
+ self = args[0]
106
+ runtime, dry_run, span = _prepare(self, args, kwargs)
107
+ start = time.perf_counter()
108
+ error: Exception | None = None
109
+ error_recorded = False
110
+ try:
111
+ async_func = cast("Callable[P, Awaitable[R]]", func)
112
+ return await async_func(*args, **kwargs)
113
+ except Exception as exc: # pragma: no cover - passthrough
114
+ error = exc
115
+ if runtime is not None:
116
+ runtime.increment_metric(f"{metric_prefix}.errors")
117
+ error_recorded = True
118
+ raise
119
+ finally:
120
+ _finalize(self, runtime, span, start, error, error_recorded, dry_run)
121
+
122
+ return cast("Callable[P, R]", async_wrapper)
123
+
124
+ @functools.wraps(func)
125
+ def sync_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
126
+ self = args[0]
127
+ runtime, dry_run, span = _prepare(self, args, kwargs)
128
+ start = time.perf_counter()
129
+ error: Exception | None = None
130
+ error_recorded = False
131
+ try:
132
+ return func(*args, **kwargs)
133
+ except Exception as exc: # pragma: no cover - passthrough
134
+ error = exc
135
+ if runtime is not None:
136
+ runtime.increment_metric(f"{metric_prefix}.errors")
137
+ error_recorded = True
138
+ raise
139
+ finally:
140
+ _finalize(self, runtime, span, start, error, error_recorded, dry_run)
141
+
142
+ return cast("Callable[P, R]", sync_wrapper)
143
+
144
+ return decorator
145
+
146
+
147
+ def _upgrade_metadata(args: dict[str, Any]) -> tuple[str | None, dict[str, Any]]:
148
+ revision = cast("str | None", args.get("revision"))
149
+ metadata = {"dry_run": str(args.get("dry_run", False)).lower()}
150
+ return revision, metadata
151
+
152
+
153
+ def _downgrade_metadata(args: dict[str, Any]) -> tuple[str | None, dict[str, Any]]:
154
+ revision = cast("str | None", args.get("revision"))
155
+ metadata = {"dry_run": str(args.get("dry_run", False)).lower()}
156
+ return revision, metadata
157
+
158
+
159
+ class SyncMigrationCommands(BaseMigrationCommands["SyncConfigT", Any]):
160
+ """Synchronous migration commands."""
161
+
162
+ def __init__(self, config: "SyncConfigT") -> None:
163
+ """Initialize migration commands.
164
+
165
+ Args:
166
+ config: The SQLSpec configuration.
167
+ """
168
+ super().__init__(config)
169
+ self.tracker = config.migration_tracker_type(self.version_table)
170
+
171
+ # Create context with extension configurations
172
+ context = MigrationContext.from_config(config)
173
+ context.extension_config = self.extension_configs
174
+
175
+ self.runner = SyncMigrationRunner(
176
+ self.migrations_path,
177
+ self._discover_extension_migrations(),
178
+ context,
179
+ self.extension_configs,
180
+ runtime=self._runtime,
181
+ description_hints=self._template_settings.description_hints,
182
+ )
183
+
184
+ def init(self, directory: str, package: bool = True) -> None:
185
+ """Initialize migration directory structure.
186
+
187
+ Args:
188
+ directory: Directory to initialize migrations in.
189
+ package: Whether to create __init__.py file.
190
+ """
191
+ self.init_directory(directory, package)
192
+
193
+ def current(self, verbose: bool = False) -> "str | None":
194
+ """Show current migration version.
195
+
196
+ Args:
197
+ verbose: Whether to show detailed migration history.
198
+
199
+ Returns:
200
+ The current migration version or None if no migrations applied.
201
+ """
202
+ with self.config.provide_session() as driver:
203
+ self.tracker.ensure_tracking_table(driver)
204
+
205
+ current = self.tracker.get_current_version(driver)
206
+ if not current:
207
+ log_with_context(
208
+ logger,
209
+ logging.DEBUG,
210
+ "migration.list",
211
+ db_system=resolve_db_system(type(driver).__name__),
212
+ current_version=None,
213
+ applied_count=0,
214
+ verbose=verbose,
215
+ status="empty",
216
+ )
217
+ console.print("[yellow]No migrations applied yet[/]")
218
+ return None
219
+
220
+ console.print(f"[green]Current version:[/] {current}")
221
+
222
+ applied: list[dict[str, Any]] = []
223
+ if verbose:
224
+ applied = self.tracker.get_applied_migrations(driver)
225
+
226
+ table = Table(title="Applied Migrations")
227
+ table.add_column("Version", style="cyan")
228
+ table.add_column("Description")
229
+ table.add_column("Applied At")
230
+ table.add_column("Time (ms)", justify="right")
231
+ table.add_column("Applied By")
232
+
233
+ for migration in applied:
234
+ table.add_row(
235
+ migration["version_num"],
236
+ migration.get("description", ""),
237
+ str(migration.get("applied_at", "")),
238
+ str(migration.get("execution_time_ms", "")),
239
+ migration.get("applied_by", ""),
240
+ )
241
+
242
+ console.print(table)
243
+
244
+ applied_count = len(applied) if verbose else None
245
+ log_with_context(
246
+ logger,
247
+ logging.DEBUG,
248
+ "migration.list",
249
+ db_system=resolve_db_system(type(driver).__name__),
250
+ current_version=current,
251
+ applied_count=applied_count,
252
+ verbose=verbose,
253
+ status="complete",
254
+ )
255
+ return cast("str | None", current)
256
+
257
+ def _load_single_migration_checksum(self, version: str, file_path: "Path") -> "tuple[str, tuple[str, Path]] | None":
258
+ """Load checksum for a single migration.
259
+
260
+ Args:
261
+ version: Migration version.
262
+ file_path: Path to migration file.
263
+
264
+ Returns:
265
+ Tuple of (version, (checksum, file_path)) or None if load fails.
266
+ """
267
+ try:
268
+ migration = self.runner.load_migration(file_path, version)
269
+ return (version, (migration["checksum"], file_path))
270
+ except Exception as exc:
271
+ log_with_context(
272
+ logger,
273
+ logging.DEBUG,
274
+ "migration.list",
275
+ db_system=resolve_db_system(type(self.config).__name__),
276
+ version=version,
277
+ file_path=str(file_path),
278
+ error_type=type(exc).__name__,
279
+ status="failed",
280
+ operation="load_checksum",
281
+ )
282
+ return None
283
+
284
+ def _load_migration_checksums(self, all_migrations: "list[tuple[str, Path]]") -> "dict[str, tuple[str, Path]]":
285
+ """Load checksums for all migrations.
286
+
287
+ Args:
288
+ all_migrations: List of (version, file_path) tuples.
289
+
290
+ Returns:
291
+ Dictionary mapping version to (checksum, file_path) tuples.
292
+ """
293
+ file_checksums = {}
294
+ for version, file_path in all_migrations:
295
+ result = self._load_single_migration_checksum(version, file_path)
296
+ if result:
297
+ file_checksums[result[0]] = result[1]
298
+ return file_checksums
299
+
300
+ def _synchronize_version_records(self, driver: Any) -> int:
301
+ """Synchronize database version records with migration files.
302
+
303
+ Auto-updates DB tracking when migrations have been renamed by fix command.
304
+ This allows developers to just run upgrade after pulling changes without
305
+ manually running fix.
306
+
307
+ Validates checksums match before updating to prevent incorrect matches.
308
+
309
+ Args:
310
+ driver: Database driver instance.
311
+
312
+ Returns:
313
+ Number of version records updated.
314
+ """
315
+ all_migrations = self.runner.get_migration_files()
316
+
317
+ try:
318
+ applied_migrations = self.tracker.get_applied_migrations(driver)
319
+ except Exception as exc:
320
+ log_with_context(
321
+ logger,
322
+ logging.DEBUG,
323
+ "migration.list",
324
+ db_system=resolve_db_system(type(driver).__name__),
325
+ error_type=type(exc).__name__,
326
+ status="failed",
327
+ operation="applied_fetch",
328
+ )
329
+ return 0
330
+
331
+ applied_map = {m["version_num"]: m for m in applied_migrations}
332
+
333
+ conversion_map = generate_conversion_map(all_migrations)
334
+
335
+ updated_count = 0
336
+ if conversion_map:
337
+ for old_version, new_version in conversion_map.items():
338
+ if old_version in applied_map and new_version not in applied_map:
339
+ applied_checksum = applied_map[old_version]["checksum"]
340
+
341
+ file_path = next((path for v, path in all_migrations if v == new_version), None)
342
+ if file_path:
343
+ migration = self.runner.load_migration(file_path, new_version)
344
+ if migration["checksum"] == applied_checksum:
345
+ self.tracker.update_version_record(driver, old_version, new_version)
346
+ console.print(f" [dim]Reconciled version:[/] {old_version} → {new_version}")
347
+ updated_count += 1
348
+ else:
349
+ console.print(
350
+ f" [yellow]Warning: Checksum mismatch for {old_version} → {new_version}, skipping auto-sync[/]"
351
+ )
352
+ else:
353
+ file_checksums = self._load_migration_checksums(all_migrations)
354
+
355
+ for applied_version, applied_record in applied_map.items():
356
+ for file_version, (file_checksum, _) in file_checksums.items():
357
+ if file_version not in applied_map and applied_record["checksum"] == file_checksum:
358
+ self.tracker.update_version_record(driver, applied_version, file_version)
359
+ console.print(f" [dim]Reconciled version:[/] {applied_version} → {file_version}")
360
+ updated_count += 1
361
+ break
362
+
363
+ if updated_count > 0:
364
+ console.print(f"[cyan]Reconciled {updated_count} version record(s)[/]")
365
+
366
+ return updated_count
367
+
368
+ @_with_command_span("upgrade", metadata_fn=_upgrade_metadata)
369
+ def upgrade(
370
+ self, revision: str = "head", allow_missing: bool = False, auto_sync: bool = True, dry_run: bool = False
371
+ ) -> None:
372
+ """Upgrade to a target revision.
373
+
374
+ Validates migration order and warns if out-of-order migrations are detected.
375
+ Out-of-order migrations can occur when branches merge in different orders
376
+ across environments.
377
+
378
+ Args:
379
+ revision: Target revision or "head" for latest.
380
+ allow_missing: If True, allow out-of-order migrations even in strict mode.
381
+ Defaults to False.
382
+ auto_sync: If True, automatically reconcile renamed migrations in database.
383
+ Defaults to True. Can be disabled via --no-auto-sync flag.
384
+ dry_run: If True, show what would be done without making changes.
385
+ """
386
+ runtime = self._runtime
387
+ applied_count = 0
388
+
389
+ if dry_run:
390
+ console.print("[bold yellow]DRY RUN MODE:[/] No database changes will be applied\n")
391
+
392
+ with self.config.provide_session() as driver:
393
+ self.tracker.ensure_tracking_table(driver)
394
+
395
+ if auto_sync:
396
+ config_auto_sync = self.config.migration_config.get("auto_sync", True)
397
+ if config_auto_sync:
398
+ self._synchronize_version_records(driver)
399
+
400
+ applied_migrations = self.tracker.get_applied_migrations(driver)
401
+ applied_versions = [m["version_num"] for m in applied_migrations]
402
+ applied_set = set(applied_versions)
403
+
404
+ all_migrations = self.runner.get_migration_files()
405
+ if runtime is not None:
406
+ runtime.increment_metric("migrations.command.upgrade.available", float(len(all_migrations)))
407
+
408
+ pending = []
409
+ for version, file_path in all_migrations:
410
+ if version not in applied_set:
411
+ if revision == "head":
412
+ pending.append((version, file_path))
413
+ else:
414
+ parsed_version = parse_version(version)
415
+ parsed_revision = parse_version(revision)
416
+ if parsed_version <= parsed_revision:
417
+ pending.append((version, file_path))
418
+
419
+ if runtime is not None:
420
+ runtime.increment_metric("migrations.command.upgrade.pending", float(len(pending)))
421
+
422
+ if not pending:
423
+ if not all_migrations:
424
+ console.print(
425
+ "[yellow]No migrations found. Create your first migration with 'sqlspec create-migration'.[/]"
426
+ )
427
+ else:
428
+ console.print("[green]Already at latest version[/]")
429
+ return
430
+ pending_versions = [v for v, _ in pending]
431
+
432
+ migration_config = cast("dict[str, Any]", self.config.migration_config) or {}
433
+ strict_ordering = migration_config.get("strict_ordering", False) and not allow_missing
434
+
435
+ validate_migration_order(pending_versions, applied_versions, strict_ordering)
436
+
437
+ console.print(f"[yellow]Found {len(pending)} pending migrations[/]")
438
+
439
+ for version, file_path in pending:
440
+ migration = self.runner.load_migration(file_path, version)
441
+
442
+ action_verb = "Would apply" if dry_run else "Applying"
443
+ console.print(f"\n[cyan]{action_verb} {version}:[/] {migration['description']}")
444
+
445
+ if dry_run:
446
+ console.print(f"[dim]Migration file: {file_path}[/]")
447
+ continue
448
+
449
+ try:
450
+
451
+ def record_version(exec_time: int, migration: "dict[str, Any]" = migration) -> None:
452
+ self.tracker.record_migration(
453
+ driver, migration["version"], migration["description"], exec_time, migration["checksum"]
454
+ )
455
+
456
+ _, execution_time = self.runner.execute_upgrade(driver, migration, on_success=record_version)
457
+ applied_count += 1
458
+ console.print(f"[green]✓ Applied in {execution_time}ms[/]")
459
+
460
+ except Exception as exc:
461
+ use_txn = self.runner.should_use_transaction(migration, self.config)
462
+ rollback_msg = " (transaction rolled back)" if use_txn else ""
463
+ console.print(f"[red]✗ Failed{rollback_msg}: {exc}[/]")
464
+ self._last_command_error = exc
465
+ return
466
+
467
+ if dry_run:
468
+ console.print("\n[bold yellow]Dry run complete.[/] No changes were made to the database.")
469
+ elif applied_count:
470
+ self._record_command_metric("applied", float(applied_count))
471
+
472
+ @_with_command_span("downgrade", metadata_fn=_downgrade_metadata)
473
+ def downgrade(self, revision: str = "-1", *, dry_run: bool = False) -> None:
474
+ """Downgrade to a target revision.
475
+
476
+ Args:
477
+ revision: Target revision or "-1" for one step back.
478
+ dry_run: If True, show what would be done without making changes.
479
+ """
480
+ runtime = self._runtime
481
+ reverted_count = 0
482
+
483
+ if dry_run:
484
+ console.print("[bold yellow]DRY RUN MODE:[/] No database changes will be applied\n")
485
+
486
+ with self.config.provide_session() as driver:
487
+ self.tracker.ensure_tracking_table(driver)
488
+ applied = self.tracker.get_applied_migrations(driver)
489
+ if runtime is not None:
490
+ runtime.increment_metric("migrations.command.downgrade.available", float(len(applied)))
491
+ if not applied:
492
+ console.print("[yellow]No migrations to downgrade[/]")
493
+ return
494
+
495
+ to_revert = []
496
+ if revision == "-1":
497
+ to_revert = [applied[-1]]
498
+ elif revision == "base":
499
+ to_revert = list(reversed(applied))
500
+ else:
501
+ parsed_revision = parse_version(revision)
502
+ for migration in reversed(applied):
503
+ parsed_migration_version = parse_version(migration["version_num"])
504
+ if parsed_migration_version > parsed_revision:
505
+ to_revert.append(migration)
506
+
507
+ if runtime is not None:
508
+ runtime.increment_metric("migrations.command.downgrade.pending", float(len(to_revert)))
509
+
510
+ if not to_revert:
511
+ console.print("[yellow]Nothing to downgrade[/]")
512
+ return
513
+
514
+ console.print(f"[yellow]Reverting {len(to_revert)} migrations[/]")
515
+ all_files = dict(self.runner.get_migration_files())
516
+ for migration_record in to_revert:
517
+ version = migration_record["version_num"]
518
+ if version not in all_files:
519
+ console.print(f"[red]Migration file not found for {version}[/]")
520
+ if runtime is not None:
521
+ runtime.increment_metric("migrations.command.downgrade.missing_files")
522
+ continue
523
+ migration = self.runner.load_migration(all_files[version], version)
524
+
525
+ action_verb = "Would revert" if dry_run else "Reverting"
526
+ console.print(f"\n[cyan]{action_verb} {version}:[/] {migration['description']}")
527
+
528
+ if dry_run:
529
+ console.print(f"[dim]Migration file: {all_files[version]}[/]")
530
+ continue
531
+
532
+ try:
533
+
534
+ def remove_version(exec_time: int, version: str = version) -> None:
535
+ self.tracker.remove_migration(driver, version)
536
+
537
+ _, execution_time = self.runner.execute_downgrade(driver, migration, on_success=remove_version)
538
+ reverted_count += 1
539
+ console.print(f"[green]✓ Reverted in {execution_time}ms[/]")
540
+ except Exception as exc:
541
+ use_txn = self.runner.should_use_transaction(migration, self.config)
542
+ rollback_msg = " (transaction rolled back)" if use_txn else ""
543
+ console.print(f"[red]✗ Failed{rollback_msg}: {exc}[/]")
544
+ self._last_command_error = exc
545
+ return
546
+
547
+ if dry_run:
548
+ console.print("\n[bold yellow]Dry run complete.[/] No changes were made to the database.")
549
+ elif reverted_count:
550
+ self._record_command_metric("applied", float(reverted_count))
551
+
552
+ def stamp(self, revision: str) -> None:
553
+ """Mark database as being at a specific revision without running migrations.
554
+
555
+ Args:
556
+ revision: The revision to stamp.
557
+ """
558
+ with self.config.provide_session() as driver:
559
+ self.tracker.ensure_tracking_table(driver)
560
+ all_migrations = dict(self.runner.get_migration_files())
561
+ if revision not in all_migrations:
562
+ console.print(f"[red]Unknown revision: {revision}[/]")
563
+ return
564
+ clear_sql = sql.delete().from_(self.tracker.version_table)
565
+ driver.execute(clear_sql)
566
+ self.tracker.record_migration(driver, revision, f"Stamped to {revision}", 0, "manual-stamp")
567
+ console.print(f"[green]Database stamped at revision {revision}[/]")
568
+
569
+ def revision(self, message: str, file_type: str | None = None) -> None:
570
+ """Create a new migration file with timestamp-based versioning.
571
+
572
+ Generates a unique timestamp version (YYYYMMDDHHmmss format) to avoid
573
+ conflicts when multiple developers create migrations concurrently.
574
+
575
+ Args:
576
+ message: Description for the migration.
577
+ file_type: Type of migration file to create ('sql' or 'py').
578
+ """
579
+ version = generate_timestamp_version()
580
+ selected_format = file_type or self._template_settings.default_format
581
+ file_path = create_migration_file(
582
+ self.migrations_path,
583
+ version,
584
+ message,
585
+ selected_format,
586
+ config=self.config,
587
+ template_settings=self._template_settings,
588
+ )
589
+ log_with_context(
590
+ logger,
591
+ logging.DEBUG,
592
+ "migration.create",
593
+ db_system=resolve_db_system(type(self.config).__name__),
594
+ version=version,
595
+ file_path=str(file_path),
596
+ file_type=selected_format,
597
+ description=message,
598
+ )
599
+ console.print(f"[green]Created migration:[/] {file_path}")
600
+
601
+ def fix(self, dry_run: bool = False, update_database: bool = True, yes: bool = False) -> None:
602
+ """Convert timestamp migrations to sequential format.
603
+
604
+ Implements hybrid versioning workflow where development uses timestamps
605
+ and production uses sequential numbers. Creates backup before changes
606
+ and provides rollback on errors.
607
+
608
+ Args:
609
+ dry_run: Preview changes without applying.
610
+ update_database: Update migration records in database.
611
+ yes: Skip confirmation prompt.
612
+
613
+ Examples:
614
+ >>> commands.fix(dry_run=True) # Preview only
615
+ >>> commands.fix(yes=True) # Auto-approve
616
+ >>> commands.fix(update_database=False) # Files only
617
+ """
618
+ all_migrations = self.runner.get_migration_files()
619
+
620
+ conversion_map = generate_conversion_map(all_migrations)
621
+
622
+ if not conversion_map:
623
+ console.print("[yellow]No timestamp migrations found - nothing to convert[/]")
624
+ return
625
+
626
+ fixer = MigrationFixer(self.migrations_path)
627
+ renames = fixer.plan_renames(conversion_map)
628
+
629
+ table = Table(title="Migration Conversions")
630
+ table.add_column("Current Version", style="cyan")
631
+ table.add_column("New Version", style="green")
632
+ table.add_column("File")
633
+
634
+ for rename in renames:
635
+ table.add_row(rename.old_version, rename.new_version, rename.old_path.name)
636
+
637
+ console.print(table)
638
+ console.print(f"\n[yellow]{len(renames)} migrations will be converted[/]")
639
+
640
+ if dry_run:
641
+ console.print("[yellow][Preview Mode - No changes made][/]")
642
+ return
643
+
644
+ if not yes:
645
+ response = input("\nProceed with conversion? [y/N]: ")
646
+ if response.lower() != "y":
647
+ console.print("[yellow]Conversion cancelled[/]")
648
+ return
649
+
650
+ try:
651
+ backup_path = fixer.create_backup()
652
+ console.print(f"[green]✓ Created backup in {backup_path.name}[/]")
653
+
654
+ fixer.apply_renames(renames)
655
+ for rename in renames:
656
+ console.print(f"[green]✓ Renamed {rename.old_path.name} → {rename.new_path.name}[/]")
657
+
658
+ if update_database:
659
+ with self.config.provide_session() as driver:
660
+ self.tracker.ensure_tracking_table(driver)
661
+ applied_migrations = self.tracker.get_applied_migrations(driver)
662
+ applied_versions = {m["version_num"] for m in applied_migrations}
663
+
664
+ updated_count = 0
665
+ for old_version, new_version in conversion_map.items():
666
+ if old_version in applied_versions:
667
+ self.tracker.update_version_record(driver, old_version, new_version)
668
+ updated_count += 1
669
+
670
+ if updated_count > 0:
671
+ console.print(
672
+ f"[green]✓ Updated {updated_count} version records in migration tracking table[/]"
673
+ )
674
+ else:
675
+ console.print("[green]✓ No applied migrations to update in tracking table[/]")
676
+
677
+ fixer.cleanup()
678
+ console.print("[green]✓ Conversion complete![/]")
679
+
680
+ except Exception as e:
681
+ console.print(f"[red]✗ Error: {e}[/]")
682
+ fixer.rollback()
683
+ console.print("[yellow]Restored files from backup[/]")
684
+ raise
685
+
686
+
687
+ class AsyncMigrationCommands(BaseMigrationCommands["AsyncConfigT", Any]):
688
+ """Asynchronous migration commands."""
689
+
690
+ def __init__(self, config: "AsyncConfigT") -> None:
691
+ """Initialize migration commands.
692
+
693
+ Args:
694
+ config: The SQLSpec configuration.
695
+ """
696
+ super().__init__(config)
697
+ self.tracker = config.migration_tracker_type(self.version_table)
698
+
699
+ # Create context with extension configurations
700
+ context = MigrationContext.from_config(config)
701
+ context.extension_config = self.extension_configs
702
+
703
+ self.runner = AsyncMigrationRunner(
704
+ self.migrations_path,
705
+ self._discover_extension_migrations(),
706
+ context,
707
+ self.extension_configs,
708
+ runtime=self._runtime,
709
+ description_hints=self._template_settings.description_hints,
710
+ )
711
+
712
+ async def init(self, directory: str, package: bool = True) -> None:
713
+ """Initialize migration directory structure.
714
+
715
+ Args:
716
+ directory: Directory path for migrations.
717
+ package: Whether to create __init__.py in the directory.
718
+ """
719
+ self.init_directory(directory, package)
720
+
721
+ async def current(self, verbose: bool = False) -> "str | None":
722
+ """Show current migration version.
723
+
724
+ Args:
725
+ verbose: Whether to show detailed migration history.
726
+
727
+ Returns:
728
+ The current migration version or None if no migrations applied.
729
+ """
730
+ async with self.config.provide_session() as driver:
731
+ await self.tracker.ensure_tracking_table(driver)
732
+
733
+ current = await self.tracker.get_current_version(driver)
734
+ if not current:
735
+ log_with_context(
736
+ logger,
737
+ logging.DEBUG,
738
+ "migration.list",
739
+ db_system=resolve_db_system(type(driver).__name__),
740
+ current_version=None,
741
+ applied_count=0,
742
+ verbose=verbose,
743
+ status="empty",
744
+ )
745
+ console.print("[yellow]No migrations applied yet[/]")
746
+ return None
747
+
748
+ console.print(f"[green]Current version:[/] {current}")
749
+ applied: list[dict[str, Any]] = []
750
+ if verbose:
751
+ applied = await self.tracker.get_applied_migrations(driver)
752
+ table = Table(title="Applied Migrations")
753
+ table.add_column("Version", style="cyan")
754
+ table.add_column("Description")
755
+ table.add_column("Applied At")
756
+ table.add_column("Time (ms)", justify="right")
757
+ table.add_column("Applied By")
758
+ for migration in applied:
759
+ table.add_row(
760
+ migration["version_num"],
761
+ migration.get("description", ""),
762
+ str(migration.get("applied_at", "")),
763
+ str(migration.get("execution_time_ms", "")),
764
+ migration.get("applied_by", ""),
765
+ )
766
+ console.print(table)
767
+
768
+ applied_count = len(applied) if verbose else None
769
+ log_with_context(
770
+ logger,
771
+ logging.DEBUG,
772
+ "migration.list",
773
+ db_system=resolve_db_system(type(driver).__name__),
774
+ current_version=current,
775
+ applied_count=applied_count,
776
+ verbose=verbose,
777
+ status="complete",
778
+ )
779
+ return cast("str | None", current)
780
+
781
+ async def _load_single_migration_checksum(
782
+ self, version: str, file_path: "Path"
783
+ ) -> "tuple[str, tuple[str, Path]] | None":
784
+ """Load checksum for a single migration.
785
+
786
+ Args:
787
+ version: Migration version.
788
+ file_path: Path to migration file.
789
+
790
+ Returns:
791
+ Tuple of (version, (checksum, file_path)) or None if load fails.
792
+ """
793
+ try:
794
+ migration = await self.runner.load_migration(file_path, version)
795
+ return (version, (migration["checksum"], file_path))
796
+ except Exception as exc:
797
+ log_with_context(
798
+ logger,
799
+ logging.DEBUG,
800
+ "migration.list",
801
+ db_system=resolve_db_system(type(self.config).__name__),
802
+ version=version,
803
+ file_path=str(file_path),
804
+ error_type=type(exc).__name__,
805
+ status="failed",
806
+ operation="load_checksum",
807
+ )
808
+ return None
809
+
810
+ async def _load_migration_checksums(
811
+ self, all_migrations: "list[tuple[str, Path]]"
812
+ ) -> "dict[str, tuple[str, Path]]":
813
+ """Load checksums for all migrations.
814
+
815
+ Args:
816
+ all_migrations: List of (version, file_path) tuples.
817
+
818
+ Returns:
819
+ Dictionary mapping version to (checksum, file_path) tuples.
820
+ """
821
+ file_checksums = {}
822
+ for version, file_path in all_migrations:
823
+ result = await self._load_single_migration_checksum(version, file_path)
824
+ if result:
825
+ file_checksums[result[0]] = result[1]
826
+ return file_checksums
827
+
828
+ async def _synchronize_version_records(self, driver: Any) -> int:
829
+ """Synchronize database version records with migration files.
830
+
831
+ Auto-updates DB tracking when migrations have been renamed by fix command.
832
+ This allows developers to just run upgrade after pulling changes without
833
+ manually running fix.
834
+
835
+ Validates checksums match before updating to prevent incorrect matches.
836
+
837
+ Args:
838
+ driver: Database driver instance.
839
+
840
+ Returns:
841
+ Number of version records updated.
842
+ """
843
+ all_migrations = await self.runner.get_migration_files()
844
+
845
+ try:
846
+ applied_migrations = await self.tracker.get_applied_migrations(driver)
847
+ except Exception as exc:
848
+ log_with_context(
849
+ logger,
850
+ logging.DEBUG,
851
+ "migration.list",
852
+ db_system=resolve_db_system(type(driver).__name__),
853
+ error_type=type(exc).__name__,
854
+ status="failed",
855
+ operation="applied_fetch",
856
+ )
857
+ return 0
858
+
859
+ applied_map = {m["version_num"]: m for m in applied_migrations}
860
+
861
+ conversion_map = generate_conversion_map(all_migrations)
862
+
863
+ updated_count = 0
864
+ if conversion_map:
865
+ for old_version, new_version in conversion_map.items():
866
+ if old_version in applied_map and new_version not in applied_map:
867
+ applied_checksum = applied_map[old_version]["checksum"]
868
+
869
+ file_path = next((path for v, path in all_migrations if v == new_version), None)
870
+ if file_path:
871
+ migration = await self.runner.load_migration(file_path, new_version)
872
+ if migration["checksum"] == applied_checksum:
873
+ await self.tracker.update_version_record(driver, old_version, new_version)
874
+ console.print(f" [dim]Reconciled version:[/] {old_version} → {new_version}")
875
+ updated_count += 1
876
+ else:
877
+ console.print(
878
+ f" [yellow]Warning: Checksum mismatch for {old_version} → {new_version}, skipping auto-sync[/]"
879
+ )
880
+ else:
881
+ file_checksums = await self._load_migration_checksums(all_migrations)
882
+
883
+ for applied_version, applied_record in applied_map.items():
884
+ for file_version, (file_checksum, _) in file_checksums.items():
885
+ if file_version not in applied_map and applied_record["checksum"] == file_checksum:
886
+ await self.tracker.update_version_record(driver, applied_version, file_version)
887
+ console.print(f" [dim]Reconciled version:[/] {applied_version} → {file_version}")
888
+ updated_count += 1
889
+ break
890
+
891
+ if updated_count > 0:
892
+ console.print(f"[cyan]Reconciled {updated_count} version record(s)[/]")
893
+
894
+ return updated_count
895
+
896
+ @_with_command_span("upgrade", metadata_fn=_upgrade_metadata)
897
+ async def upgrade(
898
+ self, revision: str = "head", allow_missing: bool = False, auto_sync: bool = True, dry_run: bool = False
899
+ ) -> None:
900
+ """Upgrade to a target revision.
901
+
902
+ Validates migration order and warns if out-of-order migrations are detected.
903
+ Out-of-order migrations can occur when branches merge in different orders
904
+ across environments.
905
+
906
+ Args:
907
+ revision: Target revision or "head" for latest.
908
+ allow_missing: If True, allow out-of-order migrations even in strict mode.
909
+ Defaults to False.
910
+ auto_sync: If True, automatically reconcile renamed migrations in database.
911
+ Defaults to True. Can be disabled via --no-auto-sync flag.
912
+ dry_run: If True, show what would be done without making changes.
913
+ """
914
+ runtime = self._runtime
915
+ applied_count = 0
916
+
917
+ if dry_run:
918
+ console.print("[bold yellow]DRY RUN MODE:[/] No database changes will be applied\n")
919
+
920
+ async with self.config.provide_session() as driver:
921
+ await self.tracker.ensure_tracking_table(driver)
922
+
923
+ if auto_sync:
924
+ migration_config = cast("dict[str, Any]", self.config.migration_config) or {}
925
+ config_auto_sync = migration_config.get("auto_sync", True)
926
+ if config_auto_sync:
927
+ await self._synchronize_version_records(driver)
928
+
929
+ applied_migrations = await self.tracker.get_applied_migrations(driver)
930
+ applied_versions = [m["version_num"] for m in applied_migrations]
931
+ applied_set = set(applied_versions)
932
+
933
+ all_migrations = await self.runner.get_migration_files()
934
+ if runtime is not None:
935
+ runtime.increment_metric("migrations.command.upgrade.available", float(len(all_migrations)))
936
+
937
+ pending = []
938
+ for version, file_path in all_migrations:
939
+ if version not in applied_set:
940
+ if revision == "head":
941
+ pending.append((version, file_path))
942
+ else:
943
+ parsed_version = parse_version(version)
944
+ parsed_revision = parse_version(revision)
945
+ if parsed_version <= parsed_revision:
946
+ pending.append((version, file_path))
947
+
948
+ if runtime is not None:
949
+ runtime.increment_metric("migrations.command.upgrade.pending", float(len(pending)))
950
+
951
+ if not pending:
952
+ if not all_migrations:
953
+ console.print(
954
+ "[yellow]No migrations found. Create your first migration with 'sqlspec create-migration'.[/]"
955
+ )
956
+ else:
957
+ console.print("[green]Already at latest version[/]")
958
+ return
959
+ pending_versions = [v for v, _ in pending]
960
+
961
+ migration_config = cast("dict[str, Any]", self.config.migration_config) or {}
962
+ strict_ordering = migration_config.get("strict_ordering", False) and not allow_missing
963
+
964
+ validate_migration_order(pending_versions, applied_versions, strict_ordering)
965
+
966
+ console.print(f"[yellow]Found {len(pending)} pending migrations[/]")
967
+ for version, file_path in pending:
968
+ migration = await self.runner.load_migration(file_path, version)
969
+
970
+ action_verb = "Would apply" if dry_run else "Applying"
971
+ console.print(f"\n[cyan]{action_verb} {version}:[/] {migration['description']}")
972
+
973
+ if dry_run:
974
+ console.print(f"[dim]Migration file: {file_path}[/]")
975
+ continue
976
+
977
+ try:
978
+
979
+ async def record_version(exec_time: int, migration: "dict[str, Any]" = migration) -> None:
980
+ await self.tracker.record_migration(
981
+ driver, migration["version"], migration["description"], exec_time, migration["checksum"]
982
+ )
983
+
984
+ _, execution_time = await self.runner.execute_upgrade(driver, migration, on_success=record_version)
985
+ applied_count += 1
986
+ console.print(f"[green]✓ Applied in {execution_time}ms[/]")
987
+ except Exception as exc:
988
+ use_txn = self.runner.should_use_transaction(migration, self.config)
989
+ rollback_msg = " (transaction rolled back)" if use_txn else ""
990
+ console.print(f"[red]✗ Failed{rollback_msg}: {exc}[/]")
991
+ self._last_command_error = exc
992
+ return
993
+
994
+ if dry_run:
995
+ console.print("\n[bold yellow]Dry run complete.[/] No changes were made to the database.")
996
+ elif applied_count:
997
+ self._record_command_metric("applied", float(applied_count))
998
+
999
+ @_with_command_span("downgrade", metadata_fn=_downgrade_metadata)
1000
+ async def downgrade(self, revision: str = "-1", *, dry_run: bool = False) -> None:
1001
+ """Downgrade to a target revision.
1002
+
1003
+ Args:
1004
+ revision: Target revision or "-1" for one step back.
1005
+ dry_run: If True, show what would be done without making changes.
1006
+ """
1007
+ runtime = self._runtime
1008
+ reverted_count = 0
1009
+
1010
+ if dry_run:
1011
+ console.print("[bold yellow]DRY RUN MODE:[/] No database changes will be applied\n")
1012
+
1013
+ async with self.config.provide_session() as driver:
1014
+ await self.tracker.ensure_tracking_table(driver)
1015
+
1016
+ applied = await self.tracker.get_applied_migrations(driver)
1017
+ if runtime is not None:
1018
+ runtime.increment_metric("migrations.command.downgrade.available", float(len(applied)))
1019
+ if not applied:
1020
+ console.print("[yellow]No migrations to downgrade[/]")
1021
+ return
1022
+ to_revert = []
1023
+ if revision == "-1":
1024
+ to_revert = [applied[-1]]
1025
+ elif revision == "base":
1026
+ to_revert = list(reversed(applied))
1027
+ else:
1028
+ parsed_revision = parse_version(revision)
1029
+ for migration in reversed(applied):
1030
+ parsed_migration_version = parse_version(migration["version_num"])
1031
+ if parsed_migration_version > parsed_revision:
1032
+ to_revert.append(migration)
1033
+
1034
+ if runtime is not None:
1035
+ runtime.increment_metric("migrations.command.downgrade.pending", float(len(to_revert)))
1036
+
1037
+ if not to_revert:
1038
+ console.print("[yellow]Nothing to downgrade[/]")
1039
+ return
1040
+
1041
+ console.print(f"[yellow]Reverting {len(to_revert)} migrations[/]")
1042
+ all_files = dict(await self.runner.get_migration_files())
1043
+ for migration_record in to_revert:
1044
+ version = migration_record["version_num"]
1045
+ if version not in all_files:
1046
+ console.print(f"[red]Migration file not found for {version}[/]")
1047
+ if runtime is not None:
1048
+ runtime.increment_metric("migrations.command.downgrade.missing_files")
1049
+ continue
1050
+
1051
+ migration = await self.runner.load_migration(all_files[version], version)
1052
+
1053
+ action_verb = "Would revert" if dry_run else "Reverting"
1054
+ console.print(f"\n[cyan]{action_verb} {version}:[/] {migration['description']}")
1055
+
1056
+ if dry_run:
1057
+ console.print(f"[dim]Migration file: {all_files[version]}[/]")
1058
+ continue
1059
+
1060
+ try:
1061
+
1062
+ async def remove_version(exec_time: int, version: str = version) -> None:
1063
+ await self.tracker.remove_migration(driver, version)
1064
+
1065
+ _, execution_time = await self.runner.execute_downgrade(
1066
+ driver, migration, on_success=remove_version
1067
+ )
1068
+ reverted_count += 1
1069
+ console.print(f"[green]✓ Reverted in {execution_time}ms[/]")
1070
+ except Exception as exc:
1071
+ use_txn = self.runner.should_use_transaction(migration, self.config)
1072
+ rollback_msg = " (transaction rolled back)" if use_txn else ""
1073
+ console.print(f"[red]✗ Failed{rollback_msg}: {exc}[/]")
1074
+ self._last_command_error = exc
1075
+ return
1076
+
1077
+ if dry_run:
1078
+ console.print("\n[bold yellow]Dry run complete.[/] No changes were made to the database.")
1079
+ elif reverted_count:
1080
+ self._record_command_metric("applied", float(reverted_count))
1081
+
1082
+ async def stamp(self, revision: str) -> None:
1083
+ """Mark database as being at a specific revision without running migrations.
1084
+
1085
+ Args:
1086
+ revision: The revision to stamp.
1087
+ """
1088
+ async with self.config.provide_session() as driver:
1089
+ await self.tracker.ensure_tracking_table(driver)
1090
+
1091
+ all_migrations = dict(await self.runner.get_migration_files())
1092
+ if revision not in all_migrations:
1093
+ console.print(f"[red]Unknown revision: {revision}[/]")
1094
+ return
1095
+
1096
+ clear_sql = sql.delete().from_(self.tracker.version_table)
1097
+ await driver.execute(clear_sql)
1098
+ await self.tracker.record_migration(driver, revision, f"Stamped to {revision}", 0, "manual-stamp")
1099
+ console.print(f"[green]Database stamped at revision {revision}[/]")
1100
+
1101
+ async def revision(self, message: str, file_type: str | None = None) -> None:
1102
+ """Create a new migration file with timestamp-based versioning.
1103
+
1104
+ Generates a unique timestamp version (YYYYMMDDHHmmss format) to avoid
1105
+ conflicts when multiple developers create migrations concurrently.
1106
+
1107
+ Args:
1108
+ message: Description for the migration.
1109
+ file_type: Type of migration file to create ('sql' or 'py').
1110
+ """
1111
+ version = generate_timestamp_version()
1112
+ selected_format = file_type or self._template_settings.default_format
1113
+ file_path = create_migration_file(
1114
+ self.migrations_path,
1115
+ version,
1116
+ message,
1117
+ selected_format,
1118
+ config=self.config,
1119
+ template_settings=self._template_settings,
1120
+ )
1121
+ log_with_context(
1122
+ logger,
1123
+ logging.DEBUG,
1124
+ "migration.create",
1125
+ db_system=resolve_db_system(type(self.config).__name__),
1126
+ version=version,
1127
+ file_path=str(file_path),
1128
+ file_type=selected_format,
1129
+ description=message,
1130
+ )
1131
+ console.print(f"[green]Created migration:[/] {file_path}")
1132
+
1133
+ async def fix(self, dry_run: bool = False, update_database: bool = True, yes: bool = False) -> None:
1134
+ """Convert timestamp migrations to sequential format.
1135
+
1136
+ Implements hybrid versioning workflow where development uses timestamps
1137
+ and production uses sequential numbers. Creates backup before changes
1138
+ and provides rollback on errors.
1139
+
1140
+ Args:
1141
+ dry_run: Preview changes without applying.
1142
+ update_database: Update migration records in database.
1143
+ yes: Skip confirmation prompt.
1144
+
1145
+ Examples:
1146
+ >>> await commands.fix(dry_run=True) # Preview only
1147
+ >>> await commands.fix(yes=True) # Auto-approve
1148
+ >>> await commands.fix(update_database=False) # Files only
1149
+ """
1150
+ all_migrations = await self.runner.get_migration_files()
1151
+
1152
+ conversion_map = generate_conversion_map(all_migrations)
1153
+
1154
+ if not conversion_map:
1155
+ console.print("[yellow]No timestamp migrations found - nothing to convert[/]")
1156
+ return
1157
+
1158
+ fixer = MigrationFixer(self.migrations_path)
1159
+ renames = fixer.plan_renames(conversion_map)
1160
+
1161
+ table = Table(title="Migration Conversions")
1162
+ table.add_column("Current Version", style="cyan")
1163
+ table.add_column("New Version", style="green")
1164
+ table.add_column("File")
1165
+
1166
+ for rename in renames:
1167
+ table.add_row(rename.old_version, rename.new_version, rename.old_path.name)
1168
+
1169
+ console.print(table)
1170
+ console.print(f"\n[yellow]{len(renames)} migrations will be converted[/]")
1171
+
1172
+ if dry_run:
1173
+ console.print("[yellow][Preview Mode - No changes made][/]")
1174
+ return
1175
+
1176
+ if not yes:
1177
+ response = input("\nProceed with conversion? [y/N]: ")
1178
+ if response.lower() != "y":
1179
+ console.print("[yellow]Conversion cancelled[/]")
1180
+ return
1181
+
1182
+ try:
1183
+ backup_path = fixer.create_backup()
1184
+ console.print(f"[green]✓ Created backup in {backup_path.name}[/]")
1185
+
1186
+ fixer.apply_renames(renames)
1187
+ for rename in renames:
1188
+ console.print(f"[green]✓ Renamed {rename.old_path.name} → {rename.new_path.name}[/]")
1189
+
1190
+ if update_database:
1191
+ async with self.config.provide_session() as driver:
1192
+ await self.tracker.ensure_tracking_table(driver)
1193
+ applied_migrations = await self.tracker.get_applied_migrations(driver)
1194
+ applied_versions = {m["version_num"] for m in applied_migrations}
1195
+
1196
+ updated_count = 0
1197
+ for old_version, new_version in conversion_map.items():
1198
+ if old_version in applied_versions:
1199
+ await self.tracker.update_version_record(driver, old_version, new_version)
1200
+ updated_count += 1
1201
+
1202
+ if updated_count > 0:
1203
+ console.print(
1204
+ f"[green]✓ Updated {updated_count} version records in migration tracking table[/]"
1205
+ )
1206
+ else:
1207
+ console.print("[green]✓ No applied migrations to update in tracking table[/]")
1208
+
1209
+ fixer.cleanup()
1210
+ console.print("[green]✓ Conversion complete![/]")
1211
+
1212
+ except Exception as e:
1213
+ console.print(f"[red]✗ Error: {e}[/]")
1214
+ fixer.rollback()
1215
+ console.print("[yellow]Restored files from backup[/]")
1216
+ raise
1217
+
1218
+
1219
+ def create_migration_commands(
1220
+ config: "SyncConfigT | AsyncConfigT",
1221
+ ) -> "SyncMigrationCommands[SyncConfigT] | AsyncMigrationCommands[AsyncConfigT]":
1222
+ """Factory function to create the appropriate migration commands.
1223
+
1224
+ Args:
1225
+ config: The SQLSpec configuration.
1226
+
1227
+ Returns:
1228
+ Appropriate migration commands instance.
1229
+ """
1230
+ if config.is_async:
1231
+ return cast("AsyncMigrationCommands[AsyncConfigT]", AsyncMigrationCommands(cast("AsyncConfigT", config)))
1232
+ return cast("SyncMigrationCommands[SyncConfigT]", SyncMigrationCommands(cast("SyncConfigT", config)))