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,1660 @@
1
+ """SELECT statement builder.
2
+
3
+ Provides a fluent interface for building SQL SELECT queries with
4
+ parameter binding and validation.
5
+ """
6
+
7
+ # pyright: reportPrivateUsage=false, reportPrivateImportUsage=false
8
+
9
+ import re
10
+ from typing import TYPE_CHECKING, Any, Final, Union, cast
11
+
12
+ from mypy_extensions import trait
13
+ from sqlglot import exp
14
+ from typing_extensions import Self
15
+
16
+ from sqlspec.builder._base import BuiltQuery, QueryBuilder
17
+ from sqlspec.builder._explain import ExplainMixin
18
+ from sqlspec.builder._join import JoinClauseMixin
19
+ from sqlspec.builder._parsing_utils import (
20
+ extract_column_name,
21
+ extract_expression,
22
+ parse_column_expression,
23
+ parse_condition_expression,
24
+ parse_order_expression,
25
+ parse_table_expression,
26
+ to_expression,
27
+ )
28
+ from sqlspec.core import SQL, ParameterStyle, ParameterValidator, SQLResult
29
+ from sqlspec.exceptions import SQLBuilderError
30
+ from sqlspec.utils.type_guards import (
31
+ has_expression_and_sql,
32
+ has_parameter_builder,
33
+ has_sqlglot_expression,
34
+ is_expression,
35
+ is_iterable_parameters,
36
+ )
37
+
38
+ BETWEEN_BOUND_COUNT = 2
39
+ PAIR_LENGTH = 2
40
+ TRIPLE_LENGTH = 3
41
+
42
+
43
+ if TYPE_CHECKING:
44
+ from collections.abc import Callable
45
+
46
+ from sqlglot.dialects.dialect import DialectType
47
+
48
+ from sqlspec.builder._column import Column, ColumnExpression, FunctionColumn
49
+ from sqlspec.builder._expression_wrappers import ExpressionWrapper
50
+ from sqlspec.protocols import SQLBuilderProtocol
51
+
52
+ __all__ = (
53
+ "Case",
54
+ "CaseBuilder",
55
+ "CommonTableExpressionMixin",
56
+ "HavingClauseMixin",
57
+ "LimitOffsetClauseMixin",
58
+ "OrderByClauseMixin",
59
+ "PivotClauseMixin",
60
+ "ReturningClauseMixin",
61
+ "Select",
62
+ "SelectClauseMixin",
63
+ "SetOperationMixin",
64
+ "SubqueryBuilder",
65
+ "UnpivotClauseMixin",
66
+ "WhereClauseMixin",
67
+ "WindowFunctionBuilder",
68
+ )
69
+
70
+
71
+ def is_explicitly_quoted(identifier: Any) -> bool:
72
+ """Detect if identifier was provided with explicit quotes."""
73
+ if not isinstance(identifier, str):
74
+ return False
75
+ stripped = identifier.strip()
76
+ return (stripped.startswith('"') and stripped.endswith('"')) or (
77
+ stripped.startswith("`") and stripped.endswith("`")
78
+ )
79
+
80
+
81
+ def _expr_eq(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
82
+ return exp.EQ(this=col, expression=placeholder)
83
+
84
+
85
+ def _expr_neq(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
86
+ return exp.NEQ(this=col, expression=placeholder)
87
+
88
+
89
+ def _expr_gt(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
90
+ return exp.GT(this=col, expression=placeholder)
91
+
92
+
93
+ def _expr_gte(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
94
+ return exp.GTE(this=col, expression=placeholder)
95
+
96
+
97
+ def _expr_lt(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
98
+ return exp.LT(this=col, expression=placeholder)
99
+
100
+
101
+ def _expr_lte(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
102
+ return exp.LTE(this=col, expression=placeholder)
103
+
104
+
105
+ def _expr_like_exp(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
106
+ return exp.Like(this=col, expression=placeholder)
107
+
108
+
109
+ def _expr_like_method(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
110
+ return cast("exp.Expression", col.like(placeholder))
111
+
112
+
113
+ def _expr_not_like(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
114
+ return exp.Not(this=exp.Like(this=col, expression=placeholder))
115
+
116
+
117
+ def _expr_like_not(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
118
+ return exp.Not(this=cast("exp.Expression", col.like(placeholder)))
119
+
120
+
121
+ def _expr_ilike(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
122
+ return cast("exp.Expression", col.ilike(placeholder))
123
+
124
+
125
+ _SIMPLE_OPERATOR_MAP: dict[str, Any] = {
126
+ "=": _expr_eq,
127
+ "==": _expr_eq,
128
+ "!=": _expr_neq,
129
+ "<>": _expr_neq,
130
+ ">": _expr_gt,
131
+ ">=": _expr_gte,
132
+ "<": _expr_lt,
133
+ "<=": _expr_lte,
134
+ "LIKE": _expr_like_exp,
135
+ "NOT LIKE": _expr_not_like,
136
+ }
137
+
138
+
139
+ class Case:
140
+ """Represent a SQL CASE expression with structured components."""
141
+
142
+ __slots__ = ("conditions", "default")
143
+
144
+ def __init__(self, *ifs: exp.Expression, default: exp.Expression | None = None) -> None:
145
+ self.conditions = list(ifs)
146
+ self.default = default
147
+
148
+ def when(self, condition: str | exp.Expression, result: Any) -> "Case":
149
+ condition_expr = parse_condition_expression(condition)
150
+ result_expr = to_expression(result)
151
+ self.conditions.append(exp.If(this=condition_expr, true=result_expr))
152
+ return self
153
+
154
+ def else_(self, value: Any) -> "Case":
155
+ self.default = to_expression(value)
156
+ return self
157
+
158
+ def end(self) -> "Case":
159
+ return self
160
+
161
+ def as_(self, alias: str) -> exp.Alias:
162
+ return cast("exp.Alias", exp.alias_(self.expression, alias))
163
+
164
+ @property
165
+ def expression(self) -> exp.Case:
166
+ return exp.Case(ifs=self.conditions, default=self.default)
167
+
168
+
169
+ class CaseBuilder:
170
+ """Fluent builder for CASE expressions used within SELECT clauses."""
171
+
172
+ __slots__ = ()
173
+
174
+ def __call__(self, *args: Any, default: Any | None = None) -> Case:
175
+ conditions = [to_expression(arg) for arg in args]
176
+ default_expr = to_expression(default) if default is not None else None
177
+ return Case(*conditions, default=default_expr)
178
+
179
+
180
+ class SubqueryBuilder:
181
+ """Helper to build subquery expressions for EXISTS/IN/ANY/ALL operations."""
182
+
183
+ __slots__ = ("_operation",)
184
+
185
+ def __init__(self, operation: str) -> None:
186
+ self._operation = operation
187
+
188
+ def __call__(self, subquery: Any) -> exp.Expression:
189
+ if isinstance(subquery, exp.Expression):
190
+ subquery_expr = subquery
191
+ elif has_parameter_builder(subquery):
192
+ built_query = subquery.build()
193
+ sql_text = built_query.sql if isinstance(built_query, BuiltQuery) else str(built_query)
194
+ dialect = subquery.dialect if isinstance(subquery, QueryBuilder) else None
195
+ parsed_expr: exp.Expression | None = exp.maybe_parse(sql_text, dialect=dialect)
196
+ if parsed_expr is None:
197
+ msg = f"Could not parse subquery SQL: {sql_text}"
198
+ raise SQLBuilderError(msg)
199
+ subquery_expr = parsed_expr
200
+ else:
201
+ dialect = subquery.dialect if isinstance(subquery, (QueryBuilder, BuiltQuery)) else None
202
+ parsed_expr = exp.maybe_parse(str(subquery), dialect=dialect)
203
+ if parsed_expr is None:
204
+ msg = f"Could not convert subquery to expression: {subquery}"
205
+ raise SQLBuilderError(msg)
206
+ subquery_expr = parsed_expr
207
+
208
+ if self._operation == "exists":
209
+ return exp.Exists(this=subquery_expr)
210
+ if self._operation == "in":
211
+ return exp.In(expressions=[subquery_expr])
212
+ if self._operation == "any":
213
+ return exp.Any(this=subquery_expr)
214
+ if self._operation == "all":
215
+ return exp.All(this=subquery_expr)
216
+ msg = f"Unknown subquery operation: {self._operation}"
217
+ raise SQLBuilderError(msg)
218
+
219
+
220
+ class WindowFunctionBuilder:
221
+ """Helper to fluently construct window function expressions."""
222
+
223
+ __slots__ = ("_function_args", "_function_name", "_order_by", "_partition_by")
224
+
225
+ def __init__(self, function_name: str, *function_args: Any) -> None:
226
+ self._function_name = function_name
227
+ self._function_args: list[exp.Expression] = [to_expression(arg) for arg in function_args]
228
+ self._partition_by: list[exp.Expression] = []
229
+ self._order_by: list[exp.Ordered] = []
230
+
231
+ def __call__(self, *function_args: Any) -> "WindowFunctionBuilder":
232
+ self._function_args = [to_expression(arg) for arg in function_args]
233
+ return self
234
+
235
+ def partition_by(self, *columns: str | exp.Expression) -> "WindowFunctionBuilder":
236
+ self._partition_by = [exp.column(column) if isinstance(column, str) else column for column in columns]
237
+ return self
238
+
239
+ def order_by(self, *columns: str | exp.Expression) -> "WindowFunctionBuilder":
240
+ ordered_columns: list[exp.Ordered] = []
241
+ for column in columns:
242
+ if isinstance(column, str):
243
+ ordered_columns.append(exp.column(column).asc())
244
+ elif isinstance(column, exp.Ordered):
245
+ ordered_columns.append(column)
246
+ else:
247
+ ordered_columns.append(exp.Ordered(this=column, desc=False))
248
+ self._order_by = ordered_columns
249
+ return self
250
+
251
+ def _build_function_expression(self) -> exp.Expression:
252
+ expressions = self._function_args or []
253
+ return exp.Anonymous(this=self._function_name, expressions=expressions)
254
+
255
+ def build(self) -> exp.Window:
256
+ over_args: dict[str, Any] = {}
257
+ if self._partition_by:
258
+ over_args["partition_by"] = self._partition_by
259
+ if self._order_by:
260
+ over_args["order"] = exp.Order(expressions=self._order_by)
261
+ return exp.Window(this=self._build_function_expression(), **over_args)
262
+
263
+ def as_(self, alias: str) -> exp.Alias:
264
+ return cast("exp.Alias", exp.alias_(self.build(), alias))
265
+
266
+
267
+ def _ensure_select_expression(
268
+ mixin: "SQLBuilderProtocol", *, error_message: str, initialize: bool = True
269
+ ) -> exp.Select:
270
+ expression = mixin.get_expression()
271
+ if expression is None and initialize:
272
+ mixin.set_expression(exp.Select())
273
+ expression = mixin.get_expression()
274
+
275
+ if not isinstance(expression, exp.Select):
276
+ raise SQLBuilderError(error_message)
277
+
278
+ return expression
279
+
280
+
281
+ @trait
282
+ class SelectClauseMixin:
283
+ """Mixin providing SELECT clause methods."""
284
+
285
+ __slots__ = ()
286
+
287
+ def get_expression(self) -> exp.Expression | None: ...
288
+ def set_expression(self, expression: exp.Expression) -> None: ...
289
+
290
+ def select(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn", SQL, Case]) -> Self:
291
+ builder = cast("SQLBuilderProtocol", self)
292
+ select_expr = _ensure_select_expression(builder, error_message="Cannot add columns to non-SELECT expression.")
293
+ for column in columns:
294
+ column_expr = column.expression if isinstance(column, Case) else parse_column_expression(column, builder)
295
+ select_expr = select_expr.select(column_expr, copy=False)
296
+ self.set_expression(select_expr)
297
+ return cast("Self", builder)
298
+
299
+ def distinct(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn", SQL]) -> Self:
300
+ builder = cast("SQLBuilderProtocol", self)
301
+ select_expr = _ensure_select_expression(builder, error_message="Cannot add DISTINCT to non-SELECT expression.")
302
+ if not columns:
303
+ select_expr.set("distinct", exp.Distinct())
304
+ else:
305
+ distinct_columns = [parse_column_expression(column, builder) for column in columns]
306
+ select_expr.set("distinct", exp.Distinct(expressions=distinct_columns))
307
+ builder.set_expression(select_expr)
308
+ return cast("Self", builder)
309
+
310
+ def from_(
311
+ self,
312
+ table: str | exp.Expression | Any,
313
+ alias: str | None = None,
314
+ as_of: Any | None = None,
315
+ as_of_type: str | None = None,
316
+ ) -> Self:
317
+ """Set the FROM clause and optionally attach temporal versioning.
318
+
319
+ ``as_of`` copies the resolved table expression, normalizes aliases, and adds an ``exp.Version`` so sqlglot's generator emits dialect-specific time-travel SQL.
320
+ """
321
+ builder = cast("SQLBuilderProtocol", self)
322
+ select_expr = _ensure_select_expression(builder, error_message="FROM clause only valid for SELECT.")
323
+ from_expr: exp.Expression
324
+
325
+ if isinstance(table, str):
326
+ from_expr = parse_table_expression(table, alias)
327
+ elif is_expression(table):
328
+ from_expr = exp.alias_(table, alias) if alias else table
329
+ elif has_parameter_builder(table):
330
+ subquery_expression = table.get_expression()
331
+ if subquery_expression is None:
332
+ msg = "Subquery builder has no expression to include in FROM clause."
333
+ raise SQLBuilderError(msg)
334
+
335
+ subquery_copy = subquery_expression.copy()
336
+ base_builder = cast("QueryBuilder", builder)
337
+ param_mapping = base_builder._merge_cte_parameters(alias or "subquery", table.parameters)
338
+ if param_mapping:
339
+ subquery_copy = base_builder._update_placeholders_in_expression(subquery_copy, param_mapping)
340
+
341
+ wrapped_subquery = exp.paren(subquery_copy)
342
+ from_expr = exp.alias_(wrapped_subquery, alias) if alias else wrapped_subquery
343
+ else:
344
+ from_expr = table
345
+
346
+ if as_of is not None:
347
+ inner_expr = from_expr.copy()
348
+ target_alias = alias
349
+
350
+ if isinstance(inner_expr, exp.Alias):
351
+ target_alias = inner_expr.alias
352
+ inner_expr = inner_expr.this
353
+
354
+ if target_alias is None and isinstance(inner_expr, exp.Table):
355
+ alias_expr = inner_expr.args.get("alias")
356
+ if alias_expr is not None:
357
+ target_alias = alias_expr.this
358
+ inner_expr.set("alias", None)
359
+
360
+ version = exp.Version(this=as_of_type or "TIMESTAMP", kind="AS OF", expression=exp.convert(as_of))
361
+ inner_expr.set("version", version)
362
+ from_expr = exp.alias_(inner_expr, target_alias) if target_alias else inner_expr
363
+
364
+ builder.set_expression(select_expr.from_(from_expr, copy=False))
365
+ return cast("Self", builder)
366
+
367
+ def group_by(self, *columns: str | exp.Expression) -> Self:
368
+ builder = cast("SQLBuilderProtocol", self)
369
+ select_expr = builder.get_expression()
370
+ if select_expr is None or not isinstance(select_expr, exp.Select):
371
+ return cast("Self", builder)
372
+
373
+ for column in columns:
374
+ column_expr = exp.column(column) if isinstance(column, str) else column
375
+ select_expr = select_expr.group_by(column_expr, copy=False)
376
+ builder.set_expression(select_expr)
377
+ return cast("Self", builder)
378
+
379
+ def group_by_rollup(self, *columns: str | exp.Expression) -> Self:
380
+ column_exprs = [exp.column(column) if isinstance(column, str) else column for column in columns]
381
+ rollup_expr = exp.Rollup(expressions=column_exprs)
382
+ return self.group_by(rollup_expr)
383
+
384
+ def group_by_cube(self, *columns: str | exp.Expression) -> Self:
385
+ column_exprs = [exp.column(column) if isinstance(column, str) else column for column in columns]
386
+ cube_expr = exp.Cube(expressions=column_exprs)
387
+ return self.group_by(cube_expr)
388
+
389
+ def group_by_grouping_sets(self, *column_sets: tuple[str, ...] | list[str]) -> Self:
390
+ grouping_sets = [
391
+ exp.Tuple(expressions=[exp.column(col) if isinstance(col, str) else col for col in column_set])
392
+ for column_set in column_sets
393
+ ]
394
+ grouping_expr = exp.GroupingSets(expressions=grouping_sets)
395
+ return self.group_by(grouping_expr)
396
+
397
+
398
+ @trait
399
+ class OrderByClauseMixin:
400
+ __slots__ = ()
401
+
402
+ _expression: exp.Expression | None
403
+
404
+ def order_by(self, *items: Union[str, exp.Ordered, "Column"], desc: bool = False) -> Self:
405
+ builder = cast("SQLBuilderProtocol", self)
406
+ select_expr = _ensure_select_expression(builder, error_message="ORDER BY only valid for SELECT.")
407
+
408
+ current_expr = select_expr
409
+ for item in items:
410
+ if isinstance(item, str):
411
+ order_item = parse_order_expression(item)
412
+ if desc:
413
+ order_item = order_item.desc()
414
+ else:
415
+ extracted_item = extract_expression(item)
416
+ order_item = extracted_item.desc() if desc and not isinstance(item, exp.Ordered) else extracted_item
417
+ current_expr = current_expr.order_by(order_item, copy=False)
418
+ builder.set_expression(current_expr)
419
+ return cast("Self", builder)
420
+
421
+
422
+ @trait
423
+ class LimitOffsetClauseMixin:
424
+ __slots__ = ()
425
+
426
+ _expression: exp.Expression | None
427
+
428
+ def limit(self, value: int) -> Self:
429
+ builder = cast("SQLBuilderProtocol", self)
430
+ select_expr = _ensure_select_expression(builder, error_message="LIMIT only valid for SELECT.")
431
+ builder.set_expression(select_expr.limit(exp.convert(value), copy=False))
432
+ return cast("Self", builder)
433
+
434
+ def offset(self, value: int) -> Self:
435
+ builder = cast("SQLBuilderProtocol", self)
436
+ select_expr = _ensure_select_expression(builder, error_message="OFFSET only valid for SELECT.")
437
+ builder.set_expression(select_expr.offset(exp.convert(value), copy=False))
438
+ return cast("Self", builder)
439
+
440
+
441
+ @trait
442
+ class ReturningClauseMixin:
443
+ __slots__ = ()
444
+
445
+ _expression: exp.Expression | None
446
+
447
+ def returning(self, *columns: Union[str, exp.Expression, "Column", "ExpressionWrapper", Case]) -> Self:
448
+ if self._expression is None:
449
+ msg = "Cannot add RETURNING: expression not initialized."
450
+ raise SQLBuilderError(msg)
451
+ if not isinstance(self._expression, (exp.Insert, exp.Update, exp.Delete)):
452
+ msg = "RETURNING only supported for INSERT, UPDATE, DELETE statements."
453
+ raise SQLBuilderError(msg)
454
+ returning_exprs = [extract_expression(col) for col in columns]
455
+ self._expression.set("returning", exp.Returning(expressions=returning_exprs))
456
+ return self
457
+
458
+
459
+ @trait
460
+ class WhereClauseMixin:
461
+ __slots__ = ()
462
+
463
+ def _merge_sql_object_parameters(self, sql_obj: Any) -> None:
464
+ builder = cast("SQLBuilderProtocol", self)
465
+ builder._merge_sql_object_parameters(sql_obj)
466
+
467
+ def get_expression(self) -> exp.Expression | None: ...
468
+ def set_expression(self, expression: exp.Expression) -> None: ...
469
+
470
+ def _create_parameterized_condition(
471
+ self,
472
+ column: str | exp.Column,
473
+ value: Any,
474
+ condition_factory: "Callable[[exp.Expression, exp.Placeholder], exp.Expression]",
475
+ ) -> exp.Expression:
476
+ builder = cast("SQLBuilderProtocol", self)
477
+ column_name = extract_column_name(column)
478
+ param_name = builder._generate_unique_parameter_name(column_name)
479
+ _, param_name = builder.add_parameter(value, name=param_name)
480
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
481
+ placeholder = exp.Placeholder(this=param_name)
482
+ return condition_factory(col_expr, placeholder)
483
+
484
+ def _get_existing_where_clause(self) -> exp.Where | None:
485
+ builder = cast("SQLBuilderProtocol", self)
486
+ expression = builder.get_expression()
487
+ if isinstance(expression, (exp.Select, exp.Update, exp.Delete)):
488
+ where_clause = expression.args.get("where")
489
+ if isinstance(where_clause, exp.Where):
490
+ return where_clause
491
+ return None
492
+
493
+ def _combine_with_or(self, new_condition: exp.Expression) -> Self:
494
+ builder = cast("SQLBuilderProtocol", self)
495
+ expression = builder.get_expression()
496
+ if expression is None or not isinstance(expression, (exp.Select, exp.Update, exp.Delete)):
497
+ msg = "OR WHERE clause not supported for current expression. Use where() first."
498
+ raise SQLBuilderError(msg)
499
+
500
+ where_clause = self._get_existing_where_clause()
501
+ if where_clause is None or where_clause.this is None:
502
+ msg = "Cannot add OR WHERE clause: no existing WHERE clause found. Use where() before or_where()."
503
+ raise SQLBuilderError(msg)
504
+
505
+ combined_condition = exp.Or(this=where_clause.this, expression=new_condition)
506
+ where_clause.set("this", combined_condition)
507
+ builder.set_expression(expression)
508
+ return cast("Self", builder)
509
+
510
+ def _handle_in_operator(
511
+ self, column_exp: exp.Expression, value: Any, column_name: str = "column"
512
+ ) -> exp.Expression:
513
+ builder = cast("SQLBuilderProtocol", self)
514
+ if has_parameter_builder(value) or isinstance(value, exp.Expression):
515
+ subquery_expr = self._normalize_subquery_expression(value, builder)
516
+ return exp.In(this=column_exp, expressions=[subquery_expr])
517
+ if is_iterable_parameters(value):
518
+ placeholders = []
519
+ for index, element in enumerate(value):
520
+ name_seed = column_name if len(value) == 1 else f"{column_name}_{index + 1}"
521
+ param_name = builder._generate_unique_parameter_name(name_seed)
522
+ _, param_name = builder.add_parameter(element, name=param_name)
523
+ placeholders.append(exp.Placeholder(this=param_name))
524
+ return exp.In(this=column_exp, expressions=placeholders)
525
+
526
+ param_name = builder._generate_unique_parameter_name(column_name)
527
+ _, param_name = builder.add_parameter(value, name=param_name)
528
+ return exp.In(this=column_exp, expressions=[exp.Placeholder(this=param_name)])
529
+
530
+ def _handle_not_in_operator(
531
+ self, column_exp: exp.Expression, value: Any, column_name: str = "column"
532
+ ) -> exp.Expression:
533
+ builder = cast("SQLBuilderProtocol", self)
534
+ if has_parameter_builder(value) or isinstance(value, exp.Expression):
535
+ subquery_expr = self._normalize_subquery_expression(value, builder)
536
+ return exp.Not(this=exp.In(this=column_exp, expressions=[subquery_expr]))
537
+ if is_iterable_parameters(value):
538
+ placeholders = []
539
+ for index, element in enumerate(value):
540
+ name_seed = column_name if len(value) == 1 else f"{column_name}_{index + 1}"
541
+ param_name = builder._generate_unique_parameter_name(name_seed)
542
+ _, param_name = builder.add_parameter(element, name=param_name)
543
+ placeholders.append(exp.Placeholder(this=param_name))
544
+ return exp.Not(this=exp.In(this=column_exp, expressions=placeholders))
545
+
546
+ param_name = builder._generate_unique_parameter_name(column_name)
547
+ _, param_name = builder.add_parameter(value, name=param_name)
548
+ return exp.Not(this=exp.In(this=column_exp, expressions=[exp.Placeholder(this=param_name)]))
549
+
550
+ def _handle_is_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
551
+ value_expr = exp.Null() if value is None else exp.convert(value)
552
+ return exp.Is(this=column_exp, expression=value_expr)
553
+
554
+ def _handle_is_not_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
555
+ value_expr = exp.Null() if value is None else exp.convert(value)
556
+ return exp.Not(this=exp.Is(this=column_exp, expression=value_expr))
557
+
558
+ def _handle_between_operator(
559
+ self, column_exp: exp.Expression, value: Any, column_name: str = "column"
560
+ ) -> exp.Expression:
561
+ if is_iterable_parameters(value) and len(value) == BETWEEN_BOUND_COUNT:
562
+ builder = cast("SQLBuilderProtocol", self)
563
+ low, high = value
564
+ low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
565
+ high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
566
+ _, low_param = builder.add_parameter(low, name=low_param)
567
+ _, high_param = builder.add_parameter(high, name=high_param)
568
+ return exp.Between(
569
+ this=column_exp, low=exp.Placeholder(this=low_param), high=exp.Placeholder(this=high_param)
570
+ )
571
+ msg = f"BETWEEN operator requires a tuple of two values, got {type(value).__name__}"
572
+ raise SQLBuilderError(msg)
573
+
574
+ def _handle_not_between_operator(
575
+ self, column_exp: exp.Expression, value: Any, column_name: str = "column"
576
+ ) -> exp.Expression:
577
+ if is_iterable_parameters(value) and len(value) == BETWEEN_BOUND_COUNT:
578
+ builder = cast("SQLBuilderProtocol", self)
579
+ low, high = value
580
+ low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
581
+ high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
582
+ _, low_param = builder.add_parameter(low, name=low_param)
583
+ _, high_param = builder.add_parameter(high, name=high_param)
584
+ return exp.Not(
585
+ this=exp.Between(
586
+ this=column_exp, low=exp.Placeholder(this=low_param), high=exp.Placeholder(this=high_param)
587
+ )
588
+ )
589
+ msg = f"NOT BETWEEN operator requires a tuple of two values, got {type(value).__name__}"
590
+ raise SQLBuilderError(msg)
591
+
592
+ def _create_any_condition(self, column_expr: exp.Expression, values: Any, column_name: str) -> exp.Expression:
593
+ builder = cast("SQLBuilderProtocol", self)
594
+ if has_parameter_builder(values):
595
+ subquery_expr = self._normalize_subquery_expression(values, builder)
596
+ return exp.EQ(this=column_expr, expression=exp.Any(this=subquery_expr))
597
+ if isinstance(values, exp.Expression):
598
+ return exp.EQ(this=column_expr, expression=exp.Any(this=values))
599
+ if has_sqlglot_expression(values):
600
+ raw_expr = values.sqlglot_expression
601
+ if isinstance(raw_expr, exp.Expression):
602
+ return exp.EQ(this=column_expr, expression=exp.Any(this=raw_expr))
603
+ parsed_expr: exp.Expression | None = exp.maybe_parse(str(values), dialect=builder.dialect)
604
+ if parsed_expr is not None:
605
+ return exp.EQ(this=column_expr, expression=exp.Any(this=parsed_expr))
606
+ if has_expression_and_sql(values):
607
+ self._merge_sql_object_parameters(values)
608
+ expression_attr = values.expression
609
+ if isinstance(expression_attr, exp.Expression):
610
+ return exp.EQ(this=column_expr, expression=exp.Any(this=expression_attr))
611
+ sql_text = values.sql
612
+ parsed_expr = exp.maybe_parse(sql_text, dialect=builder.dialect)
613
+ if parsed_expr is not None:
614
+ return exp.EQ(this=column_expr, expression=exp.Any(this=parsed_expr))
615
+ if isinstance(values, str):
616
+ parsed_expr = exp.maybe_parse(values, dialect=builder.dialect)
617
+ if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
618
+ return exp.EQ(this=column_expr, expression=exp.Any(this=exp.paren(parsed_expr)))
619
+ msg = "Unsupported type for 'values' in WHERE ANY"
620
+ raise SQLBuilderError(msg)
621
+ if not is_iterable_parameters(values) or isinstance(values, (bytes, bytearray)):
622
+ msg = "Unsupported type for 'values' in WHERE ANY"
623
+ raise SQLBuilderError(msg)
624
+ placeholders: list[exp.Expression] = []
625
+ values_list = list(values)
626
+ for index, element in enumerate(values_list):
627
+ if len(values_list) == 1:
628
+ param_name = builder._generate_unique_parameter_name(column_name)
629
+ else:
630
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_any_{index + 1}")
631
+ _, param_name = builder.add_parameter(element, name=param_name)
632
+ placeholders.append(exp.Placeholder(this=param_name))
633
+ tuple_expr = exp.Tuple(expressions=placeholders)
634
+ return exp.EQ(this=column_expr, expression=exp.Any(this=tuple_expr))
635
+
636
+ def _create_not_any_condition(self, column_expr: exp.Expression, values: Any, column_name: str) -> exp.Expression:
637
+ builder = cast("SQLBuilderProtocol", self)
638
+ if has_parameter_builder(values):
639
+ subquery_expr = self._normalize_subquery_expression(values, builder)
640
+ return exp.NEQ(this=column_expr, expression=exp.Any(this=subquery_expr))
641
+ if isinstance(values, exp.Expression):
642
+ return exp.NEQ(this=column_expr, expression=exp.Any(this=values))
643
+ if has_sqlglot_expression(values):
644
+ raw_expr = values.sqlglot_expression
645
+ if isinstance(raw_expr, exp.Expression):
646
+ return exp.NEQ(this=column_expr, expression=exp.Any(this=raw_expr))
647
+ parsed_expr: exp.Expression | None = exp.maybe_parse(str(values), dialect=builder.dialect)
648
+ if parsed_expr is not None:
649
+ return exp.NEQ(this=column_expr, expression=exp.Any(this=parsed_expr))
650
+ if has_expression_and_sql(values):
651
+ self._merge_sql_object_parameters(values)
652
+ expression_attr = values.expression
653
+ if isinstance(expression_attr, exp.Expression):
654
+ return exp.NEQ(this=column_expr, expression=exp.Any(this=expression_attr))
655
+ sql_text = values.sql
656
+ parsed_expr = exp.maybe_parse(sql_text, dialect=builder.dialect)
657
+ if parsed_expr is not None:
658
+ return exp.NEQ(this=column_expr, expression=exp.Any(this=parsed_expr))
659
+ if isinstance(values, str):
660
+ parsed_expr = exp.maybe_parse(values, dialect=builder.dialect)
661
+ if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
662
+ return exp.NEQ(this=column_expr, expression=exp.Any(this=exp.paren(parsed_expr)))
663
+ msg = "Unsupported type for 'values' in WHERE NOT ANY"
664
+ raise SQLBuilderError(msg)
665
+ if not is_iterable_parameters(values) or isinstance(values, (bytes, bytearray)):
666
+ msg = "Unsupported type for 'values' in WHERE NOT ANY"
667
+ raise SQLBuilderError(msg)
668
+ placeholders: list[exp.Expression] = []
669
+ values_list = list(values)
670
+ for index, element in enumerate(values_list):
671
+ if len(values_list) == 1:
672
+ param_name = builder._generate_unique_parameter_name(column_name)
673
+ else:
674
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_not_any_{index + 1}")
675
+ _, param_name = builder.add_parameter(element, name=param_name)
676
+ placeholders.append(exp.Placeholder(this=param_name))
677
+ tuple_expr = exp.Tuple(expressions=placeholders)
678
+ return exp.NEQ(this=column_expr, expression=exp.Any(this=tuple_expr))
679
+
680
+ def _normalize_subquery_expression(self, subquery: Any, builder: "SQLBuilderProtocol") -> exp.Expression:
681
+ if has_parameter_builder(subquery):
682
+ subquery_builder = cast("QueryBuilder", subquery)
683
+ safe_query: BuiltQuery = subquery_builder.build()
684
+ parsed_subquery: exp.Expression | None = exp.maybe_parse(safe_query.sql, dialect=builder.dialect)
685
+ if parsed_subquery is None:
686
+ msg = f"Could not parse subquery SQL: {safe_query.sql}"
687
+ raise SQLBuilderError(msg)
688
+ subquery_expr = exp.paren(parsed_subquery)
689
+ parameters: Any = safe_query.parameters
690
+ if isinstance(parameters, dict):
691
+ param_mapping: dict[str, str] = {}
692
+ query_builder = cast("QueryBuilder", builder)
693
+ for param_name, param_value in parameters.items():
694
+ unique_name = query_builder._generate_unique_parameter_name(param_name)
695
+ param_mapping[param_name] = unique_name
696
+ query_builder.add_parameter(param_value, name=unique_name)
697
+ if param_mapping:
698
+ updated = query_builder._update_placeholders_in_expression(parsed_subquery, param_mapping)
699
+ subquery_expr = exp.paren(updated)
700
+ elif isinstance(parameters, (list, tuple)):
701
+ for param_value in parameters:
702
+ builder.add_parameter(param_value)
703
+ elif parameters is not None:
704
+ builder.add_parameter(parameters)
705
+ return subquery_expr
706
+
707
+ if has_expression_and_sql(subquery):
708
+ self._merge_sql_object_parameters(subquery)
709
+ expression_attr = subquery.expression
710
+ if isinstance(expression_attr, exp.Expression):
711
+ return expression_attr
712
+ sql_text = subquery.sql
713
+ parsed_from_sql: exp.Expression | None = exp.maybe_parse(sql_text, dialect=builder.dialect)
714
+ if parsed_from_sql is None:
715
+ msg = f"Could not parse subquery SQL: {sql_text}"
716
+ raise SQLBuilderError(msg)
717
+ return parsed_from_sql
718
+
719
+ if isinstance(subquery, exp.Expression):
720
+ return subquery
721
+
722
+ if isinstance(subquery, str):
723
+ parsed_expression_from_str: exp.Expression | None = exp.maybe_parse(subquery, dialect=builder.dialect)
724
+ if parsed_expression_from_str is None:
725
+ msg = f"Could not parse subquery SQL: {subquery}"
726
+ raise SQLBuilderError(msg)
727
+ return parsed_expression_from_str
728
+
729
+ converted_expr: exp.Expression = exp.convert(subquery)
730
+ return converted_expr
731
+
732
+ def _create_or_expression(self, conditions: "list[exp.Expression]") -> exp.Expression:
733
+ if not conditions:
734
+ msg = "OR expression requires at least one condition"
735
+ raise SQLBuilderError(msg)
736
+
737
+ return exp.or_(*conditions)
738
+
739
+ def _process_tuple_condition(self, condition: "tuple[Any, ...]") -> exp.Expression:
740
+ if len(condition) == PAIR_LENGTH:
741
+ column, value = condition
742
+ return self._create_parameterized_condition(column, value, _expr_eq)
743
+
744
+ if len(condition) != TRIPLE_LENGTH:
745
+ msg = f"Condition tuple must have 2 or 3 elements, got {len(condition)}"
746
+ raise SQLBuilderError(msg)
747
+
748
+ column_raw, operator, value = condition
749
+ operator_upper = str(operator).upper()
750
+ column_expr = parse_column_expression(column_raw)
751
+ column_name = extract_column_name(column_raw)
752
+
753
+ if operator_upper in _SIMPLE_OPERATOR_MAP:
754
+ return self._create_parameterized_condition(column_raw, value, _SIMPLE_OPERATOR_MAP[operator_upper])
755
+
756
+ if operator_upper == "IN":
757
+ return self._handle_in_operator(column_expr, value, column_name)
758
+ if operator_upper == "NOT IN":
759
+ return self._handle_not_in_operator(column_expr, value, column_name)
760
+ if operator_upper == "IS":
761
+ return self._handle_is_operator(column_expr, value)
762
+ if operator_upper == "IS NOT":
763
+ return self._handle_is_not_operator(column_expr, value)
764
+ if operator_upper == "BETWEEN":
765
+ return self._handle_between_operator(column_expr, value, column_name)
766
+ if operator_upper == "NOT BETWEEN":
767
+ return self._handle_not_between_operator(column_expr, value, column_name)
768
+
769
+ msg = f"Unsupported operator: {operator}"
770
+ raise SQLBuilderError(msg)
771
+
772
+ def _process_where_condition(
773
+ self,
774
+ condition: Union[
775
+ str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression", SQL
776
+ ],
777
+ values: tuple[Any, ...],
778
+ operator: str | None,
779
+ kwargs: dict[str, Any],
780
+ ) -> exp.Expression:
781
+ if values or kwargs:
782
+ if not isinstance(condition, str):
783
+ msg = "When values are provided, condition must be a string"
784
+ raise SQLBuilderError(msg)
785
+
786
+ validator = ParameterValidator()
787
+ param_info = validator.extract_parameters(condition)
788
+
789
+ if param_info:
790
+ param_dict = dict(kwargs)
791
+ positional_params = [
792
+ info
793
+ for info in param_info
794
+ if info.style in {ParameterStyle.NUMERIC, ParameterStyle.POSITIONAL_COLON, ParameterStyle.QMARK}
795
+ ]
796
+
797
+ if len(values) != len(positional_params):
798
+ msg = (
799
+ "Parameter count mismatch: condition has "
800
+ f"{len(positional_params)} positional placeholders, got {len(values)} values"
801
+ )
802
+ raise SQLBuilderError(msg)
803
+
804
+ for index, value in enumerate(values):
805
+ param_dict[f"param_{index}"] = value
806
+
807
+ condition = SQL(condition, param_dict)
808
+ elif len(values) == 1 and not kwargs:
809
+ if operator is not None:
810
+ return self._process_tuple_condition((condition, operator, values[0]))
811
+ return self._process_tuple_condition((condition, values[0]))
812
+ else:
813
+ msg = f"Cannot bind parameters to condition without placeholders: {condition}"
814
+ raise SQLBuilderError(msg)
815
+
816
+ builder = cast("SQLBuilderProtocol", self)
817
+
818
+ if isinstance(condition, str):
819
+ return parse_condition_expression(condition)
820
+ if isinstance(condition, (exp.Expression, exp.Condition)):
821
+ return condition
822
+ if isinstance(condition, tuple):
823
+ return self._process_tuple_condition(condition)
824
+ if has_parameter_builder(condition):
825
+ column_expr_obj = cast("ColumnExpression", condition)
826
+ expression_attr = cast("exp.Expression | None", column_expr_obj._expression)
827
+ if expression_attr is None:
828
+ msg = "Column expression is missing underlying sqlglot expression."
829
+ raise SQLBuilderError(msg)
830
+ return expression_attr
831
+ if has_sqlglot_expression(condition):
832
+ raw_expr = condition.sqlglot_expression
833
+ if isinstance(raw_expr, exp.Expression):
834
+ return builder._parameterize_expression(raw_expr)
835
+ return parse_condition_expression(str(condition))
836
+ if has_expression_and_sql(condition):
837
+ expression_attr = condition.expression
838
+ if isinstance(expression_attr, exp.Expression):
839
+ self._merge_sql_object_parameters(condition)
840
+ return expression_attr
841
+ sql_text = condition.sql
842
+ self._merge_sql_object_parameters(condition)
843
+ return parse_condition_expression(sql_text)
844
+
845
+ msg = f"Unsupported condition type: {type(condition).__name__}"
846
+ raise SQLBuilderError(msg)
847
+
848
+ def where(
849
+ self,
850
+ condition: Union[
851
+ str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression", SQL
852
+ ],
853
+ *values: Any,
854
+ operator: str | None = None,
855
+ **kwargs: Any,
856
+ ) -> Self:
857
+ builder = cast("SQLBuilderProtocol", self)
858
+ current_expr = builder.get_expression()
859
+ if current_expr is None:
860
+ msg = "Cannot add WHERE clause: expression is not initialized."
861
+ raise SQLBuilderError(msg)
862
+
863
+ if isinstance(current_expr, exp.Delete) and not current_expr.args.get("this"):
864
+ msg = "WHERE clause requires a table to be set. Use from() to set the table first."
865
+ raise SQLBuilderError(msg)
866
+
867
+ where_expr = self._process_where_condition(condition, values, operator, kwargs)
868
+
869
+ if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)):
870
+ updated_expr = current_expr.where(where_expr, copy=False)
871
+ builder.set_expression(updated_expr)
872
+ return cast("Self", builder)
873
+ msg = f"WHERE clause not supported for {type(current_expr).__name__}"
874
+ raise SQLBuilderError(msg)
875
+
876
+ def where_eq(self, column: str | exp.Column, value: Any) -> Self:
877
+ condition = self._create_parameterized_condition(column, value, _expr_eq)
878
+ return self.where(condition)
879
+
880
+ def where_neq(self, column: str | exp.Column, value: Any) -> Self:
881
+ condition = self._create_parameterized_condition(column, value, _expr_neq)
882
+ return self.where(condition)
883
+
884
+ def where_lt(self, column: str | exp.Column, value: Any) -> Self:
885
+ condition = self._create_parameterized_condition(column, value, _expr_lt)
886
+ return self.where(condition)
887
+
888
+ def where_lte(self, column: str | exp.Column, value: Any) -> Self:
889
+ condition = self._create_parameterized_condition(column, value, _expr_lte)
890
+ return self.where(condition)
891
+
892
+ def where_gt(self, column: str | exp.Column, value: Any) -> Self:
893
+ condition = self._create_parameterized_condition(column, value, _expr_gt)
894
+ return self.where(condition)
895
+
896
+ def where_gte(self, column: str | exp.Column, value: Any) -> Self:
897
+ condition = self._create_parameterized_condition(column, value, _expr_gte)
898
+ return self.where(condition)
899
+
900
+ def where_between(self, column: str | exp.Column, low: Any, high: Any) -> Self:
901
+ builder = cast("SQLBuilderProtocol", self)
902
+ column_name = extract_column_name(column)
903
+ low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
904
+ high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
905
+ _, low_param = builder.add_parameter(low, name=low_param)
906
+ _, high_param = builder.add_parameter(high, name=high_param)
907
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
908
+ condition: exp.Expression = col_expr.between(exp.Placeholder(this=low_param), exp.Placeholder(this=high_param))
909
+ return self.where(condition)
910
+
911
+ def where_like(self, column: str | exp.Column, pattern: str, escape: str | None = None) -> Self:
912
+ builder = cast("SQLBuilderProtocol", self)
913
+ column_name = extract_column_name(column)
914
+ param_name = builder._generate_unique_parameter_name(column_name)
915
+ _, param_name = builder.add_parameter(pattern, name=param_name)
916
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
917
+ if escape is not None:
918
+ condition = exp.Like(
919
+ this=col_expr, expression=exp.Placeholder(this=param_name), escape=exp.convert(str(escape))
920
+ )
921
+ else:
922
+ condition = col_expr.like(exp.Placeholder(this=param_name))
923
+ return self.where(condition)
924
+
925
+ def where_not_like(self, column: str | exp.Column, pattern: str) -> Self:
926
+ condition = self._create_parameterized_condition(column, pattern, _expr_not_like)
927
+ return self.where(condition)
928
+
929
+ def where_ilike(self, column: str | exp.Column, pattern: str) -> Self:
930
+ condition = self._create_parameterized_condition(column, pattern, _expr_ilike)
931
+ return self.where(condition)
932
+
933
+ def where_is_null(self, column: str | exp.Column) -> Self:
934
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
935
+ condition: exp.Expression = col_expr.is_(exp.null())
936
+ return self.where(condition)
937
+
938
+ def where_is_not_null(self, column: str | exp.Column) -> Self:
939
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
940
+ condition: exp.Expression = col_expr.is_(exp.null()).not_()
941
+ return self.where(condition)
942
+
943
+ def where_in(self, column: str | exp.Column, values: Any) -> Self:
944
+ builder = cast("SQLBuilderProtocol", self)
945
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
946
+ if has_parameter_builder(values) or isinstance(values, (exp.Expression, str)):
947
+ subquery_exp = self._normalize_subquery_expression(values, builder)
948
+ return self.where(exp.In(this=col_expr, expressions=[subquery_exp]))
949
+
950
+ condition = self._handle_in_operator(col_expr, values, extract_column_name(column))
951
+ return self.where(condition)
952
+
953
+ def where_not_in(self, column: str | exp.Column, values: Any) -> Self:
954
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
955
+ condition = self._handle_not_in_operator(col_expr, values, extract_column_name(column))
956
+ return self.where(condition)
957
+
958
+ def where_any(self, column: str | exp.Column, subquery: Any) -> Self:
959
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
960
+ column_name = extract_column_name(column)
961
+ condition = self._create_any_condition(col_expr, subquery, column_name)
962
+ return self.where(condition)
963
+
964
+ def where_not_any(self, column: str | exp.Column, subquery: Any) -> Self:
965
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
966
+ column_name = extract_column_name(column)
967
+ condition = self._create_not_any_condition(col_expr, subquery, column_name)
968
+ return self.where(condition)
969
+
970
+ def where_exists(self, subquery: Any) -> Self:
971
+ builder = cast("SQLBuilderProtocol", self)
972
+ subquery_expr = self._normalize_subquery_expression(subquery, builder)
973
+ return self.where(exp.Exists(this=subquery_expr))
974
+
975
+ def where_not_exists(self, subquery: Any) -> Self:
976
+ builder = cast("SQLBuilderProtocol", self)
977
+ subquery_expr = self._normalize_subquery_expression(subquery, builder)
978
+ return self.where(exp.Not(this=exp.Exists(this=subquery_expr)))
979
+
980
+ def where_like_any(self, column: str | exp.Column, patterns: list[str]) -> Self:
981
+ conditions = [self._create_parameterized_condition(column, pattern, _expr_like_method) for pattern in patterns]
982
+ or_condition = self._create_or_expression(conditions)
983
+ return self.where(or_condition)
984
+
985
+ def or_where_eq(self, column: str | exp.Column, value: Any) -> Self:
986
+ condition = self._create_parameterized_condition(column, value, _expr_eq)
987
+ return self._combine_with_or(condition)
988
+
989
+ def or_where_neq(self, column: str | exp.Column, value: Any) -> Self:
990
+ condition = self._create_parameterized_condition(column, value, _expr_neq)
991
+ return self._combine_with_or(condition)
992
+
993
+ def or_where_lt(self, column: str | exp.Column, value: Any) -> Self:
994
+ condition = self._create_parameterized_condition(column, value, _expr_lt)
995
+ return self._combine_with_or(condition)
996
+
997
+ def or_where_lte(self, column: str | exp.Column, value: Any) -> Self:
998
+ condition = self._create_parameterized_condition(column, value, _expr_lte)
999
+ return self._combine_with_or(condition)
1000
+
1001
+ def or_where_gt(self, column: str | exp.Column, value: Any) -> Self:
1002
+ condition = self._create_parameterized_condition(column, value, _expr_gt)
1003
+ return self._combine_with_or(condition)
1004
+
1005
+ def or_where_gte(self, column: str | exp.Column, value: Any) -> Self:
1006
+ condition = self._create_parameterized_condition(column, value, _expr_gte)
1007
+ return self._combine_with_or(condition)
1008
+
1009
+ def or_where_between(self, column: str | exp.Column, low: Any, high: Any) -> Self:
1010
+ column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
1011
+ condition = self._handle_between_operator(column_expr, (low, high), extract_column_name(column))
1012
+ return self._combine_with_or(condition)
1013
+
1014
+ def or_where_like(self, column: str | exp.Column, pattern: str, escape: str | None = None) -> Self:
1015
+ column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
1016
+ builder = cast("SQLBuilderProtocol", self)
1017
+ column_name = extract_column_name(column)
1018
+ param_name = builder._generate_unique_parameter_name(column_name)
1019
+ _, param_name = builder.add_parameter(pattern, name=param_name)
1020
+ placeholder = exp.Placeholder(this=param_name)
1021
+ if escape is not None:
1022
+ condition = exp.Like(this=column_expr, expression=placeholder, escape=exp.convert(str(escape)))
1023
+ else:
1024
+ condition = column_expr.like(placeholder)
1025
+ return self._combine_with_or(cast("exp.Expression", condition))
1026
+
1027
+ def or_where_not_like(self, column: str | exp.Column, pattern: str) -> Self:
1028
+ condition = self._create_parameterized_condition(column, pattern, _expr_like_not)
1029
+ return self._combine_with_or(condition)
1030
+
1031
+ def or_where_ilike(self, column: str | exp.Column, pattern: str) -> Self:
1032
+ condition = self._create_parameterized_condition(column, pattern, _expr_ilike)
1033
+ return self._combine_with_or(condition)
1034
+
1035
+ def or_where_is_null(self, column: str | exp.Column) -> Self:
1036
+ column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
1037
+ condition: exp.Expression = column_expr.is_(exp.null())
1038
+ return self._combine_with_or(condition)
1039
+
1040
+ def or_where_is_not_null(self, column: str | exp.Column) -> Self:
1041
+ column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
1042
+ condition: exp.Expression = column_expr.is_(exp.null()).not_()
1043
+ return self._combine_with_or(condition)
1044
+
1045
+ def or_where_null(self, column: str | exp.Column) -> Self:
1046
+ return self.or_where_is_null(column)
1047
+
1048
+ def or_where_not_null(self, column: str | exp.Column) -> Self:
1049
+ return self.or_where_is_not_null(column)
1050
+
1051
+ def or_where_in(self, column: str | exp.Column, values: Any) -> Self:
1052
+ column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
1053
+ condition = self._handle_in_operator(column_expr, values, extract_column_name(column))
1054
+ return self._combine_with_or(condition)
1055
+
1056
+ def or_where_not_in(self, column: str | exp.Column, values: Any) -> Self:
1057
+ column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
1058
+ condition = self._handle_not_in_operator(column_expr, values, extract_column_name(column))
1059
+ return self._combine_with_or(condition)
1060
+
1061
+ def or_where_any(self, column: str | exp.Column, subquery: Any) -> Self:
1062
+ column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
1063
+ condition = self._create_any_condition(column_expr, subquery, extract_column_name(column))
1064
+ return self._combine_with_or(condition)
1065
+
1066
+ def or_where_not_any(self, column: str | exp.Column, subquery: Any) -> Self:
1067
+ column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
1068
+ condition = self._create_not_any_condition(column_expr, subquery, extract_column_name(column))
1069
+ return self._combine_with_or(condition)
1070
+
1071
+ def or_where_exists(self, subquery: Any) -> Self:
1072
+ builder = cast("SQLBuilderProtocol", self)
1073
+ subquery_expr = self._normalize_subquery_expression(subquery, builder)
1074
+ condition = exp.Exists(this=subquery_expr)
1075
+ return self._combine_with_or(condition)
1076
+
1077
+ def or_where_not_exists(self, subquery: Any) -> Self:
1078
+ builder = cast("SQLBuilderProtocol", self)
1079
+ subquery_expr = self._normalize_subquery_expression(subquery, builder)
1080
+ condition = exp.Not(this=exp.Exists(this=subquery_expr))
1081
+ return self._combine_with_or(condition)
1082
+
1083
+ def where_or(self, *conditions: str | tuple[str, Any] | tuple[str, str, Any] | exp.Expression) -> Self:
1084
+ if not conditions:
1085
+ msg = "where_or() requires at least one condition"
1086
+ raise SQLBuilderError(msg)
1087
+
1088
+ builder = cast("SQLBuilderProtocol", self)
1089
+ if builder.get_expression() is None:
1090
+ msg = "Cannot add WHERE OR clause: expression is not initialized."
1091
+ raise SQLBuilderError(msg)
1092
+
1093
+ processed_conditions = [self._process_where_condition(condition, (), None, {}) for condition in conditions]
1094
+ or_condition = self._create_or_expression(processed_conditions)
1095
+ return self.where(or_condition)
1096
+
1097
+ def or_where(
1098
+ self,
1099
+ condition: Union[
1100
+ str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression", SQL
1101
+ ],
1102
+ *values: Any,
1103
+ operator: str | None = None,
1104
+ **kwargs: Any,
1105
+ ) -> Self:
1106
+ or_condition = self._process_where_condition(condition, values, operator, kwargs)
1107
+ return self._combine_with_or(or_condition)
1108
+
1109
+
1110
+ @trait
1111
+ class HavingClauseMixin:
1112
+ __slots__ = ()
1113
+
1114
+ def having(self, condition: str | exp.Expression | exp.Condition | tuple[str, Any] | tuple[str, str, Any]) -> Self:
1115
+ builder = cast("SQLBuilderProtocol", self)
1116
+ current_expr = builder.get_expression()
1117
+ if current_expr is None or not isinstance(current_expr, exp.Select):
1118
+ return cast("Self", builder)
1119
+
1120
+ if isinstance(condition, tuple):
1121
+ where_mixin = cast("WhereClauseMixin", self)
1122
+ having_expr = where_mixin._process_tuple_condition(condition)
1123
+ else:
1124
+ having_expr = parse_condition_expression(condition)
1125
+
1126
+ builder.set_expression(current_expr.having(having_expr, copy=False))
1127
+ return cast("Self", builder)
1128
+
1129
+
1130
+ @trait
1131
+ class PivotClauseMixin:
1132
+ __slots__ = ()
1133
+
1134
+ def pivot(
1135
+ self,
1136
+ aggregate_function: str | exp.Expression,
1137
+ aggregate_column: str | exp.Expression,
1138
+ pivot_column: str | exp.Expression,
1139
+ pivot_values: list[str | int | float | exp.Expression],
1140
+ alias: str | None = None,
1141
+ ) -> "Select":
1142
+ builder = cast("SQLBuilderProtocol", self)
1143
+ current_expr = builder.get_expression()
1144
+ if not isinstance(current_expr, exp.Select):
1145
+ msg = "Pivot can only be applied to a Select expression managed by SelectBuilder."
1146
+ raise TypeError(msg)
1147
+
1148
+ agg_name = aggregate_function if isinstance(aggregate_function, str) else aggregate_function.name
1149
+ agg_column = exp.column(aggregate_column) if isinstance(aggregate_column, str) else aggregate_column
1150
+ pivot_col_expr = exp.column(pivot_column) if isinstance(pivot_column, str) else pivot_column
1151
+
1152
+ pivot_agg_expr = exp.func(agg_name, agg_column)
1153
+
1154
+ pivot_value_exprs: list[exp.Expression] = []
1155
+ for raw_value in pivot_values:
1156
+ if isinstance(raw_value, exp.Expression):
1157
+ pivot_value_exprs.append(raw_value)
1158
+ elif isinstance(raw_value, (str, int, float)):
1159
+ pivot_value_exprs.append(exp.convert(raw_value))
1160
+ else:
1161
+ pivot_value_exprs.append(exp.convert(str(raw_value)))
1162
+
1163
+ in_expr = exp.In(this=pivot_col_expr, expressions=pivot_value_exprs)
1164
+ pivot_node = exp.Pivot(expressions=[pivot_agg_expr], fields=[in_expr], unpivot=False)
1165
+
1166
+ if alias:
1167
+ pivot_node.set("alias", exp.TableAlias(this=exp.to_identifier(alias)))
1168
+
1169
+ from_clause = current_expr.args.get("from")
1170
+ if from_clause and isinstance(from_clause, exp.From):
1171
+ table = from_clause.this
1172
+ if isinstance(table, exp.Table):
1173
+ existing = table.args.get("pivots", [])
1174
+ existing.append(pivot_node)
1175
+ table.set("pivots", existing)
1176
+
1177
+ return cast("Select", self)
1178
+
1179
+
1180
+ @trait
1181
+ class UnpivotClauseMixin:
1182
+ __slots__ = ()
1183
+
1184
+ def unpivot(
1185
+ self,
1186
+ value_column_name: str,
1187
+ name_column_name: str,
1188
+ columns_to_unpivot: list[str | exp.Expression],
1189
+ alias: str | None = None,
1190
+ ) -> "Select":
1191
+ builder = cast("SQLBuilderProtocol", self)
1192
+ current_expr = builder.get_expression()
1193
+ if not isinstance(current_expr, exp.Select):
1194
+ msg = "Unpivot can only be applied to a Select expression managed by Select."
1195
+ raise TypeError(msg)
1196
+
1197
+ value_identifier = exp.to_identifier(value_column_name)
1198
+ name_identifier = exp.to_identifier(name_column_name)
1199
+
1200
+ unpivot_columns: list[exp.Expression] = []
1201
+ for column in columns_to_unpivot:
1202
+ if isinstance(column, exp.Expression):
1203
+ unpivot_columns.append(column)
1204
+ elif isinstance(column, str):
1205
+ unpivot_columns.append(exp.column(column))
1206
+ else:
1207
+ unpivot_columns.append(exp.column(str(column)))
1208
+
1209
+ in_expr = exp.In(this=name_identifier, expressions=unpivot_columns)
1210
+ unpivot_node = exp.Pivot(expressions=[value_identifier], fields=[in_expr], unpivot=True)
1211
+
1212
+ if alias:
1213
+ unpivot_node.set("alias", exp.TableAlias(this=exp.to_identifier(alias)))
1214
+
1215
+ from_clause = current_expr.args.get("from")
1216
+ if from_clause and isinstance(from_clause, exp.From):
1217
+ table = from_clause.this
1218
+ if isinstance(table, exp.Table):
1219
+ existing = table.args.get("pivots", [])
1220
+ existing.append(unpivot_node)
1221
+ table.set("pivots", existing)
1222
+
1223
+ return cast("Select", self)
1224
+
1225
+
1226
+ @trait
1227
+ class CommonTableExpressionMixin:
1228
+ __slots__ = ()
1229
+
1230
+ def get_expression(self) -> exp.Expression | None: ...
1231
+ def set_expression(self, expression: exp.Expression) -> None: ...
1232
+
1233
+ _with_ctes: Any
1234
+ dialect: Any
1235
+
1236
+ def with_(self, name: str, query: Any | str, recursive: bool = False, columns: list[str] | None = None) -> Self:
1237
+ """Add a CTE via the WITH clause.
1238
+
1239
+ When ``query`` is another builder we reuse its expression, merge parameters with unique names, and let sqlglot handle the actual CTE wrapping to avoid duplicating ``_with_ctes`` state.
1240
+ """
1241
+ builder = cast("QueryBuilder", self)
1242
+ expression = builder.get_expression()
1243
+ if expression is None:
1244
+ msg = "Cannot add WITH clause: expression not initialized."
1245
+ raise SQLBuilderError(msg)
1246
+
1247
+ if not isinstance(expression, (exp.Select, exp.Insert, exp.Update, exp.Delete)):
1248
+ msg = f"Cannot add WITH clause to {type(expression).__name__} expression."
1249
+ raise SQLBuilderError(msg)
1250
+
1251
+ cte_select: exp.Expression | None
1252
+ if isinstance(query, str):
1253
+ cte_select = exp.maybe_parse(query, dialect=self.dialect)
1254
+ elif isinstance(query, exp.Expression):
1255
+ cte_select = query
1256
+ else:
1257
+ cte_select = query.get_expression()
1258
+ if cte_select is None:
1259
+ msg = f"Could not get expression from builder: {query}"
1260
+ raise SQLBuilderError(msg)
1261
+
1262
+ built_query = query.to_statement()
1263
+ parameters = built_query.parameters
1264
+ if isinstance(parameters, dict):
1265
+ param_mapping: dict[str, str] = {}
1266
+ for param_name, param_value in parameters.items():
1267
+ unique_name = builder._generate_unique_parameter_name(f"{name}_{param_name}")
1268
+ param_mapping[param_name] = unique_name
1269
+ builder.add_parameter(param_value, name=unique_name)
1270
+ cte_select = builder._update_placeholders_in_expression(cte_select, param_mapping)
1271
+ elif isinstance(parameters, (list, tuple)):
1272
+ for param_value in parameters:
1273
+ builder.add_parameter(param_value)
1274
+ elif parameters is not None:
1275
+ builder.add_parameter(parameters)
1276
+
1277
+ if cte_select is None:
1278
+ msg = f"Could not parse CTE query: {query}"
1279
+ raise SQLBuilderError(msg)
1280
+
1281
+ if isinstance(expression, (exp.Select, exp.Insert, exp.Update)):
1282
+ updated = expression.with_(name, as_=cte_select.copy(), recursive=recursive, copy=True)
1283
+ builder.set_expression(updated)
1284
+
1285
+ return cast("Self", builder)
1286
+
1287
+
1288
+ @trait
1289
+ class SetOperationMixin:
1290
+ __slots__ = ()
1291
+
1292
+ def get_expression(self) -> exp.Expression | None: ...
1293
+ def set_expression(self, expression: exp.Expression) -> None: ...
1294
+ def set_parameters(self, parameters: dict[str, Any]) -> None: ...
1295
+
1296
+ dialect: Any = None
1297
+
1298
+ def union(self, other: Any, all_: bool = False) -> Self:
1299
+ return self._combine_with_other(other, operator="union", distinct=not all_)
1300
+
1301
+ def intersect(self, other: Any) -> Self:
1302
+ return self._combine_with_other(other, operator="intersect", distinct=True)
1303
+
1304
+ def except_(self, other: Any) -> Self:
1305
+ return self._combine_with_other(other, operator="except", distinct=True)
1306
+
1307
+ def _combine_with_other(self, other: Any, *, operator: str, distinct: bool) -> Self:
1308
+ builder = cast("QueryBuilder", self)
1309
+
1310
+ if not isinstance(other, QueryBuilder):
1311
+ msg = "Set operations require another SQLSpec query builder."
1312
+ raise SQLBuilderError(msg)
1313
+
1314
+ other_builder = other
1315
+ left_expr = builder._build_final_expression(copy=True)
1316
+ right_expr = other_builder._build_final_expression(copy=True)
1317
+
1318
+ merged_parameters: dict[str, Any] = dict(builder.parameters)
1319
+ rename_map: dict[str, str] = {}
1320
+ for param_name, param_value in other_builder.parameters.items():
1321
+ target_name = param_name
1322
+ if target_name in merged_parameters:
1323
+ counter = 1
1324
+ while True:
1325
+ candidate = f"{param_name}_right_{counter}"
1326
+ if candidate not in merged_parameters:
1327
+ target_name = candidate
1328
+ break
1329
+ counter += 1
1330
+ rename_map[param_name] = target_name
1331
+ merged_parameters[target_name] = param_value
1332
+
1333
+ if rename_map:
1334
+ right_expr = builder._update_placeholders_in_expression(right_expr, rename_map)
1335
+
1336
+ combined: exp.Expression
1337
+ if operator == "union":
1338
+ combined = exp.union(left_expr, right_expr, distinct=distinct)
1339
+ elif operator == "intersect":
1340
+ combined = exp.intersect(left_expr, right_expr, distinct=distinct)
1341
+ elif operator == "except":
1342
+ combined = exp.except_(left_expr, right_expr)
1343
+ else: # pragma: no cover - defensive
1344
+ msg = f"Unsupported set operation: {operator}"
1345
+ raise SQLBuilderError(msg)
1346
+
1347
+ new_builder = builder._spawn_like_self()
1348
+ new_builder.set_expression(combined)
1349
+ new_builder.set_parameters(merged_parameters)
1350
+ return cast("Self", new_builder)
1351
+
1352
+
1353
+ TABLE_HINT_PATTERN: Final[str] = r"\b{}\b(\s+AS\s+\w+)?"
1354
+
1355
+
1356
+ def _parse_hint_expression(hint: Any, dialect: "DialectType | str | None") -> exp.Expression:
1357
+ try:
1358
+ hint_str = str(hint)
1359
+ hint_expr: exp.Expression | None = exp.maybe_parse(hint_str, dialect=dialect)
1360
+ return hint_expr or exp.Anonymous(this=hint_str)
1361
+ except Exception:
1362
+ return exp.Anonymous(this=str(hint))
1363
+
1364
+
1365
+ class _TableHintReplacer:
1366
+ __slots__ = ("_hint", "_table")
1367
+
1368
+ def __init__(self, hint: str, table: str) -> None:
1369
+ self._hint = hint
1370
+ self._table = table
1371
+
1372
+ def __call__(self, match: "re.Match[str]") -> str:
1373
+ alias_part = match.group(1) or ""
1374
+ return f"/*+ {self._hint} */ {self._table}{alias_part}"
1375
+
1376
+
1377
+ class Select(
1378
+ QueryBuilder,
1379
+ WhereClauseMixin,
1380
+ OrderByClauseMixin,
1381
+ LimitOffsetClauseMixin,
1382
+ SelectClauseMixin,
1383
+ JoinClauseMixin,
1384
+ HavingClauseMixin,
1385
+ SetOperationMixin,
1386
+ CommonTableExpressionMixin,
1387
+ PivotClauseMixin,
1388
+ UnpivotClauseMixin,
1389
+ ExplainMixin,
1390
+ ):
1391
+ """Builder for SELECT queries.
1392
+
1393
+ Provides a fluent interface for constructing SQL SELECT statements
1394
+ with parameter binding and validation.
1395
+
1396
+ Example:
1397
+ >>> class User(BaseModel):
1398
+ ... id: int
1399
+ ... name: str
1400
+ >>> builder = Select("id", "name").from_("users")
1401
+ >>> result = driver.execute(builder)
1402
+ """
1403
+
1404
+ __slots__ = ("_hints",)
1405
+ _expression: exp.Expression | None
1406
+
1407
+ def __init__(self, *columns: str, **kwargs: Any) -> None:
1408
+ """Initialize SELECT with optional columns.
1409
+
1410
+ Args:
1411
+ *columns: Column names to select (e.g., "id", "name", "u.email")
1412
+ **kwargs: Additional QueryBuilder arguments (dialect, schema, etc.)
1413
+
1414
+ Examples:
1415
+ Select("id", "name") # Shorthand for Select().select("id", "name")
1416
+ Select() # Same as Select() - start empty
1417
+ """
1418
+ (dialect, schema, enable_optimization, optimize_joins, optimize_predicates, simplify_expressions) = (
1419
+ self._parse_query_builder_kwargs(kwargs)
1420
+ )
1421
+ super().__init__(
1422
+ dialect=dialect,
1423
+ schema=schema,
1424
+ enable_optimization=enable_optimization,
1425
+ optimize_joins=optimize_joins,
1426
+ optimize_predicates=optimize_predicates,
1427
+ simplify_expressions=simplify_expressions,
1428
+ )
1429
+
1430
+ self._hints: list[dict[str, object]] = []
1431
+
1432
+ self._initialize_expression()
1433
+
1434
+ if columns:
1435
+ self.select(*columns)
1436
+
1437
+ @property
1438
+ def _expected_result_type(self) -> "type[SQLResult]":
1439
+ """Get the expected result type for SELECT operations.
1440
+
1441
+ Returns:
1442
+ type: The SelectResult type.
1443
+ """
1444
+ return SQLResult
1445
+
1446
+ def _create_base_expression(self) -> exp.Select:
1447
+ """Create base SELECT expression."""
1448
+ if self._expression is None or not isinstance(self._expression, exp.Select):
1449
+ self._expression = exp.Select()
1450
+ return self._expression
1451
+
1452
+ def with_hint(
1453
+ self, hint: "str", *, location: "str" = "statement", table: "str | None" = None, dialect: "str | None" = None
1454
+ ) -> "Self":
1455
+ """Attach an optimizer or dialect-specific hint to the query.
1456
+
1457
+ Args:
1458
+ hint: The raw hint string (e.g., 'INDEX(users idx_users_name)').
1459
+ location: Where to apply the hint ('statement', 'table').
1460
+ table: Table name if the hint is for a specific table.
1461
+ dialect: Restrict the hint to a specific dialect (optional).
1462
+
1463
+ Returns:
1464
+ The current builder instance for method chaining.
1465
+ """
1466
+ self._hints.append({"hint": hint, "location": location, "table": table, "dialect": dialect})
1467
+ return self
1468
+
1469
+ def build(self, dialect: "DialectType" = None) -> "BuiltQuery":
1470
+ """Builds the SQL query string and parameters with hint injection.
1471
+
1472
+ Args:
1473
+ dialect: Optional dialect override for SQL generation.
1474
+
1475
+ Returns:
1476
+ BuiltQuery: A dataclass containing the SQL string and parameters.
1477
+ """
1478
+ safe_query = super().build(dialect=dialect)
1479
+
1480
+ if not self._hints:
1481
+ return safe_query
1482
+
1483
+ target_dialect = str(dialect) if dialect else self.dialect_name
1484
+
1485
+ modified_expr = self._expression or self._create_base_expression()
1486
+
1487
+ if isinstance(modified_expr, exp.Select):
1488
+ statement_hints = [h["hint"] for h in self._hints if h.get("location") == "statement"]
1489
+ if statement_hints:
1490
+ hint_expressions: list[exp.Expression] = [
1491
+ _parse_hint_expression(hint, target_dialect) for hint in statement_hints
1492
+ ]
1493
+
1494
+ if hint_expressions:
1495
+ modified_expr.set("hint", exp.Hint(expressions=hint_expressions))
1496
+
1497
+ modified_sql = modified_expr.sql(dialect=target_dialect, pretty=True)
1498
+
1499
+ for hint_dict in self._hints:
1500
+ if hint_dict.get("location") == "table" and hint_dict.get("table"):
1501
+ table = str(hint_dict["table"])
1502
+ hint = str(hint_dict["hint"])
1503
+ pattern = TABLE_HINT_PATTERN.format(re.escape(table))
1504
+
1505
+ modified_sql = re.sub(
1506
+ pattern, _TableHintReplacer(hint, table), modified_sql, count=1, flags=re.IGNORECASE
1507
+ )
1508
+
1509
+ return BuiltQuery(sql=modified_sql, parameters=safe_query.parameters, dialect=safe_query.dialect)
1510
+
1511
+ def _validate_select_expression(self) -> None:
1512
+ """Validate that current expression is a valid SELECT statement.
1513
+
1514
+ Raises:
1515
+ SQLBuilderError: If expression is None or not a SELECT statement
1516
+ """
1517
+ if self._expression is None or not isinstance(self._expression, exp.Select):
1518
+ msg = "Locking clauses can only be applied to SELECT statements"
1519
+ raise SQLBuilderError(msg)
1520
+
1521
+ def _validate_lock_parameters(self, skip_locked: bool, nowait: bool) -> None:
1522
+ """Validate locking parameters for conflicting options.
1523
+
1524
+ Args:
1525
+ skip_locked: Whether SKIP LOCKED option is enabled
1526
+ nowait: Whether NOWAIT option is enabled
1527
+
1528
+ Raises:
1529
+ SQLBuilderError: If both skip_locked and nowait are True
1530
+ """
1531
+ if skip_locked and nowait:
1532
+ msg = "Cannot use both skip_locked and nowait"
1533
+ raise SQLBuilderError(msg)
1534
+
1535
+ def for_update(
1536
+ self, *, skip_locked: bool = False, nowait: bool = False, of: "str | list[str] | None" = None
1537
+ ) -> "Self":
1538
+ """Add FOR UPDATE clause to SELECT statement for row-level locking.
1539
+
1540
+ Args:
1541
+ skip_locked: Skip rows that are already locked (SKIP LOCKED)
1542
+ nowait: Return immediately if row is locked (NOWAIT)
1543
+ of: Table names/aliases to lock (FOR UPDATE OF table)
1544
+
1545
+ Returns:
1546
+ Self for method chaining
1547
+ """
1548
+ self._validate_select_expression()
1549
+ self._validate_lock_parameters(skip_locked, nowait)
1550
+
1551
+ assert self._expression is not None
1552
+ select_expr = cast("exp.Select", self._expression)
1553
+
1554
+ lock_args: dict[str, Any] = {"update": True}
1555
+
1556
+ if skip_locked:
1557
+ lock_args["wait"] = False
1558
+ elif nowait:
1559
+ lock_args["wait"] = True
1560
+
1561
+ if of:
1562
+ tables = [of] if isinstance(of, str) else of
1563
+ lock_args["expressions"] = [exp.to_identifier(str(t), quoted=is_explicitly_quoted(t)) for t in tables]
1564
+ self._lock_targets_quoted = any(is_explicitly_quoted(t) for t in tables)
1565
+ else:
1566
+ self._lock_targets_quoted = False
1567
+
1568
+ lock = exp.Lock(**lock_args)
1569
+
1570
+ current_locks = select_expr.args.get("locks", [])
1571
+ current_locks.append(lock)
1572
+ select_expr.set("locks", current_locks)
1573
+
1574
+ return self
1575
+
1576
+ def for_share(
1577
+ self, *, skip_locked: bool = False, nowait: bool = False, of: "str | list[str] | None" = None
1578
+ ) -> "Self":
1579
+ """Add FOR SHARE clause for shared row-level locking.
1580
+
1581
+ Args:
1582
+ skip_locked: Skip rows that are already locked (SKIP LOCKED)
1583
+ nowait: Return immediately if row is locked (NOWAIT)
1584
+ of: Table names/aliases to lock (FOR SHARE OF table)
1585
+
1586
+ Returns:
1587
+ Self for method chaining
1588
+ """
1589
+ self._validate_select_expression()
1590
+ self._validate_lock_parameters(skip_locked, nowait)
1591
+
1592
+ assert self._expression is not None
1593
+ select_expr = cast("exp.Select", self._expression)
1594
+
1595
+ lock_args: dict[str, Any] = {"update": False}
1596
+
1597
+ if skip_locked:
1598
+ lock_args["wait"] = False
1599
+ elif nowait:
1600
+ lock_args["wait"] = True
1601
+
1602
+ if of:
1603
+ tables = [of] if isinstance(of, str) else of
1604
+ lock_args["expressions"] = [exp.to_identifier(str(t), quoted=is_explicitly_quoted(t)) for t in tables]
1605
+ self._lock_targets_quoted = any(is_explicitly_quoted(t) for t in tables)
1606
+ else:
1607
+ self._lock_targets_quoted = False
1608
+
1609
+ lock = exp.Lock(**lock_args)
1610
+
1611
+ current_locks = select_expr.args.get("locks", [])
1612
+ current_locks.append(lock)
1613
+ select_expr.set("locks", current_locks)
1614
+
1615
+ return self
1616
+
1617
+ def for_key_share(self) -> "Self":
1618
+ """Add FOR KEY SHARE clause (PostgreSQL-specific).
1619
+
1620
+ FOR KEY SHARE is like FOR SHARE, but the lock is weaker:
1621
+ SELECT FOR UPDATE is blocked, but not SELECT FOR NO KEY UPDATE.
1622
+
1623
+ Returns:
1624
+ Self for method chaining
1625
+ """
1626
+ self._validate_select_expression()
1627
+
1628
+ assert self._expression is not None
1629
+ select_expr = cast("exp.Select", self._expression)
1630
+
1631
+ lock = exp.Lock(update=False, key=True)
1632
+
1633
+ current_locks = select_expr.args.get("locks", [])
1634
+ current_locks.append(lock)
1635
+ select_expr.set("locks", current_locks)
1636
+
1637
+ return self
1638
+
1639
+ def for_no_key_update(self) -> "Self":
1640
+ """Add FOR NO KEY UPDATE clause (PostgreSQL-specific).
1641
+
1642
+ FOR NO KEY UPDATE is like FOR UPDATE, but the lock is weaker:
1643
+ it does not block SELECT FOR KEY SHARE commands that attempt to
1644
+ acquire a share lock on the same rows.
1645
+
1646
+ Returns:
1647
+ Self for method chaining
1648
+ """
1649
+ self._validate_select_expression()
1650
+
1651
+ assert self._expression is not None
1652
+ select_expr = cast("exp.Select", self._expression)
1653
+
1654
+ lock = exp.Lock(update=True, key=False)
1655
+
1656
+ current_locks = select_expr.args.get("locks", [])
1657
+ current_locks.append(lock)
1658
+ select_expr.set("locks", current_locks)
1659
+
1660
+ return self