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,821 @@
1
+ """MERGE statement builder.
2
+
3
+ Provides a fluent interface for building SQL MERGE queries with
4
+ parameter binding and validation.
5
+ """
6
+
7
+ import contextlib
8
+ from collections.abc import Mapping, Sequence
9
+ from datetime import datetime
10
+ from decimal import Decimal
11
+ from itertools import starmap
12
+ from typing import TYPE_CHECKING, Any, cast
13
+
14
+ import sqlglot as sg
15
+ from mypy_extensions import trait
16
+ from sqlglot import exp
17
+ from sqlglot.errors import ParseError
18
+ from typing_extensions import Self
19
+
20
+ from sqlspec.builder._base import QueryBuilder
21
+ from sqlspec.builder._explain import ExplainMixin
22
+ from sqlspec.builder._parsing_utils import extract_sql_object_expression
23
+ from sqlspec.builder._select import is_explicitly_quoted
24
+ from sqlspec.core import SQLResult
25
+ from sqlspec.exceptions import DialectNotSupportedError, SQLBuilderError
26
+ from sqlspec.utils.serializers import to_json
27
+ from sqlspec.utils.type_guards import has_expression_and_sql
28
+
29
+ if TYPE_CHECKING:
30
+ from sqlglot.dialects.dialect import DialectType
31
+
32
+ __all__ = ("Merge",)
33
+
34
+ MERGE_UNSUPPORTED_DIALECTS = frozenset({"mysql", "sqlite", "duckdb"})
35
+
36
+
37
+ @trait
38
+ class _MergeAssignmentMixin:
39
+ """Shared assignment helpers for MERGE clause mixins."""
40
+
41
+ __slots__ = ()
42
+
43
+ def _is_column_reference(self, value: str) -> bool:
44
+ """Check if value is a SQL expression rather than a literal string.
45
+
46
+ Returns True for qualified column references, SQL keywords, functions, and expressions.
47
+ Returns False for plain literal strings that should be parameterized.
48
+ """
49
+ if not isinstance(value, str):
50
+ return False
51
+
52
+ builder = cast("QueryBuilder", self)
53
+ with contextlib.suppress(ParseError):
54
+ parsed: exp.Expression | None = exp.maybe_parse(value.strip(), dialect=builder.dialect)
55
+ if parsed is None:
56
+ return False
57
+
58
+ if isinstance(parsed, exp.Column):
59
+ return parsed.table is not None and bool(parsed.table)
60
+
61
+ return isinstance(
62
+ parsed,
63
+ (
64
+ exp.Dot,
65
+ exp.Add,
66
+ exp.Sub,
67
+ exp.Mul,
68
+ exp.Div,
69
+ exp.Mod,
70
+ exp.Func,
71
+ exp.Anonymous,
72
+ exp.Null,
73
+ exp.CurrentTimestamp,
74
+ exp.CurrentDate,
75
+ exp.CurrentTime,
76
+ exp.Paren,
77
+ exp.Case,
78
+ ),
79
+ )
80
+ return False
81
+
82
+ def _process_assignment(self, target_column: str, value: Any) -> exp.Expression:
83
+ column_identifier = exp.column(target_column) if isinstance(target_column, str) else target_column
84
+
85
+ if has_expression_and_sql(value):
86
+ value_expr = extract_sql_object_expression(value, builder=self)
87
+ return exp.EQ(this=column_identifier, expression=value_expr)
88
+ if isinstance(value, exp.Expression):
89
+ return exp.EQ(this=column_identifier, expression=value)
90
+ if isinstance(value, str) and self._is_column_reference(value):
91
+ builder = cast("QueryBuilder", self)
92
+ parsed_expression: exp.Expression | None = exp.maybe_parse(value, dialect=builder.dialect)
93
+ if parsed_expression is None:
94
+ msg = f"Could not parse assignment expression: {value}"
95
+ raise SQLBuilderError(msg)
96
+ return exp.EQ(this=column_identifier, expression=parsed_expression)
97
+
98
+ column_name = target_column if isinstance(target_column, str) else str(target_column)
99
+ column_leaf = column_name.split(".")[-1]
100
+ placeholder, _ = cast("QueryBuilder", self).create_placeholder(value, column_leaf)
101
+ return exp.EQ(this=column_identifier, expression=placeholder)
102
+
103
+
104
+ @trait
105
+ class MergeIntoClauseMixin:
106
+ """Mixin providing INTO clause for MERGE builders."""
107
+
108
+ __slots__ = ()
109
+
110
+ def get_expression(self) -> exp.Expression | None: ...
111
+ def set_expression(self, expression: exp.Expression) -> None: ...
112
+
113
+ def into(self, table: str | exp.Expression, alias: str | None = None) -> Self:
114
+ current_expr = self.get_expression()
115
+ if current_expr is None or not isinstance(current_expr, exp.Merge):
116
+ self.set_expression(exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])))
117
+ current_expr = self.get_expression()
118
+
119
+ assert current_expr is not None
120
+
121
+ table_expr: exp.Expression
122
+ if isinstance(table, str):
123
+ table_expr = exp.to_table(table)
124
+ if is_explicitly_quoted(table):
125
+ stripped = table.strip('"`')
126
+ table_expr.set("quoted", True)
127
+ table_expr.set("this", exp.to_identifier(stripped, quoted=True))
128
+ cast("QueryBuilder", self)._merge_target_quoted = True # pyright: ignore[reportPrivateUsage]
129
+ else:
130
+ cast("QueryBuilder", self)._merge_target_quoted = False # pyright: ignore[reportPrivateUsage]
131
+ if alias:
132
+ table_expr = exp.alias_(table_expr, alias, table=True)
133
+ else:
134
+ table_expr = table
135
+
136
+ current_expr.set("this", table_expr)
137
+ return self
138
+
139
+
140
+ @trait
141
+ class MergeUsingClauseMixin(_MergeAssignmentMixin):
142
+ """Mixin providing USING clause for MERGE builders."""
143
+
144
+ __slots__ = ()
145
+
146
+ def get_expression(self) -> exp.Expression | None: ...
147
+ def set_expression(self, expression: exp.Expression) -> None: ...
148
+
149
+ def _create_dict_source_expression(
150
+ self, source: "dict[str, Any] | list[dict[str, Any]]", alias: "str | None"
151
+ ) -> "exp.Expression":
152
+ """Create USING clause expression from dict or list of dicts.
153
+
154
+ Uses JSON-based approach for type-safe bulk operations:
155
+ - PostgreSQL: json_populate_recordset(NULL::table_name, $1::jsonb)
156
+ - Oracle: JSON_TABLE(:payload, '$[*]' COLUMNS (...))
157
+ - Others: Fall back to SELECT with parameterized values
158
+
159
+ Args:
160
+ source: Dict or list of dicts for USING clause
161
+ alias: Optional alias for the source
162
+
163
+ Returns:
164
+ Expression for USING clause
165
+ """
166
+ data: list[dict[str, Any]]
167
+ is_list: bool
168
+ if isinstance(source, list):
169
+ data = source
170
+ is_list = True
171
+ else:
172
+ data = [source]
173
+ is_list = False
174
+
175
+ if not data:
176
+ msg = "Cannot create USING clause from empty list"
177
+ raise SQLBuilderError(msg)
178
+
179
+ columns = list(data[0].keys())
180
+ builder = cast("QueryBuilder", self)
181
+ dialect = builder.dialect_name
182
+
183
+ if dialect == "postgres":
184
+ return self._create_postgres_json_source(data, columns, is_list, alias)
185
+ if dialect == "oracle":
186
+ return self._create_oracle_json_source(data, columns, alias)
187
+
188
+ return self._create_select_union_source(data, columns, is_list, alias)
189
+
190
+ def _create_postgres_json_source(
191
+ self, data: "list[dict[str, Any]]", columns: "list[str]", is_list: bool, alias: "str | None"
192
+ ) -> "exp.Expression":
193
+ """Create PostgreSQL jsonb_to_recordset source with explicit column definitions.
194
+
195
+ Uses jsonb_to_recordset(jsonb) AS alias(col1 type1, col2 type2, ...) pattern
196
+ which avoids composite type dependencies and provides explicit type definitions.
197
+
198
+ Passes native Python list to driver for JSON serialization via driver-specific
199
+ mechanisms (AsyncPG json_serializer, Psycopg/Psqlpy JSON codecs).
200
+ """
201
+
202
+ json_value = data if is_list else [data[0]]
203
+ _, json_param_name = cast("QueryBuilder", self).create_placeholder(json_value, "json_data")
204
+
205
+ sample_values: dict[str, Any] = {}
206
+ for record in data:
207
+ for column, value in record.items():
208
+ if value is not None and column not in sample_values:
209
+ sample_values[column] = value
210
+
211
+ alias_name = alias or "src"
212
+ recordset_alias = f"{alias_name}_data"
213
+
214
+ column_type_spec = ", ".join([f"{col} {self._infer_postgres_type(sample_values.get(col))}" for col in columns])
215
+ column_selects = ", ".join(columns)
216
+ from_sql = (
217
+ f"SELECT {column_selects} FROM jsonb_to_recordset(:{json_param_name}::jsonb) AS "
218
+ f"{recordset_alias}({column_type_spec})"
219
+ )
220
+
221
+ parsed = sg.parse_one(from_sql, dialect="postgres")
222
+ return exp.Subquery(
223
+ this=parsed,
224
+ alias=exp.TableAlias(
225
+ this=exp.to_identifier(alias_name), columns=[exp.to_identifier(col) for col in columns]
226
+ ),
227
+ )
228
+
229
+ def _create_oracle_json_source(
230
+ self, data: "list[dict[str, Any]]", columns: "list[str]", alias: "str | None"
231
+ ) -> "exp.Expression":
232
+ """Create Oracle JSON_TABLE source (production-proven pattern from oracledb-vertexai-demo)."""
233
+ json_value = to_json(data)
234
+ _, json_param_name = cast("QueryBuilder", self).create_placeholder(json_value, "json_payload")
235
+
236
+ sample_values: dict[str, Any] = {}
237
+ for record in data:
238
+ for column, value in record.items():
239
+ if value is not None and column not in sample_values:
240
+ sample_values[column] = value
241
+
242
+ json_columns = [
243
+ f"{column} {self._infer_oracle_type(sample_values.get(column))} PATH '$.{column}'" for column in columns
244
+ ]
245
+
246
+ alias_name = alias or "src"
247
+ column_selects = ", ".join(columns)
248
+ columns_clause = ", ".join(json_columns)
249
+
250
+ from_sql = f"SELECT {column_selects} FROM JSON_TABLE(:{json_param_name}, '$[*]' COLUMNS ({columns_clause}))"
251
+
252
+ parsed = sg.parse_one(from_sql, dialect="oracle")
253
+ return exp.Subquery(this=parsed, alias=exp.TableAlias(this=exp.to_identifier(alias_name)))
254
+
255
+ def _infer_postgres_type(self, value: "Any") -> str:
256
+ """Infer PostgreSQL column type from Python value.
257
+
258
+ Maps Python types to PostgreSQL types for jsonb_to_recordset column definitions.
259
+
260
+ Note: When value is None and we cannot infer the type from other records,
261
+ we default to NUMERIC which is more permissive than TEXT for NULL values
262
+ and commonly used for business data.
263
+ """
264
+ if value is None:
265
+ return "NUMERIC"
266
+ if isinstance(value, bool):
267
+ return "BOOLEAN"
268
+ if isinstance(value, int):
269
+ return "INTEGER"
270
+ if isinstance(value, float):
271
+ return "DOUBLE PRECISION"
272
+ if isinstance(value, Decimal):
273
+ return "NUMERIC"
274
+ if isinstance(value, (dict, list)):
275
+ return "JSONB"
276
+ if isinstance(value, datetime):
277
+ return "TIMESTAMP"
278
+ return "TEXT"
279
+
280
+ def _infer_oracle_type(self, value: "Any") -> str:
281
+ """Infer Oracle column type for JSON_TABLE projection."""
282
+ varchar2_max = 4000
283
+
284
+ if isinstance(value, bool):
285
+ return "NUMBER(1)"
286
+ if isinstance(value, (int, float, Decimal)):
287
+ return "NUMBER"
288
+ if isinstance(value, (dict, list)):
289
+ return "JSON"
290
+ if isinstance(value, datetime):
291
+ return "TIMESTAMP"
292
+ if value is not None and len(str(value)) > varchar2_max:
293
+ return "CLOB"
294
+ return f"VARCHAR2({varchar2_max})"
295
+
296
+ def _create_select_union_source(
297
+ self, data: "list[dict[str, Any]]", columns: "list[str]", is_list: bool, alias: "str | None"
298
+ ) -> "exp.Expression":
299
+ """Create fallback SELECT UNION source for other databases."""
300
+ parameterized_values: list[list[exp.Expression]] = []
301
+ for row in data:
302
+ row_params: list[exp.Expression] = []
303
+ for column in columns:
304
+ value = row.get(column)
305
+ column_name = column if isinstance(column, str) else str(column)
306
+ if "." in column_name:
307
+ column_name = column_name.split(".")[-1]
308
+ placeholder, _ = cast("QueryBuilder", self).create_placeholder(value, column_name)
309
+ row_params.append(placeholder)
310
+ parameterized_values.append(row_params)
311
+
312
+ if is_list:
313
+ union_selects: list[exp.Select] = []
314
+ for row_params in parameterized_values:
315
+ select_expr = exp.Select()
316
+ select_expr.set(
317
+ "expressions", [exp.alias_(row_params[index], column) for index, column in enumerate(columns)]
318
+ )
319
+ union_selects.append(select_expr)
320
+
321
+ source_expr: exp.Expression
322
+ if len(union_selects) == 1:
323
+ source_expr = union_selects[0]
324
+ else:
325
+ union_expr: exp.Expression = union_selects[0]
326
+ for select in union_selects[1:]:
327
+ union_expr = exp.Union(this=union_expr, expression=select, distinct=False)
328
+ source_expr = union_expr
329
+
330
+ return exp.paren(source_expr)
331
+
332
+ select_expr = exp.Select()
333
+ select_expr.set(
334
+ "expressions", [exp.alias_(parameterized_values[0][index], column) for index, column in enumerate(columns)]
335
+ )
336
+
337
+ return exp.paren(select_expr)
338
+
339
+ def using(self, source: str | exp.Expression | Any, alias: str | None = None) -> Self:
340
+ current_expr = self.get_expression()
341
+ if current_expr is None or not isinstance(current_expr, exp.Merge):
342
+ self.set_expression(exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])))
343
+ current_expr = self.get_expression()
344
+
345
+ assert current_expr is not None
346
+ source_expr: exp.Expression
347
+ if isinstance(source, str):
348
+ source_expr = exp.to_table(source, alias=alias)
349
+ elif isinstance(source, (dict, list)):
350
+ paren_expr = self._create_dict_source_expression(source, alias)
351
+ if alias and isinstance(paren_expr, exp.Paren):
352
+ source_expr = exp.Subquery(this=paren_expr.this, alias=exp.to_identifier(alias))
353
+ else:
354
+ source_expr = paren_expr
355
+ elif isinstance(source, QueryBuilder):
356
+ builder = cast("QueryBuilder", self)
357
+ for param_name, param_value in source.parameters.items():
358
+ builder.add_parameter(param_value, name=param_name)
359
+ subquery_expression_source = source.get_expression()
360
+ if not isinstance(subquery_expression_source, exp.Expression):
361
+ subquery_expression_source = exp.select()
362
+
363
+ if alias:
364
+ source_expr = exp.Subquery(this=subquery_expression_source, alias=exp.to_identifier(alias))
365
+ else:
366
+ source_expr = exp.paren(subquery_expression_source)
367
+ elif isinstance(source, exp.Expression):
368
+ # Handle different expression types for MERGE USING
369
+ if isinstance(source, exp.Select):
370
+ # Wrap SELECT in Subquery if alias provided
371
+ source_expr = exp.Subquery(this=source, alias=exp.to_identifier(alias)) if alias else exp.paren(source)
372
+ elif isinstance(source, exp.Paren) and alias:
373
+ # Convert Paren to Subquery with alias
374
+ inner = source.this
375
+ source_expr = exp.Subquery(this=inner, alias=exp.to_identifier(alias))
376
+ elif isinstance(source, exp.Subquery) and alias:
377
+ # Update existing Subquery's alias
378
+ source.set("alias", exp.to_identifier(alias))
379
+ source_expr = source
380
+ else:
381
+ # Table name or other expression - use standard aliasing
382
+ source_expr = exp.alias_(source, alias) if alias else source
383
+ else:
384
+ msg = f"Unsupported source type for USING clause: {type(source)}"
385
+ raise SQLBuilderError(msg)
386
+
387
+ current_expr.set("using", source_expr)
388
+ return self
389
+
390
+
391
+ @trait
392
+ class MergeOnClauseMixin:
393
+ """Mixin providing ON clause for MERGE builders."""
394
+
395
+ __slots__ = ()
396
+
397
+ def get_expression(self) -> exp.Expression | None: ...
398
+ def set_expression(self, expression: exp.Expression) -> None: ...
399
+
400
+ def on(self, condition: str | exp.Expression) -> Self:
401
+ current_expr = self.get_expression()
402
+ if current_expr is None or not isinstance(current_expr, exp.Merge):
403
+ self.set_expression(exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])))
404
+ current_expr = self.get_expression()
405
+
406
+ assert current_expr is not None
407
+ if isinstance(condition, str):
408
+ builder = cast("QueryBuilder", self)
409
+ parsed_condition: exp.Expression | None = exp.maybe_parse(condition, dialect=builder.dialect)
410
+ if parsed_condition is None:
411
+ msg = f"Could not parse ON condition: {condition}"
412
+ raise SQLBuilderError(msg)
413
+ condition_expr = parsed_condition
414
+ elif isinstance(condition, exp.Expression):
415
+ condition_expr = condition
416
+ else:
417
+ msg = f"Unsupported condition type for ON clause: {type(condition)}"
418
+ raise SQLBuilderError(msg)
419
+
420
+ current_expr.set("on", exp.paren(condition_expr))
421
+ return self
422
+
423
+
424
+ @trait
425
+ class MergeMatchedClauseMixin(_MergeAssignmentMixin):
426
+ """Mixin providing WHEN MATCHED THEN ... clauses for MERGE builders."""
427
+
428
+ __slots__ = ()
429
+
430
+ def get_expression(self) -> exp.Expression | None: ...
431
+ def set_expression(self, expression: exp.Expression) -> None: ...
432
+
433
+ def when_matched_then_update(
434
+ self,
435
+ set_values: dict[str, Any] | None = None,
436
+ condition: str | exp.Expression | None = None,
437
+ **assignments: Any,
438
+ ) -> Self:
439
+ current_expr = self.get_expression()
440
+ if current_expr is None or not isinstance(current_expr, exp.Merge):
441
+ self.set_expression(exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])))
442
+ current_expr = self.get_expression()
443
+
444
+ assert current_expr is not None
445
+ combined_assignments: dict[str, Any] = {}
446
+ if set_values:
447
+ combined_assignments.update(set_values)
448
+ if assignments:
449
+ combined_assignments.update(assignments)
450
+
451
+ if not combined_assignments:
452
+ msg = "No update values provided. Use set_values or keyword arguments."
453
+ raise SQLBuilderError(msg)
454
+
455
+ set_expressions = list(starmap(self._process_assignment, combined_assignments.items()))
456
+ update_expression = exp.Update(expressions=set_expressions)
457
+
458
+ when_kwargs: dict[str, Any] = {"matched": True, "then": update_expression}
459
+ if condition is not None:
460
+ if isinstance(condition, str):
461
+ builder = cast("QueryBuilder", self)
462
+ parsed_condition: exp.Expression | None = exp.maybe_parse(condition, dialect=builder.dialect)
463
+ if parsed_condition is None:
464
+ msg = f"Could not parse WHEN clause condition: {condition}"
465
+ raise SQLBuilderError(msg)
466
+ condition_expr = parsed_condition
467
+ elif isinstance(condition, exp.Expression):
468
+ condition_expr = condition
469
+ else:
470
+ msg = f"Unsupported condition type for WHEN clause: {type(condition)}"
471
+ raise SQLBuilderError(msg)
472
+
473
+ builder = cast("QueryBuilder", self)
474
+ dialect_name = builder.dialect_name
475
+ if dialect_name == "oracle":
476
+ update_expression.set("where", condition_expr)
477
+ else:
478
+ when_kwargs["condition"] = condition_expr
479
+
480
+ whens = current_expr.args.get("whens")
481
+ if not isinstance(whens, exp.Whens):
482
+ whens = exp.Whens(expressions=[])
483
+ current_expr.set("whens", whens)
484
+ whens.append("expressions", exp.When(**when_kwargs))
485
+ return self
486
+
487
+ def when_matched_then_delete(self, condition: str | exp.Expression | None = None) -> Self:
488
+ current_expr = self.get_expression()
489
+ if current_expr is None or not isinstance(current_expr, exp.Merge):
490
+ self.set_expression(exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])))
491
+ current_expr = self.get_expression()
492
+
493
+ assert current_expr is not None
494
+ when_kwargs: dict[str, Any] = {"matched": True, "then": exp.Var(this="DELETE")}
495
+ if condition is not None:
496
+ if isinstance(condition, str):
497
+ builder = cast("QueryBuilder", self)
498
+ parsed_condition: exp.Expression | None = exp.maybe_parse(condition, dialect=builder.dialect)
499
+ if parsed_condition is None:
500
+ msg = f"Could not parse WHEN clause condition: {condition}"
501
+ raise SQLBuilderError(msg)
502
+ when_kwargs["condition"] = parsed_condition
503
+ elif isinstance(condition, exp.Expression):
504
+ when_kwargs["condition"] = condition
505
+ else:
506
+ msg = f"Unsupported condition type for WHEN clause: {type(condition)}"
507
+ raise SQLBuilderError(msg)
508
+
509
+ whens = current_expr.args.get("whens")
510
+ if not isinstance(whens, exp.Whens):
511
+ whens = exp.Whens(expressions=[])
512
+ current_expr.set("whens", whens)
513
+ whens.append("expressions", exp.When(**when_kwargs))
514
+ return self
515
+
516
+
517
+ @trait
518
+ class MergeNotMatchedClauseMixin(_MergeAssignmentMixin):
519
+ """Mixin providing WHEN NOT MATCHED THEN ... clauses for MERGE builders."""
520
+
521
+ __slots__ = ()
522
+
523
+ def get_expression(self) -> exp.Expression | None: ...
524
+ def set_expression(self, expression: exp.Expression) -> None: ...
525
+
526
+ def when_not_matched_then_insert(
527
+ self,
528
+ columns: Mapping[str, Any] | Sequence[str] | None = None,
529
+ values: Sequence[Any] | None = None,
530
+ **value_kwargs: Any,
531
+ ) -> Self:
532
+ current_expr = self.get_expression()
533
+ if current_expr is None or not isinstance(current_expr, exp.Merge):
534
+ self.set_expression(exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])))
535
+ current_expr = self.get_expression()
536
+
537
+ assert current_expr is not None
538
+ insert_expr = exp.Insert()
539
+ column_names: list[str]
540
+ column_values: list[Any]
541
+
542
+ if isinstance(columns, Mapping):
543
+ combined = dict(columns)
544
+ if value_kwargs:
545
+ combined.update(value_kwargs)
546
+ column_names = list(combined.keys())
547
+ column_values = list(combined.values())
548
+ elif value_kwargs:
549
+ column_names = list(value_kwargs.keys())
550
+ column_values = list(value_kwargs.values())
551
+ else:
552
+ if columns is None:
553
+ msg = "Columns must be provided when not using keyword arguments."
554
+ raise SQLBuilderError(msg)
555
+ column_names = [str(column) for column in columns]
556
+ if values is None:
557
+ using_alias = None
558
+ using_expr = current_expr.args.get("using")
559
+ if using_expr is not None and isinstance(using_expr, (exp.Subquery, exp.Table)):
560
+ using_alias = using_expr.alias
561
+ elif using_expr is not None:
562
+ try:
563
+ using_alias = using_expr.alias
564
+ except AttributeError:
565
+ using_alias = None
566
+ column_values = [f"{using_alias}.{col}" for col in column_names] if using_alias else column_names
567
+ else:
568
+ column_values = list(values)
569
+ if len(column_names) != len(column_values):
570
+ msg = "Number of columns must match number of values for MERGE insert"
571
+ raise SQLBuilderError(msg)
572
+
573
+ insert_columns = [exp.column(name) for name in column_names]
574
+
575
+ insert_values: list[exp.Expression] = []
576
+ for column_name, value in zip(column_names, column_values, strict=True):
577
+ if has_expression_and_sql(value):
578
+ insert_values.append(extract_sql_object_expression(value, builder=self))
579
+ elif isinstance(value, exp.Expression):
580
+ insert_values.append(value)
581
+ elif isinstance(value, str):
582
+ if self._is_column_reference(value):
583
+ builder = cast("QueryBuilder", self)
584
+ parsed_value: exp.Expression | None = exp.maybe_parse(value, dialect=builder.dialect)
585
+ if parsed_value is None:
586
+ msg = f"Could not parse column reference: {value}"
587
+ raise SQLBuilderError(msg)
588
+ insert_values.append(parsed_value)
589
+ else:
590
+ placeholder, _ = cast("QueryBuilder", self).create_placeholder(value, column_name.split(".")[-1])
591
+ insert_values.append(placeholder)
592
+ else:
593
+ placeholder, _ = cast("QueryBuilder", self).create_placeholder(value, column_name.split(".")[-1])
594
+ insert_values.append(placeholder)
595
+
596
+ insert_expr.set("this", exp.Tuple(expressions=insert_columns))
597
+ insert_expr.set("expression", exp.Tuple(expressions=insert_values))
598
+ whens = current_expr.args.get("whens")
599
+ if not isinstance(whens, exp.Whens):
600
+ whens = exp.Whens(expressions=[])
601
+ current_expr.set("whens", whens)
602
+ whens.append("expressions", exp.When(matched=False, then=insert_expr))
603
+ return self
604
+
605
+
606
+ @trait
607
+ class MergeNotMatchedBySourceClauseMixin(_MergeAssignmentMixin):
608
+ """Mixin providing WHEN NOT MATCHED BY SOURCE THEN ... clauses."""
609
+
610
+ __slots__ = ()
611
+
612
+ def get_expression(self) -> exp.Expression | None: ...
613
+ def set_expression(self, expression: exp.Expression) -> None: ...
614
+
615
+ def when_not_matched_by_source_then_update(
616
+ self, set_values: dict[str, Any] | None = None, **assignments: Any
617
+ ) -> Self:
618
+ current_expr = self.get_expression()
619
+ if current_expr is None or not isinstance(current_expr, exp.Merge):
620
+ self.set_expression(exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])))
621
+ current_expr = self.get_expression()
622
+
623
+ assert current_expr is not None
624
+ combined_assignments: dict[str, Any] = {}
625
+ if set_values:
626
+ combined_assignments.update(set_values)
627
+ if assignments:
628
+ combined_assignments.update(assignments)
629
+
630
+ if not combined_assignments:
631
+ msg = "No update values provided. Use set_values or keyword arguments."
632
+ raise SQLBuilderError(msg)
633
+
634
+ set_expressions: list[exp.Expression] = []
635
+ for column_name, value in combined_assignments.items():
636
+ column_identifier = exp.column(column_name)
637
+ if has_expression_and_sql(value):
638
+ value_expr = extract_sql_object_expression(value, builder=self)
639
+ elif isinstance(value, exp.Expression):
640
+ value_expr = value
641
+ elif isinstance(value, str) and self._is_column_reference(value):
642
+ builder = cast("QueryBuilder", self)
643
+ parsed_value: exp.Expression | None = exp.maybe_parse(value, dialect=builder.dialect)
644
+ if parsed_value is None:
645
+ msg = f"Could not parse assignment expression: {value}"
646
+ raise SQLBuilderError(msg)
647
+ value_expr = parsed_value
648
+ else:
649
+ placeholder, _ = cast("QueryBuilder", self).create_placeholder(value, column_name)
650
+ value_expr = placeholder
651
+ set_expressions.append(exp.EQ(this=column_identifier, expression=value_expr))
652
+
653
+ update_expr = exp.Update(expressions=set_expressions)
654
+ whens = current_expr.args.get("whens")
655
+ if not isinstance(whens, exp.Whens):
656
+ whens = exp.Whens(expressions=[])
657
+ current_expr.set("whens", whens)
658
+ whens.append("expressions", exp.When(matched=False, source=True, then=update_expr))
659
+ return self
660
+
661
+ def when_not_matched_by_source_then_delete(self) -> Self:
662
+ current_expr = self.get_expression()
663
+ if current_expr is None or not isinstance(current_expr, exp.Merge):
664
+ self.set_expression(exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])))
665
+ current_expr = self.get_expression()
666
+
667
+ assert current_expr is not None
668
+ whens = current_expr.args.get("whens")
669
+ if not isinstance(whens, exp.Whens):
670
+ whens = exp.Whens(expressions=[])
671
+ current_expr.set("whens", whens)
672
+ whens.append("expressions", exp.When(matched=False, source=True, then=exp.Delete()))
673
+ return self
674
+
675
+
676
+ class Merge(
677
+ QueryBuilder,
678
+ MergeUsingClauseMixin,
679
+ MergeOnClauseMixin,
680
+ MergeMatchedClauseMixin,
681
+ MergeNotMatchedClauseMixin,
682
+ MergeIntoClauseMixin,
683
+ MergeNotMatchedBySourceClauseMixin,
684
+ ExplainMixin,
685
+ ):
686
+ """Builder for MERGE statements.
687
+
688
+ Constructs SQL MERGE statements (also known as UPSERT in some databases)
689
+ with parameter binding and validation.
690
+ """
691
+
692
+ __slots__ = ()
693
+ _expression: exp.Expression | None
694
+ _merge_target_quoted: bool
695
+ _lock_targets_quoted: bool
696
+
697
+ def __init__(self, target_table: str | None = None, **kwargs: Any) -> None:
698
+ """Initialize MERGE with optional target table.
699
+
700
+ Args:
701
+ target_table: Target table name
702
+ **kwargs: Additional QueryBuilder arguments
703
+ """
704
+ if "enable_optimization" not in kwargs:
705
+ kwargs["enable_optimization"] = False
706
+ (dialect, schema, enable_optimization, optimize_joins, optimize_predicates, simplify_expressions) = (
707
+ self._parse_query_builder_kwargs(kwargs)
708
+ )
709
+ super().__init__(
710
+ dialect=dialect,
711
+ schema=schema,
712
+ enable_optimization=enable_optimization,
713
+ optimize_joins=optimize_joins,
714
+ optimize_predicates=optimize_predicates,
715
+ simplify_expressions=simplify_expressions,
716
+ )
717
+ self._merge_target_quoted = False
718
+ self._lock_targets_quoted = False
719
+ self._initialize_expression()
720
+
721
+ if target_table:
722
+ self.into(target_table)
723
+
724
+ @property
725
+ def _expected_result_type(self) -> "type[SQLResult]":
726
+ """Return the expected result type for this builder.
727
+
728
+ Returns:
729
+ The SQLResult type for MERGE statements.
730
+ """
731
+ return SQLResult
732
+
733
+ def _create_base_expression(self) -> "exp.Merge":
734
+ """Create a base MERGE expression.
735
+
736
+ Returns:
737
+ A new sqlglot Merge expression with empty clauses.
738
+ """
739
+ return exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))
740
+
741
+ def _validate_dialect_support(self) -> None:
742
+ """Validate that the current dialect supports MERGE statements.
743
+
744
+ Raises:
745
+ DialectNotSupportedError: If the dialect does not support MERGE.
746
+ """
747
+ dialect = self.dialect_name
748
+ if dialect and dialect in MERGE_UNSUPPORTED_DIALECTS:
749
+ self._raise_dialect_not_supported(dialect)
750
+
751
+ def _raise_dialect_not_supported(self, dialect: str) -> None:
752
+ """Raise error with helpful alternatives for unsupported dialects.
753
+
754
+ Args:
755
+ dialect: Name of the unsupported dialect.
756
+
757
+ Raises:
758
+ DialectNotSupportedError: Always raised with dialect-specific message.
759
+ """
760
+ alternatives = {
761
+ "mysql": "INSERT ... ON DUPLICATE KEY UPDATE",
762
+ "sqlite": "INSERT ... ON CONFLICT DO UPDATE",
763
+ "duckdb": "INSERT ... ON CONFLICT DO UPDATE",
764
+ }
765
+
766
+ alternative = alternatives.get(dialect, "INSERT ... ON CONFLICT or equivalent")
767
+ msg = (
768
+ f"MERGE statements are not supported in {dialect.upper()}. "
769
+ f"Use {alternative} instead. "
770
+ f"See the SQLSpec documentation for examples."
771
+ )
772
+ raise DialectNotSupportedError(msg)
773
+
774
+ def build(self, dialect: "DialectType" = None) -> "Any":
775
+ """Build MERGE statement with dialect validation.
776
+
777
+ Args:
778
+ dialect: Optional dialect override for SQL generation.
779
+
780
+ Returns:
781
+ Built statement object.
782
+ """
783
+ self._validate_dialect_support()
784
+ target_dialect = dialect or self.dialect
785
+ if isinstance(target_dialect, str):
786
+ dialect_name = target_dialect
787
+ elif isinstance(target_dialect, type):
788
+ dialect_name = target_dialect.__name__
789
+ else:
790
+ dialect_name = None
791
+ if dialect_name:
792
+ dialect_name = dialect_name.lower()
793
+ self._normalize_merge_conditions_for_dialect(dialect_name)
794
+ return super().build(dialect=dialect)
795
+
796
+ def _normalize_merge_conditions_for_dialect(self, dialect_name: str | None) -> None:
797
+ """Normalize WHEN clause conditions for dialect quirks (e.g., Oracle).
798
+
799
+ Oracle requires conditional logic on UPDATE/DELETE branches to live in the
800
+ clause-specific WHERE, not on the WHEN predicate. Move the condition down
801
+ when present so generated SQL matches Oracle syntax.
802
+ """
803
+ if dialect_name != "oracle":
804
+ return
805
+
806
+ merge_expr = self.get_expression()
807
+ if not isinstance(merge_expr, exp.Merge):
808
+ return
809
+
810
+ whens = merge_expr.args.get("whens")
811
+ if not isinstance(whens, exp.Whens):
812
+ return
813
+
814
+ for when_expr in list(whens.expressions):
815
+ condition_expr = when_expr.args.get("condition")
816
+ if condition_expr is None:
817
+ continue
818
+ then_expr = when_expr.args.get("then")
819
+ if isinstance(then_expr, exp.Update):
820
+ then_expr.set("where", condition_expr)
821
+ when_expr.set("condition", None)