sqlspec 0.47.0__cp314-cp314-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (621) hide show
  1. f68e0789eb443ecb1c2c__mypyc.cp314-win_amd64.pyd +0 -0
  2. sqlspec/__init__.py +167 -0
  3. sqlspec/__main__.py +12 -0
  4. sqlspec/__metadata__.py +14 -0
  5. sqlspec/_typing.py +714 -0
  6. sqlspec/adapters/__init__.py +0 -0
  7. sqlspec/adapters/adbc/__init__.py +13 -0
  8. sqlspec/adapters/adbc/_typing.py +106 -0
  9. sqlspec/adapters/adbc/adk/__init__.py +5 -0
  10. sqlspec/adapters/adbc/adk/store.py +1280 -0
  11. sqlspec/adapters/adbc/config.py +378 -0
  12. sqlspec/adapters/adbc/core.cp314-win_amd64.pyd +0 -0
  13. sqlspec/adapters/adbc/core.py +922 -0
  14. sqlspec/adapters/adbc/data_dictionary.py +339 -0
  15. sqlspec/adapters/adbc/driver.py +534 -0
  16. sqlspec/adapters/adbc/events/__init__.py +5 -0
  17. sqlspec/adapters/adbc/events/store.py +285 -0
  18. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  19. sqlspec/adapters/adbc/litestar/store.py +534 -0
  20. sqlspec/adapters/adbc/type_converter.cp314-win_amd64.pyd +0 -0
  21. sqlspec/adapters/adbc/type_converter.py +142 -0
  22. sqlspec/adapters/aiomysql/__init__.py +21 -0
  23. sqlspec/adapters/aiomysql/_typing.py +137 -0
  24. sqlspec/adapters/aiomysql/adk/__init__.py +5 -0
  25. sqlspec/adapters/aiomysql/adk/store.py +678 -0
  26. sqlspec/adapters/aiomysql/config.py +305 -0
  27. sqlspec/adapters/aiomysql/core.cp314-win_amd64.pyd +0 -0
  28. sqlspec/adapters/aiomysql/core.py +536 -0
  29. sqlspec/adapters/aiomysql/data_dictionary.py +121 -0
  30. sqlspec/adapters/aiomysql/driver.py +386 -0
  31. sqlspec/adapters/aiomysql/events/__init__.py +5 -0
  32. sqlspec/adapters/aiomysql/events/store.py +104 -0
  33. sqlspec/adapters/aiomysql/litestar/__init__.py +5 -0
  34. sqlspec/adapters/aiomysql/litestar/store.py +314 -0
  35. sqlspec/adapters/aiosqlite/__init__.py +26 -0
  36. sqlspec/adapters/aiosqlite/_typing.py +109 -0
  37. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  38. sqlspec/adapters/aiosqlite/adk/store.py +829 -0
  39. sqlspec/adapters/aiosqlite/config.py +315 -0
  40. sqlspec/adapters/aiosqlite/core.cp314-win_amd64.pyd +0 -0
  41. sqlspec/adapters/aiosqlite/core.py +315 -0
  42. sqlspec/adapters/aiosqlite/data_dictionary.py +202 -0
  43. sqlspec/adapters/aiosqlite/driver.py +311 -0
  44. sqlspec/adapters/aiosqlite/events/__init__.py +5 -0
  45. sqlspec/adapters/aiosqlite/events/store.py +20 -0
  46. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  47. sqlspec/adapters/aiosqlite/litestar/store.py +279 -0
  48. sqlspec/adapters/aiosqlite/pool.cp314-win_amd64.pyd +0 -0
  49. sqlspec/adapters/aiosqlite/pool.py +734 -0
  50. sqlspec/adapters/asyncmy/__init__.py +21 -0
  51. sqlspec/adapters/asyncmy/_typing.py +113 -0
  52. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  53. sqlspec/adapters/asyncmy/adk/store.py +644 -0
  54. sqlspec/adapters/asyncmy/config.py +307 -0
  55. sqlspec/adapters/asyncmy/core.cp314-win_amd64.pyd +0 -0
  56. sqlspec/adapters/asyncmy/core.py +538 -0
  57. sqlspec/adapters/asyncmy/data_dictionary.py +122 -0
  58. sqlspec/adapters/asyncmy/driver.py +391 -0
  59. sqlspec/adapters/asyncmy/events/__init__.py +5 -0
  60. sqlspec/adapters/asyncmy/events/store.py +104 -0
  61. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  62. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  63. sqlspec/adapters/asyncpg/__init__.py +26 -0
  64. sqlspec/adapters/asyncpg/_typing.py +103 -0
  65. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  66. sqlspec/adapters/asyncpg/adk/store.py +483 -0
  67. sqlspec/adapters/asyncpg/config.py +575 -0
  68. sqlspec/adapters/asyncpg/core.cp314-win_amd64.pyd +0 -0
  69. sqlspec/adapters/asyncpg/core.py +480 -0
  70. sqlspec/adapters/asyncpg/data_dictionary.py +157 -0
  71. sqlspec/adapters/asyncpg/driver.py +487 -0
  72. sqlspec/adapters/asyncpg/events/__init__.py +6 -0
  73. sqlspec/adapters/asyncpg/events/_hub.py +181 -0
  74. sqlspec/adapters/asyncpg/events/backend.py +210 -0
  75. sqlspec/adapters/asyncpg/events/store.py +40 -0
  76. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  77. sqlspec/adapters/asyncpg/litestar/store.py +251 -0
  78. sqlspec/adapters/bigquery/__init__.py +15 -0
  79. sqlspec/adapters/bigquery/_typing.py +108 -0
  80. sqlspec/adapters/bigquery/config.py +362 -0
  81. sqlspec/adapters/bigquery/core.cp314-win_amd64.pyd +0 -0
  82. sqlspec/adapters/bigquery/core.py +768 -0
  83. sqlspec/adapters/bigquery/data_dictionary.py +120 -0
  84. sqlspec/adapters/bigquery/driver.py +542 -0
  85. sqlspec/adapters/bigquery/events/__init__.py +5 -0
  86. sqlspec/adapters/bigquery/events/store.py +139 -0
  87. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  88. sqlspec/adapters/bigquery/litestar/store.py +325 -0
  89. sqlspec/adapters/bigquery/type_converter.cp314-win_amd64.pyd +0 -0
  90. sqlspec/adapters/bigquery/type_converter.py +107 -0
  91. sqlspec/adapters/cockroach_asyncpg/__init__.py +26 -0
  92. sqlspec/adapters/cockroach_asyncpg/_typing.py +73 -0
  93. sqlspec/adapters/cockroach_asyncpg/adk/__init__.py +3 -0
  94. sqlspec/adapters/cockroach_asyncpg/adk/store.py +465 -0
  95. sqlspec/adapters/cockroach_asyncpg/config.py +248 -0
  96. sqlspec/adapters/cockroach_asyncpg/core.cp314-win_amd64.pyd +0 -0
  97. sqlspec/adapters/cockroach_asyncpg/core.py +55 -0
  98. sqlspec/adapters/cockroach_asyncpg/data_dictionary.py +110 -0
  99. sqlspec/adapters/cockroach_asyncpg/driver.py +142 -0
  100. sqlspec/adapters/cockroach_asyncpg/events/__init__.py +3 -0
  101. sqlspec/adapters/cockroach_asyncpg/events/store.py +20 -0
  102. sqlspec/adapters/cockroach_asyncpg/litestar/__init__.py +3 -0
  103. sqlspec/adapters/cockroach_asyncpg/litestar/store.py +142 -0
  104. sqlspec/adapters/cockroach_psycopg/__init__.py +39 -0
  105. sqlspec/adapters/cockroach_psycopg/_typing.py +137 -0
  106. sqlspec/adapters/cockroach_psycopg/adk/__init__.py +13 -0
  107. sqlspec/adapters/cockroach_psycopg/adk/store.py +1039 -0
  108. sqlspec/adapters/cockroach_psycopg/config.py +511 -0
  109. sqlspec/adapters/cockroach_psycopg/core.cp314-win_amd64.pyd +0 -0
  110. sqlspec/adapters/cockroach_psycopg/core.py +63 -0
  111. sqlspec/adapters/cockroach_psycopg/data_dictionary.py +220 -0
  112. sqlspec/adapters/cockroach_psycopg/driver.py +273 -0
  113. sqlspec/adapters/cockroach_psycopg/events/__init__.py +6 -0
  114. sqlspec/adapters/cockroach_psycopg/events/store.py +34 -0
  115. sqlspec/adapters/cockroach_psycopg/litestar/__init__.py +3 -0
  116. sqlspec/adapters/cockroach_psycopg/litestar/store.py +327 -0
  117. sqlspec/adapters/duckdb/__init__.py +29 -0
  118. sqlspec/adapters/duckdb/_typing.py +104 -0
  119. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  120. sqlspec/adapters/duckdb/adk/store.py +935 -0
  121. sqlspec/adapters/duckdb/config.py +386 -0
  122. sqlspec/adapters/duckdb/core.cp314-win_amd64.pyd +0 -0
  123. sqlspec/adapters/duckdb/core.py +332 -0
  124. sqlspec/adapters/duckdb/data_dictionary.py +140 -0
  125. sqlspec/adapters/duckdb/driver.py +426 -0
  126. sqlspec/adapters/duckdb/events/__init__.py +5 -0
  127. sqlspec/adapters/duckdb/events/store.py +57 -0
  128. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  129. sqlspec/adapters/duckdb/litestar/store.py +330 -0
  130. sqlspec/adapters/duckdb/pool.cp314-win_amd64.pyd +0 -0
  131. sqlspec/adapters/duckdb/pool.py +350 -0
  132. sqlspec/adapters/duckdb/type_converter.cp314-win_amd64.pyd +0 -0
  133. sqlspec/adapters/duckdb/type_converter.py +118 -0
  134. sqlspec/adapters/mysqlconnector/__init__.py +39 -0
  135. sqlspec/adapters/mysqlconnector/_typing.py +186 -0
  136. sqlspec/adapters/mysqlconnector/adk/__init__.py +15 -0
  137. sqlspec/adapters/mysqlconnector/adk/store.py +1183 -0
  138. sqlspec/adapters/mysqlconnector/config.py +421 -0
  139. sqlspec/adapters/mysqlconnector/core.cp314-win_amd64.pyd +0 -0
  140. sqlspec/adapters/mysqlconnector/core.py +472 -0
  141. sqlspec/adapters/mysqlconnector/data_dictionary.py +230 -0
  142. sqlspec/adapters/mysqlconnector/driver.py +516 -0
  143. sqlspec/adapters/mysqlconnector/events/__init__.py +8 -0
  144. sqlspec/adapters/mysqlconnector/events/store.py +98 -0
  145. sqlspec/adapters/mysqlconnector/litestar/__init__.py +5 -0
  146. sqlspec/adapters/mysqlconnector/litestar/store.py +426 -0
  147. sqlspec/adapters/oracledb/__init__.py +39 -0
  148. sqlspec/adapters/oracledb/_json_handlers.cp314-win_amd64.pyd +0 -0
  149. sqlspec/adapters/oracledb/_json_handlers.py +196 -0
  150. sqlspec/adapters/oracledb/_param_types.cp314-win_amd64.pyd +0 -0
  151. sqlspec/adapters/oracledb/_param_types.py +46 -0
  152. sqlspec/adapters/oracledb/_typing.py +258 -0
  153. sqlspec/adapters/oracledb/_uuid_handlers.cp314-win_amd64.pyd +0 -0
  154. sqlspec/adapters/oracledb/_uuid_handlers.py +163 -0
  155. sqlspec/adapters/oracledb/_vector_handlers.cp314-win_amd64.pyd +0 -0
  156. sqlspec/adapters/oracledb/_vector_handlers.py +228 -0
  157. sqlspec/adapters/oracledb/adk/__init__.py +21 -0
  158. sqlspec/adapters/oracledb/adk/store.py +2453 -0
  159. sqlspec/adapters/oracledb/config.py +575 -0
  160. sqlspec/adapters/oracledb/core.cp314-win_amd64.pyd +0 -0
  161. sqlspec/adapters/oracledb/core.py +820 -0
  162. sqlspec/adapters/oracledb/data_dictionary.py +404 -0
  163. sqlspec/adapters/oracledb/driver.py +1277 -0
  164. sqlspec/adapters/oracledb/events/__init__.py +16 -0
  165. sqlspec/adapters/oracledb/events/_hub.py +345 -0
  166. sqlspec/adapters/oracledb/events/backend.py +300 -0
  167. sqlspec/adapters/oracledb/events/store.py +420 -0
  168. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  169. sqlspec/adapters/oracledb/litestar/store.py +781 -0
  170. sqlspec/adapters/oracledb/migrations.py +539 -0
  171. sqlspec/adapters/oracledb/type_converter.cp314-win_amd64.pyd +0 -0
  172. sqlspec/adapters/oracledb/type_converter.py +211 -0
  173. sqlspec/adapters/psqlpy/__init__.py +18 -0
  174. sqlspec/adapters/psqlpy/_typing.py +121 -0
  175. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  176. sqlspec/adapters/psqlpy/adk/store.py +591 -0
  177. sqlspec/adapters/psqlpy/config.py +376 -0
  178. sqlspec/adapters/psqlpy/core.cp314-win_amd64.pyd +0 -0
  179. sqlspec/adapters/psqlpy/core.py +694 -0
  180. sqlspec/adapters/psqlpy/data_dictionary.py +121 -0
  181. sqlspec/adapters/psqlpy/driver.py +411 -0
  182. sqlspec/adapters/psqlpy/events/__init__.py +6 -0
  183. sqlspec/adapters/psqlpy/events/_hub.py +204 -0
  184. sqlspec/adapters/psqlpy/events/backend.py +210 -0
  185. sqlspec/adapters/psqlpy/events/store.py +20 -0
  186. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  187. sqlspec/adapters/psqlpy/litestar/store.py +270 -0
  188. sqlspec/adapters/psqlpy/type_converter.cp314-win_amd64.pyd +0 -0
  189. sqlspec/adapters/psqlpy/type_converter.py +113 -0
  190. sqlspec/adapters/psycopg/__init__.py +38 -0
  191. sqlspec/adapters/psycopg/_typing.py +218 -0
  192. sqlspec/adapters/psycopg/adk/__init__.py +10 -0
  193. sqlspec/adapters/psycopg/adk/store.py +1106 -0
  194. sqlspec/adapters/psycopg/config.py +695 -0
  195. sqlspec/adapters/psycopg/core.cp314-win_amd64.pyd +0 -0
  196. sqlspec/adapters/psycopg/core.py +520 -0
  197. sqlspec/adapters/psycopg/data_dictionary.py +278 -0
  198. sqlspec/adapters/psycopg/driver.py +1033 -0
  199. sqlspec/adapters/psycopg/events/__init__.py +20 -0
  200. sqlspec/adapters/psycopg/events/_hub.py +388 -0
  201. sqlspec/adapters/psycopg/events/backend.py +398 -0
  202. sqlspec/adapters/psycopg/events/store.py +42 -0
  203. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  204. sqlspec/adapters/psycopg/litestar/store.py +554 -0
  205. sqlspec/adapters/psycopg/type_converter.cp314-win_amd64.pyd +0 -0
  206. sqlspec/adapters/psycopg/type_converter.py +93 -0
  207. sqlspec/adapters/pymysql/__init__.py +21 -0
  208. sqlspec/adapters/pymysql/_typing.py +92 -0
  209. sqlspec/adapters/pymysql/adk/__init__.py +5 -0
  210. sqlspec/adapters/pymysql/adk/store.py +657 -0
  211. sqlspec/adapters/pymysql/config.py +176 -0
  212. sqlspec/adapters/pymysql/core.cp314-win_amd64.pyd +0 -0
  213. sqlspec/adapters/pymysql/core.py +469 -0
  214. sqlspec/adapters/pymysql/data_dictionary.py +120 -0
  215. sqlspec/adapters/pymysql/driver.py +271 -0
  216. sqlspec/adapters/pymysql/events/__init__.py +5 -0
  217. sqlspec/adapters/pymysql/events/store.py +50 -0
  218. sqlspec/adapters/pymysql/litestar/__init__.py +5 -0
  219. sqlspec/adapters/pymysql/litestar/store.py +232 -0
  220. sqlspec/adapters/pymysql/pool.cp314-win_amd64.pyd +0 -0
  221. sqlspec/adapters/pymysql/pool.py +184 -0
  222. sqlspec/adapters/spanner/__init__.py +33 -0
  223. sqlspec/adapters/spanner/_typing.py +102 -0
  224. sqlspec/adapters/spanner/adk/__init__.py +5 -0
  225. sqlspec/adapters/spanner/adk/store.py +758 -0
  226. sqlspec/adapters/spanner/config.py +355 -0
  227. sqlspec/adapters/spanner/core.cp314-win_amd64.pyd +0 -0
  228. sqlspec/adapters/spanner/core.py +263 -0
  229. sqlspec/adapters/spanner/data_dictionary.py +120 -0
  230. sqlspec/adapters/spanner/driver.py +407 -0
  231. sqlspec/adapters/spanner/events/__init__.py +5 -0
  232. sqlspec/adapters/spanner/events/store.py +187 -0
  233. sqlspec/adapters/spanner/litestar/__init__.py +5 -0
  234. sqlspec/adapters/spanner/litestar/store.py +291 -0
  235. sqlspec/adapters/spanner/type_converter.cp314-win_amd64.pyd +0 -0
  236. sqlspec/adapters/spanner/type_converter.py +342 -0
  237. sqlspec/adapters/sqlite/__init__.py +19 -0
  238. sqlspec/adapters/sqlite/_typing.py +123 -0
  239. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  240. sqlspec/adapters/sqlite/adk/store.py +992 -0
  241. sqlspec/adapters/sqlite/config.py +240 -0
  242. sqlspec/adapters/sqlite/core.cp314-win_amd64.pyd +0 -0
  243. sqlspec/adapters/sqlite/core.py +357 -0
  244. sqlspec/adapters/sqlite/data_dictionary.py +198 -0
  245. sqlspec/adapters/sqlite/driver.py +527 -0
  246. sqlspec/adapters/sqlite/events/__init__.py +5 -0
  247. sqlspec/adapters/sqlite/events/store.py +20 -0
  248. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  249. sqlspec/adapters/sqlite/litestar/store.py +316 -0
  250. sqlspec/adapters/sqlite/pool.cp314-win_amd64.pyd +0 -0
  251. sqlspec/adapters/sqlite/pool.py +237 -0
  252. sqlspec/adapters/sqlite/type_converter.cp314-win_amd64.pyd +0 -0
  253. sqlspec/adapters/sqlite/type_converter.py +114 -0
  254. sqlspec/base.py +832 -0
  255. sqlspec/builder/__init__.py +181 -0
  256. sqlspec/builder/_base.cp314-win_amd64.pyd +0 -0
  257. sqlspec/builder/_base.py +1071 -0
  258. sqlspec/builder/_column.cp314-win_amd64.pyd +0 -0
  259. sqlspec/builder/_column.py +521 -0
  260. sqlspec/builder/_ddl.cp314-win_amd64.pyd +0 -0
  261. sqlspec/builder/_ddl.py +1691 -0
  262. sqlspec/builder/_delete.cp314-win_amd64.pyd +0 -0
  263. sqlspec/builder/_delete.py +95 -0
  264. sqlspec/builder/_dml.cp314-win_amd64.pyd +0 -0
  265. sqlspec/builder/_dml.py +386 -0
  266. sqlspec/builder/_explain.cp314-win_amd64.pyd +0 -0
  267. sqlspec/builder/_explain.py +579 -0
  268. sqlspec/builder/_expression_wrappers.cp314-win_amd64.pyd +0 -0
  269. sqlspec/builder/_expression_wrappers.py +46 -0
  270. sqlspec/builder/_factory.cp314-win_amd64.pyd +0 -0
  271. sqlspec/builder/_factory.py +1884 -0
  272. sqlspec/builder/_insert.cp314-win_amd64.pyd +0 -0
  273. sqlspec/builder/_insert.py +405 -0
  274. sqlspec/builder/_join.cp314-win_amd64.pyd +0 -0
  275. sqlspec/builder/_join.py +489 -0
  276. sqlspec/builder/_merge.cp314-win_amd64.pyd +0 -0
  277. sqlspec/builder/_merge.py +823 -0
  278. sqlspec/builder/_parsing_utils.cp314-win_amd64.pyd +0 -0
  279. sqlspec/builder/_parsing_utils.py +295 -0
  280. sqlspec/builder/_select.cp314-win_amd64.pyd +0 -0
  281. sqlspec/builder/_select.py +1666 -0
  282. sqlspec/builder/_temporal.cp314-win_amd64.pyd +0 -0
  283. sqlspec/builder/_temporal.py +167 -0
  284. sqlspec/builder/_update.cp314-win_amd64.pyd +0 -0
  285. sqlspec/builder/_update.py +173 -0
  286. sqlspec/builder/_vector_distance.cp314-win_amd64.pyd +0 -0
  287. sqlspec/builder/_vector_distance.py +330 -0
  288. sqlspec/cli.py +1095 -0
  289. sqlspec/config.py +2383 -0
  290. sqlspec/core/__init__.py +372 -0
  291. sqlspec/core/_correlation.cp314-win_amd64.pyd +0 -0
  292. sqlspec/core/_correlation.py +176 -0
  293. sqlspec/core/_pagination.py +42 -0
  294. sqlspec/core/_pool.cp314-win_amd64.pyd +0 -0
  295. sqlspec/core/_pool.py +76 -0
  296. sqlspec/core/cache.cp314-win_amd64.pyd +0 -0
  297. sqlspec/core/cache.py +1085 -0
  298. sqlspec/core/compiler.cp314-win_amd64.pyd +0 -0
  299. sqlspec/core/compiler.py +1090 -0
  300. sqlspec/core/config_runtime.cp314-win_amd64.pyd +0 -0
  301. sqlspec/core/config_runtime.py +174 -0
  302. sqlspec/core/explain.cp314-win_amd64.pyd +0 -0
  303. sqlspec/core/explain.py +275 -0
  304. sqlspec/core/filters.cp314-win_amd64.pyd +0 -0
  305. sqlspec/core/filters.py +969 -0
  306. sqlspec/core/hashing.cp314-win_amd64.pyd +0 -0
  307. sqlspec/core/hashing.py +266 -0
  308. sqlspec/core/metrics.cp314-win_amd64.pyd +0 -0
  309. sqlspec/core/metrics.py +83 -0
  310. sqlspec/core/parameters/__init__.py +72 -0
  311. sqlspec/core/parameters/_alignment.cp314-win_amd64.pyd +0 -0
  312. sqlspec/core/parameters/_alignment.py +283 -0
  313. sqlspec/core/parameters/_converter.cp314-win_amd64.pyd +0 -0
  314. sqlspec/core/parameters/_converter.py +554 -0
  315. sqlspec/core/parameters/_processor.cp314-win_amd64.pyd +0 -0
  316. sqlspec/core/parameters/_processor.py +1182 -0
  317. sqlspec/core/parameters/_registry.cp314-win_amd64.pyd +0 -0
  318. sqlspec/core/parameters/_registry.py +206 -0
  319. sqlspec/core/parameters/_transformers.cp314-win_amd64.pyd +0 -0
  320. sqlspec/core/parameters/_transformers.py +324 -0
  321. sqlspec/core/parameters/_types.cp314-win_amd64.pyd +0 -0
  322. sqlspec/core/parameters/_types.py +536 -0
  323. sqlspec/core/parameters/_validator.cp314-win_amd64.pyd +0 -0
  324. sqlspec/core/parameters/_validator.py +171 -0
  325. sqlspec/core/pipeline.cp314-win_amd64.pyd +0 -0
  326. sqlspec/core/pipeline.py +333 -0
  327. sqlspec/core/query_modifiers.cp314-win_amd64.pyd +0 -0
  328. sqlspec/core/query_modifiers.py +508 -0
  329. sqlspec/core/result/__init__.py +25 -0
  330. sqlspec/core/result/_base.cp314-win_amd64.pyd +0 -0
  331. sqlspec/core/result/_base.py +1232 -0
  332. sqlspec/core/result/_io.cp314-win_amd64.pyd +0 -0
  333. sqlspec/core/result/_io.py +28 -0
  334. sqlspec/core/splitter.cp314-win_amd64.pyd +0 -0
  335. sqlspec/core/splitter.py +1021 -0
  336. sqlspec/core/sqlcommenter.cp314-win_amd64.pyd +0 -0
  337. sqlspec/core/sqlcommenter.py +249 -0
  338. sqlspec/core/stack.cp314-win_amd64.pyd +0 -0
  339. sqlspec/core/stack.py +163 -0
  340. sqlspec/core/statement.cp314-win_amd64.pyd +0 -0
  341. sqlspec/core/statement.py +1865 -0
  342. sqlspec/core/type_converter.cp314-win_amd64.pyd +0 -0
  343. sqlspec/core/type_converter.py +340 -0
  344. sqlspec/data_dictionary/__init__.py +22 -0
  345. sqlspec/data_dictionary/_loader.cp314-win_amd64.pyd +0 -0
  346. sqlspec/data_dictionary/_loader.py +138 -0
  347. sqlspec/data_dictionary/_registry.cp314-win_amd64.pyd +0 -0
  348. sqlspec/data_dictionary/_registry.py +74 -0
  349. sqlspec/data_dictionary/_types.cp314-win_amd64.pyd +0 -0
  350. sqlspec/data_dictionary/_types.py +121 -0
  351. sqlspec/data_dictionary/dialects/__init__.py +21 -0
  352. sqlspec/data_dictionary/dialects/bigquery.cp314-win_amd64.pyd +0 -0
  353. sqlspec/data_dictionary/dialects/bigquery.py +81 -0
  354. sqlspec/data_dictionary/dialects/cockroachdb.cp314-win_amd64.pyd +0 -0
  355. sqlspec/data_dictionary/dialects/cockroachdb.py +54 -0
  356. sqlspec/data_dictionary/dialects/duckdb.cp314-win_amd64.pyd +0 -0
  357. sqlspec/data_dictionary/dialects/duckdb.py +47 -0
  358. sqlspec/data_dictionary/dialects/mysql.cp314-win_amd64.pyd +0 -0
  359. sqlspec/data_dictionary/dialects/mysql.py +53 -0
  360. sqlspec/data_dictionary/dialects/oracle.cp314-win_amd64.pyd +0 -0
  361. sqlspec/data_dictionary/dialects/oracle.py +197 -0
  362. sqlspec/data_dictionary/dialects/postgres.cp314-win_amd64.pyd +0 -0
  363. sqlspec/data_dictionary/dialects/postgres.py +69 -0
  364. sqlspec/data_dictionary/dialects/spanner.cp314-win_amd64.pyd +0 -0
  365. sqlspec/data_dictionary/dialects/spanner.py +37 -0
  366. sqlspec/data_dictionary/dialects/sqlite.cp314-win_amd64.pyd +0 -0
  367. sqlspec/data_dictionary/dialects/sqlite.py +59 -0
  368. sqlspec/data_dictionary/sql/.gitkeep +0 -0
  369. sqlspec/data_dictionary/sql/bigquery/columns.sql +23 -0
  370. sqlspec/data_dictionary/sql/bigquery/foreign_keys.sql +34 -0
  371. sqlspec/data_dictionary/sql/bigquery/indexes.sql +19 -0
  372. sqlspec/data_dictionary/sql/bigquery/tables.sql +33 -0
  373. sqlspec/data_dictionary/sql/bigquery/version.sql +3 -0
  374. sqlspec/data_dictionary/sql/cockroachdb/columns.sql +34 -0
  375. sqlspec/data_dictionary/sql/cockroachdb/foreign_keys.sql +40 -0
  376. sqlspec/data_dictionary/sql/cockroachdb/indexes.sql +32 -0
  377. sqlspec/data_dictionary/sql/cockroachdb/tables.sql +44 -0
  378. sqlspec/data_dictionary/sql/cockroachdb/version.sql +3 -0
  379. sqlspec/data_dictionary/sql/duckdb/columns.sql +23 -0
  380. sqlspec/data_dictionary/sql/duckdb/foreign_keys.sql +36 -0
  381. sqlspec/data_dictionary/sql/duckdb/indexes.sql +19 -0
  382. sqlspec/data_dictionary/sql/duckdb/tables.sql +38 -0
  383. sqlspec/data_dictionary/sql/duckdb/version.sql +3 -0
  384. sqlspec/data_dictionary/sql/mysql/columns.sql +23 -0
  385. sqlspec/data_dictionary/sql/mysql/foreign_keys.sql +28 -0
  386. sqlspec/data_dictionary/sql/mysql/indexes.sql +26 -0
  387. sqlspec/data_dictionary/sql/mysql/tables.sql +33 -0
  388. sqlspec/data_dictionary/sql/mysql/version.sql +3 -0
  389. sqlspec/data_dictionary/sql/oracle/columns.sql +23 -0
  390. sqlspec/data_dictionary/sql/oracle/foreign_keys.sql +48 -0
  391. sqlspec/data_dictionary/sql/oracle/indexes.sql +44 -0
  392. sqlspec/data_dictionary/sql/oracle/tables.sql +25 -0
  393. sqlspec/data_dictionary/sql/oracle/version.sql +20 -0
  394. sqlspec/data_dictionary/sql/postgres/columns.sql +34 -0
  395. sqlspec/data_dictionary/sql/postgres/foreign_keys.sql +40 -0
  396. sqlspec/data_dictionary/sql/postgres/indexes.sql +56 -0
  397. sqlspec/data_dictionary/sql/postgres/tables.sql +44 -0
  398. sqlspec/data_dictionary/sql/postgres/version.sql +3 -0
  399. sqlspec/data_dictionary/sql/spanner/columns.sql +23 -0
  400. sqlspec/data_dictionary/sql/spanner/foreign_keys.sql +70 -0
  401. sqlspec/data_dictionary/sql/spanner/indexes.sql +30 -0
  402. sqlspec/data_dictionary/sql/spanner/tables.sql +9 -0
  403. sqlspec/data_dictionary/sql/spanner/version.sql +3 -0
  404. sqlspec/data_dictionary/sql/sqlite/columns.sql +23 -0
  405. sqlspec/data_dictionary/sql/sqlite/foreign_keys.sql +22 -0
  406. sqlspec/data_dictionary/sql/sqlite/indexes.sql +7 -0
  407. sqlspec/data_dictionary/sql/sqlite/tables.sql +28 -0
  408. sqlspec/data_dictionary/sql/sqlite/version.sql +3 -0
  409. sqlspec/dialects/__init__.py +22 -0
  410. sqlspec/dialects/_compat.cp314-win_amd64.pyd +0 -0
  411. sqlspec/dialects/_compat.py +14 -0
  412. sqlspec/dialects/postgres/__init__.py +9 -0
  413. sqlspec/dialects/postgres/_generators.cp314-win_amd64.pyd +0 -0
  414. sqlspec/dialects/postgres/_generators.py +57 -0
  415. sqlspec/dialects/postgres/_operators.cp314-win_amd64.pyd +0 -0
  416. sqlspec/dialects/postgres/_operators.py +81 -0
  417. sqlspec/dialects/postgres/_paradedb.py +50 -0
  418. sqlspec/dialects/postgres/_pgvector.py +36 -0
  419. sqlspec/dialects/spanner/__init__.py +6 -0
  420. sqlspec/dialects/spanner/_generators.cp314-win_amd64.pyd +0 -0
  421. sqlspec/dialects/spanner/_generators.py +206 -0
  422. sqlspec/dialects/spanner/_spangres.py +77 -0
  423. sqlspec/dialects/spanner/_spanner.py +179 -0
  424. sqlspec/driver/__init__.py +49 -0
  425. sqlspec/driver/_async.cp314-win_amd64.pyd +0 -0
  426. sqlspec/driver/_async.py +1830 -0
  427. sqlspec/driver/_common.cp314-win_amd64.pyd +0 -0
  428. sqlspec/driver/_common.py +2292 -0
  429. sqlspec/driver/_exception_handler.cp314-win_amd64.pyd +0 -0
  430. sqlspec/driver/_exception_handler.py +108 -0
  431. sqlspec/driver/_query_cache.cp314-win_amd64.pyd +0 -0
  432. sqlspec/driver/_query_cache.py +96 -0
  433. sqlspec/driver/_sql_helpers.cp314-win_amd64.pyd +0 -0
  434. sqlspec/driver/_sql_helpers.py +139 -0
  435. sqlspec/driver/_storage_helpers.cp314-win_amd64.pyd +0 -0
  436. sqlspec/driver/_storage_helpers.py +153 -0
  437. sqlspec/driver/_sync.cp314-win_amd64.pyd +0 -0
  438. sqlspec/driver/_sync.py +1817 -0
  439. sqlspec/exceptions.cp314-win_amd64.pyd +0 -0
  440. sqlspec/exceptions.py +480 -0
  441. sqlspec/extensions/__init__.py +0 -0
  442. sqlspec/extensions/adk/__init__.py +84 -0
  443. sqlspec/extensions/adk/_config_utils.py +199 -0
  444. sqlspec/extensions/adk/_types.cp314-win_amd64.pyd +0 -0
  445. sqlspec/extensions/adk/_types.py +41 -0
  446. sqlspec/extensions/adk/artifact/__init__.py +57 -0
  447. sqlspec/extensions/adk/artifact/_types.cp314-win_amd64.pyd +0 -0
  448. sqlspec/extensions/adk/artifact/_types.py +32 -0
  449. sqlspec/extensions/adk/artifact/service.py +508 -0
  450. sqlspec/extensions/adk/artifact/store.py +361 -0
  451. sqlspec/extensions/adk/converters.py +212 -0
  452. sqlspec/extensions/adk/memory/__init__.py +69 -0
  453. sqlspec/extensions/adk/memory/_types.cp314-win_amd64.pyd +0 -0
  454. sqlspec/extensions/adk/memory/_types.py +30 -0
  455. sqlspec/extensions/adk/memory/converters.py +225 -0
  456. sqlspec/extensions/adk/memory/service.py +316 -0
  457. sqlspec/extensions/adk/memory/store.py +525 -0
  458. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +184 -0
  459. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  460. sqlspec/extensions/adk/service.py +279 -0
  461. sqlspec/extensions/adk/store.py +590 -0
  462. sqlspec/extensions/events/__init__.py +51 -0
  463. sqlspec/extensions/events/_channel.py +703 -0
  464. sqlspec/extensions/events/_hints.cp314-win_amd64.pyd +0 -0
  465. sqlspec/extensions/events/_hints.py +45 -0
  466. sqlspec/extensions/events/_models.py +23 -0
  467. sqlspec/extensions/events/_payload.cp314-win_amd64.pyd +0 -0
  468. sqlspec/extensions/events/_payload.py +69 -0
  469. sqlspec/extensions/events/_protocols.py +134 -0
  470. sqlspec/extensions/events/_queue.py +462 -0
  471. sqlspec/extensions/events/_store.py +209 -0
  472. sqlspec/extensions/events/migrations/0001_create_event_queue.py +59 -0
  473. sqlspec/extensions/events/migrations/__init__.py +3 -0
  474. sqlspec/extensions/fastapi/__init__.py +22 -0
  475. sqlspec/extensions/fastapi/extension.py +391 -0
  476. sqlspec/extensions/fastapi/providers.cp314-win_amd64.pyd +0 -0
  477. sqlspec/extensions/fastapi/providers.py +712 -0
  478. sqlspec/extensions/flask/__init__.py +38 -0
  479. sqlspec/extensions/flask/_state.py +87 -0
  480. sqlspec/extensions/flask/_utils.py +71 -0
  481. sqlspec/extensions/flask/extension.py +539 -0
  482. sqlspec/extensions/litestar/__init__.py +31 -0
  483. sqlspec/extensions/litestar/_utils.py +52 -0
  484. sqlspec/extensions/litestar/channels.py +165 -0
  485. sqlspec/extensions/litestar/cli.py +102 -0
  486. sqlspec/extensions/litestar/config.py +90 -0
  487. sqlspec/extensions/litestar/handlers.py +316 -0
  488. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  489. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  490. sqlspec/extensions/litestar/plugin.py +1066 -0
  491. sqlspec/extensions/litestar/providers.cp314-win_amd64.pyd +0 -0
  492. sqlspec/extensions/litestar/providers.py +784 -0
  493. sqlspec/extensions/litestar/store.py +298 -0
  494. sqlspec/extensions/otel/__init__.py +58 -0
  495. sqlspec/extensions/prometheus/__init__.py +113 -0
  496. sqlspec/extensions/sanic/__init__.py +19 -0
  497. sqlspec/extensions/sanic/_state.py +43 -0
  498. sqlspec/extensions/sanic/_utils.py +127 -0
  499. sqlspec/extensions/sanic/extension.py +647 -0
  500. sqlspec/extensions/starlette/__init__.py +22 -0
  501. sqlspec/extensions/starlette/_state.py +42 -0
  502. sqlspec/extensions/starlette/_utils.py +96 -0
  503. sqlspec/extensions/starlette/extension.py +374 -0
  504. sqlspec/extensions/starlette/middleware.py +281 -0
  505. sqlspec/loader.cp314-win_amd64.pyd +0 -0
  506. sqlspec/loader.py +727 -0
  507. sqlspec/migrations/__init__.py +39 -0
  508. sqlspec/migrations/base.cp314-win_amd64.pyd +0 -0
  509. sqlspec/migrations/base.py +862 -0
  510. sqlspec/migrations/commands.py +2151 -0
  511. sqlspec/migrations/context.cp314-win_amd64.pyd +0 -0
  512. sqlspec/migrations/context.py +157 -0
  513. sqlspec/migrations/fix.cp314-win_amd64.pyd +0 -0
  514. sqlspec/migrations/fix.py +204 -0
  515. sqlspec/migrations/loaders.cp314-win_amd64.pyd +0 -0
  516. sqlspec/migrations/loaders.py +443 -0
  517. sqlspec/migrations/runner.cp314-win_amd64.pyd +0 -0
  518. sqlspec/migrations/runner.py +1195 -0
  519. sqlspec/migrations/squash.cp314-win_amd64.pyd +0 -0
  520. sqlspec/migrations/squash.py +490 -0
  521. sqlspec/migrations/templates.cp314-win_amd64.pyd +0 -0
  522. sqlspec/migrations/templates.py +234 -0
  523. sqlspec/migrations/tracker.cp314-win_amd64.pyd +0 -0
  524. sqlspec/migrations/tracker.py +792 -0
  525. sqlspec/migrations/utils.cp314-win_amd64.pyd +0 -0
  526. sqlspec/migrations/utils.py +256 -0
  527. sqlspec/migrations/validation.cp314-win_amd64.pyd +0 -0
  528. sqlspec/migrations/validation.py +359 -0
  529. sqlspec/migrations/version.cp314-win_amd64.pyd +0 -0
  530. sqlspec/migrations/version.py +446 -0
  531. sqlspec/observability/__init__.py +57 -0
  532. sqlspec/observability/_common.cp314-win_amd64.pyd +0 -0
  533. sqlspec/observability/_common.py +77 -0
  534. sqlspec/observability/_config.cp314-win_amd64.pyd +0 -0
  535. sqlspec/observability/_config.py +364 -0
  536. sqlspec/observability/_diagnostics.cp314-win_amd64.pyd +0 -0
  537. sqlspec/observability/_diagnostics.py +74 -0
  538. sqlspec/observability/_dispatcher.cp314-win_amd64.pyd +0 -0
  539. sqlspec/observability/_dispatcher.py +200 -0
  540. sqlspec/observability/_formatters/__init__.py +13 -0
  541. sqlspec/observability/_formatters/_aws.cp314-win_amd64.pyd +0 -0
  542. sqlspec/observability/_formatters/_aws.py +102 -0
  543. sqlspec/observability/_formatters/_azure.cp314-win_amd64.pyd +0 -0
  544. sqlspec/observability/_formatters/_azure.py +96 -0
  545. sqlspec/observability/_formatters/_base.cp314-win_amd64.pyd +0 -0
  546. sqlspec/observability/_formatters/_base.py +57 -0
  547. sqlspec/observability/_formatters/_gcp.cp314-win_amd64.pyd +0 -0
  548. sqlspec/observability/_formatters/_gcp.py +131 -0
  549. sqlspec/observability/_formatting.py +58 -0
  550. sqlspec/observability/_observer.cp314-win_amd64.pyd +0 -0
  551. sqlspec/observability/_observer.py +361 -0
  552. sqlspec/observability/_runtime.cp314-win_amd64.pyd +0 -0
  553. sqlspec/observability/_runtime.py +461 -0
  554. sqlspec/observability/_sampling.cp314-win_amd64.pyd +0 -0
  555. sqlspec/observability/_sampling.py +188 -0
  556. sqlspec/observability/_spans.cp314-win_amd64.pyd +0 -0
  557. sqlspec/observability/_spans.py +161 -0
  558. sqlspec/protocols.py +955 -0
  559. sqlspec/py.typed +0 -0
  560. sqlspec/service.py +433 -0
  561. sqlspec/storage/__init__.py +48 -0
  562. sqlspec/storage/_arrow_payload.py +68 -0
  563. sqlspec/storage/_paths.cp314-win_amd64.pyd +0 -0
  564. sqlspec/storage/_paths.py +58 -0
  565. sqlspec/storage/_utils.py +46 -0
  566. sqlspec/storage/backends/__init__.py +1 -0
  567. sqlspec/storage/backends/base.cp314-win_amd64.pyd +0 -0
  568. sqlspec/storage/backends/base.py +374 -0
  569. sqlspec/storage/backends/fsspec.py +574 -0
  570. sqlspec/storage/backends/local.py +468 -0
  571. sqlspec/storage/backends/obstore.py +956 -0
  572. sqlspec/storage/errors.cp314-win_amd64.pyd +0 -0
  573. sqlspec/storage/errors.py +102 -0
  574. sqlspec/storage/pipeline.cp314-win_amd64.pyd +0 -0
  575. sqlspec/storage/pipeline.py +628 -0
  576. sqlspec/storage/registry.cp314-win_amd64.pyd +0 -0
  577. sqlspec/storage/registry.py +329 -0
  578. sqlspec/typing.py +405 -0
  579. sqlspec/utils/__init__.py +7 -0
  580. sqlspec/utils/arrow_helpers.py +384 -0
  581. sqlspec/utils/config_tools.cp314-win_amd64.pyd +0 -0
  582. sqlspec/utils/config_tools.py +314 -0
  583. sqlspec/utils/correlation.cp314-win_amd64.pyd +0 -0
  584. sqlspec/utils/correlation.py +134 -0
  585. sqlspec/utils/deprecation.cp314-win_amd64.pyd +0 -0
  586. sqlspec/utils/deprecation.py +157 -0
  587. sqlspec/utils/dispatch.cp314-win_amd64.pyd +0 -0
  588. sqlspec/utils/dispatch.py +101 -0
  589. sqlspec/utils/fixtures.cp314-win_amd64.pyd +0 -0
  590. sqlspec/utils/fixtures.py +260 -0
  591. sqlspec/utils/logging.cp314-win_amd64.pyd +0 -0
  592. sqlspec/utils/logging.py +251 -0
  593. sqlspec/utils/module_loader.py +306 -0
  594. sqlspec/utils/portal.cp314-win_amd64.pyd +0 -0
  595. sqlspec/utils/portal.py +377 -0
  596. sqlspec/utils/schema.cp314-win_amd64.pyd +0 -0
  597. sqlspec/utils/schema.py +1040 -0
  598. sqlspec/utils/serializers/__init__.py +30 -0
  599. sqlspec/utils/serializers/_json.cp314-win_amd64.pyd +0 -0
  600. sqlspec/utils/serializers/_json.py +415 -0
  601. sqlspec/utils/serializers/_numpy.cp314-win_amd64.pyd +0 -0
  602. sqlspec/utils/serializers/_numpy.py +65 -0
  603. sqlspec/utils/serializers/_schema.cp314-win_amd64.pyd +0 -0
  604. sqlspec/utils/serializers/_schema.py +285 -0
  605. sqlspec/utils/singleton.cp314-win_amd64.pyd +0 -0
  606. sqlspec/utils/singleton.py +41 -0
  607. sqlspec/utils/sync_tools.cp314-win_amd64.pyd +0 -0
  608. sqlspec/utils/sync_tools.py +316 -0
  609. sqlspec/utils/text.cp314-win_amd64.pyd +0 -0
  610. sqlspec/utils/text.py +109 -0
  611. sqlspec/utils/type_converters.cp314-win_amd64.pyd +0 -0
  612. sqlspec/utils/type_converters.py +216 -0
  613. sqlspec/utils/type_guards.cp314-win_amd64.pyd +0 -0
  614. sqlspec/utils/type_guards.py +1508 -0
  615. sqlspec/utils/uuids.cp314-win_amd64.pyd +0 -0
  616. sqlspec/utils/uuids.py +241 -0
  617. sqlspec-0.47.0.dist-info/METADATA +202 -0
  618. sqlspec-0.47.0.dist-info/RECORD +621 -0
  619. sqlspec-0.47.0.dist-info/WHEEL +4 -0
  620. sqlspec-0.47.0.dist-info/entry_points.txt +6 -0
  621. sqlspec-0.47.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,935 @@
1
+ """DuckDB ADK store for Google Agent Development Kit.
2
+
3
+ DuckDB is an OLAP database optimized for analytical queries. This adapter provides:
4
+ - Embedded session storage with zero-configuration setup
5
+ - Excellent performance for analytical queries on session data
6
+ - Native JSON type support for flexible state storage
7
+ - Perfect for development, testing, and analytical workloads
8
+
9
+ Notes:
10
+ DuckDB is optimized for OLAP workloads and analytical queries. For highly
11
+ concurrent DML operations (frequent inserts/updates/deletes), consider
12
+ PostgreSQL or other OLTP-optimized databases.
13
+ """
14
+
15
+ import contextlib
16
+ from datetime import datetime, timezone
17
+ from typing import TYPE_CHECKING, Any, Final, cast
18
+
19
+ from sqlspec.extensions.adk import BaseAsyncADKStore, EventRecord, SessionRecord
20
+ from sqlspec.extensions.adk.memory.store import BaseAsyncADKMemoryStore
21
+ from sqlspec.utils.logging import get_logger
22
+ from sqlspec.utils.serializers import from_json, to_json
23
+ from sqlspec.utils.sync_tools import async_
24
+
25
+ if TYPE_CHECKING:
26
+ from sqlspec.adapters.duckdb.config import DuckDBConfig
27
+ from sqlspec.extensions.adk import MemoryRecord
28
+
29
+
30
+ __all__ = ("DuckdbADKMemoryStore", "DuckdbADKStore")
31
+
32
+ logger = get_logger("sqlspec.adapters.duckdb.adk.store")
33
+
34
+ DUCKDB_TABLE_NOT_FOUND_ERROR: Final = "does not exist"
35
+
36
+
37
+ class DuckdbADKStore(BaseAsyncADKStore["DuckDBConfig"]):
38
+ """DuckDB ADK store for Google Agent Development Kit.
39
+
40
+ Implements session and event storage for Google Agent Development Kit
41
+ using DuckDB's synchronous driver with async wrappers via ``async_()``.
42
+ Provides:
43
+ - Session state management with native JSON type
44
+ - Event history with single JSON blob (event_json) plus indexed scalars
45
+ - Native TIMESTAMPTZ type support
46
+ - Manual cascade delete (DuckDB has no FK CASCADE)
47
+ - Columnar storage for analytical queries
48
+
49
+ Args:
50
+ config: DuckDBConfig with extension_config["adk"] settings.
51
+
52
+ Example:
53
+ from sqlspec.adapters.duckdb import DuckDBConfig
54
+ from sqlspec.adapters.duckdb.adk import DuckdbADKStore
55
+
56
+ config = DuckDBConfig(
57
+ database="sessions.ddb",
58
+ extension_config={
59
+ "adk": {
60
+ "session_table": "my_sessions",
61
+ "events_table": "my_events",
62
+ "owner_id_column": "tenant_id INTEGER REFERENCES tenants(id)"
63
+ }
64
+ }
65
+ )
66
+ store = DuckdbADKStore(config)
67
+ await store.ensure_tables()
68
+
69
+ Notes:
70
+ - Uses DuckDB native JSON type for event_json and state
71
+ - TIMESTAMPTZ for date/time storage with microsecond precision
72
+ - event_json stores the full ADK Event as a single JSON blob
73
+ - Columnar storage provides excellent analytical query performance
74
+ - DuckDB doesn't support CASCADE in foreign keys (manual cascade required)
75
+ - Optimized for OLAP workloads; for high-concurrency writes use PostgreSQL
76
+ - Configuration is read from config.extension_config["adk"]
77
+ """
78
+
79
+ __slots__ = ()
80
+
81
+ def __init__(self, config: "DuckDBConfig") -> None:
82
+ """Initialize DuckDB ADK store.
83
+
84
+ Args:
85
+ config: DuckDBConfig instance.
86
+
87
+ Notes:
88
+ Configuration is read from config.extension_config["adk"]:
89
+ - session_table: Sessions table name (default: "adk_sessions")
90
+ - events_table: Events table name (default: "adk_events")
91
+ - owner_id_column: Optional owner FK column DDL (default: None)
92
+ """
93
+ super().__init__(config)
94
+
95
+ async def _get_create_sessions_table_sql(self) -> str:
96
+ """Get DuckDB CREATE TABLE SQL for sessions.
97
+
98
+ Returns:
99
+ SQL statement to create adk_sessions table with indexes.
100
+
101
+ Notes:
102
+ - VARCHAR for IDs and names
103
+ - JSON type for state storage (DuckDB native)
104
+ - TIMESTAMPTZ for create_time and update_time
105
+ - CURRENT_TIMESTAMP for defaults
106
+ - Optional owner ID column for multi-tenant scenarios
107
+ - Composite index on (app_name, user_id) for listing
108
+ - Index on update_time DESC for recent session queries
109
+ """
110
+ owner_id_line = ""
111
+ if self._owner_id_column_ddl:
112
+ owner_id_line = f",\n {self._owner_id_column_ddl}"
113
+
114
+ return f"""
115
+ CREATE TABLE IF NOT EXISTS {self._session_table} (
116
+ id VARCHAR PRIMARY KEY,
117
+ app_name VARCHAR NOT NULL,
118
+ user_id VARCHAR NOT NULL{owner_id_line},
119
+ state JSON NOT NULL,
120
+ create_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
121
+ update_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
122
+ );
123
+ CREATE INDEX IF NOT EXISTS idx_{self._session_table}_app_user ON {self._session_table}(app_name, user_id);
124
+ CREATE INDEX IF NOT EXISTS idx_{self._session_table}_update_time ON {self._session_table}(update_time DESC);
125
+ """
126
+
127
+ async def _get_create_events_table_sql(self) -> str:
128
+ """Get DuckDB CREATE TABLE SQL for events.
129
+
130
+ Returns:
131
+ SQL statement to create adk_events table with indexes.
132
+
133
+ Notes:
134
+ - 5-column schema: session_id, invocation_id, author, timestamp, event_json
135
+ - event_json stores the full ADK Event as a single JSON blob
136
+ - No decomposed columns -- eliminates column drift with upstream ADK
137
+ - Foreign key constraint (DuckDB doesn't support CASCADE)
138
+ - Index on (session_id, timestamp ASC) for ordered event retrieval
139
+ - Manual cascade delete required in delete_session method
140
+ """
141
+ return f"""
142
+ CREATE TABLE IF NOT EXISTS {self._events_table} (
143
+ session_id VARCHAR NOT NULL,
144
+ invocation_id VARCHAR NOT NULL,
145
+ author VARCHAR NOT NULL,
146
+ timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
147
+ event_json JSON NOT NULL,
148
+ FOREIGN KEY (session_id) REFERENCES {self._session_table}(id)
149
+ );
150
+ CREATE INDEX IF NOT EXISTS idx_{self._events_table}_session ON {self._events_table}(session_id, timestamp ASC);
151
+ """
152
+
153
+ def _get_drop_tables_sql(self) -> "list[str]":
154
+ """Get DuckDB DROP TABLE SQL statements.
155
+
156
+ Returns:
157
+ List of SQL statements to drop tables and indexes.
158
+
159
+ Notes:
160
+ Order matters: drop events table (child) before sessions (parent).
161
+ DuckDB automatically drops indexes when dropping tables.
162
+ """
163
+ return [f"DROP TABLE IF EXISTS {self._events_table}", f"DROP TABLE IF EXISTS {self._session_table}"]
164
+
165
+ def _create_tables(self) -> None:
166
+ """Synchronous implementation of create_tables."""
167
+ with self._config.provide_connection() as conn:
168
+ conn.execute(self.__get_create_sessions_table_sql_sync())
169
+ conn.execute(self.__get_create_events_table_sql_sync())
170
+
171
+ def __get_create_sessions_table_sql_sync(self) -> str:
172
+ """Synchronous version of DDL generation for use in _create_tables."""
173
+ owner_id_line = ""
174
+ if self._owner_id_column_ddl:
175
+ owner_id_line = f",\n {self._owner_id_column_ddl}"
176
+
177
+ return f"""
178
+ CREATE TABLE IF NOT EXISTS {self._session_table} (
179
+ id VARCHAR PRIMARY KEY,
180
+ app_name VARCHAR NOT NULL,
181
+ user_id VARCHAR NOT NULL{owner_id_line},
182
+ state JSON NOT NULL,
183
+ create_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
184
+ update_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
185
+ );
186
+ CREATE INDEX IF NOT EXISTS idx_{self._session_table}_app_user ON {self._session_table}(app_name, user_id);
187
+ CREATE INDEX IF NOT EXISTS idx_{self._session_table}_update_time ON {self._session_table}(update_time DESC);
188
+ """
189
+
190
+ def __get_create_events_table_sql_sync(self) -> str:
191
+ """Synchronous version of DDL generation for use in _create_tables."""
192
+ return f"""
193
+ CREATE TABLE IF NOT EXISTS {self._events_table} (
194
+ session_id VARCHAR NOT NULL,
195
+ invocation_id VARCHAR NOT NULL,
196
+ author VARCHAR NOT NULL,
197
+ timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
198
+ event_json JSON NOT NULL,
199
+ FOREIGN KEY (session_id) REFERENCES {self._session_table}(id)
200
+ );
201
+ CREATE INDEX IF NOT EXISTS idx_{self._events_table}_session ON {self._events_table}(session_id, timestamp ASC);
202
+ """
203
+
204
+ async def create_tables(self) -> None:
205
+ """Create both sessions and events tables if they don't exist."""
206
+ await async_(self._create_tables)()
207
+
208
+ def _create_session(
209
+ self, session_id: str, app_name: str, user_id: str, state: "dict[str, Any]", owner_id: "Any | None" = None
210
+ ) -> SessionRecord:
211
+ """Synchronous implementation of create_session."""
212
+ now = datetime.now(timezone.utc)
213
+ state_json = to_json(state)
214
+
215
+ params: tuple[Any, ...]
216
+ if self._owner_id_column_name:
217
+ sql = f"""
218
+ INSERT INTO {self._session_table}
219
+ (id, app_name, user_id, {self._owner_id_column_name}, state, create_time, update_time)
220
+ VALUES (?, ?, ?, ?, ?, ?, ?)
221
+ """
222
+ params = (session_id, app_name, user_id, owner_id, state_json, now, now)
223
+ else:
224
+ sql = f"""
225
+ INSERT INTO {self._session_table} (id, app_name, user_id, state, create_time, update_time)
226
+ VALUES (?, ?, ?, ?, ?, ?)
227
+ """
228
+ params = (session_id, app_name, user_id, state_json, now, now)
229
+
230
+ with self._config.provide_connection() as conn:
231
+ conn.execute(sql, params)
232
+ conn.commit()
233
+
234
+ return SessionRecord(
235
+ id=session_id, app_name=app_name, user_id=user_id, state=state, create_time=now, update_time=now
236
+ )
237
+
238
+ async def create_session(
239
+ self, session_id: str, app_name: str, user_id: str, state: "dict[str, Any]", owner_id: "Any | None" = None
240
+ ) -> SessionRecord:
241
+ """Create a new session.
242
+
243
+ Args:
244
+ session_id: Unique session identifier.
245
+ app_name: Application name.
246
+ user_id: User identifier.
247
+ state: Initial session state.
248
+ owner_id: Optional owner ID value for owner_id_column (if configured).
249
+
250
+ Returns:
251
+ Created session record.
252
+
253
+ Notes:
254
+ Uses current UTC timestamp for create_time and update_time.
255
+ State is JSON-serialized using SQLSpec serializers.
256
+ """
257
+ return await async_(self._create_session)(session_id, app_name, user_id, state, owner_id)
258
+
259
+ def _get_session(self, session_id: str) -> "SessionRecord | None":
260
+ """Synchronous implementation of get_session."""
261
+ sql = f"""
262
+ SELECT id, app_name, user_id, state, create_time, update_time
263
+ FROM {self._session_table}
264
+ WHERE id = ?
265
+ """
266
+
267
+ try:
268
+ with self._config.provide_connection() as conn:
269
+ cursor = conn.execute(sql, (session_id,))
270
+ row = cursor.fetchone()
271
+
272
+ if row is None:
273
+ return None
274
+
275
+ session_id_val, app_name, user_id, state_data, create_time, update_time = row
276
+
277
+ state = from_json(state_data) if state_data else {}
278
+
279
+ return SessionRecord(
280
+ id=session_id_val,
281
+ app_name=app_name,
282
+ user_id=user_id,
283
+ state=state,
284
+ create_time=create_time,
285
+ update_time=update_time,
286
+ )
287
+ except Exception as e:
288
+ if DUCKDB_TABLE_NOT_FOUND_ERROR in str(e):
289
+ return None
290
+ raise
291
+
292
+ async def get_session(self, session_id: str) -> "SessionRecord | None":
293
+ """Get session by ID.
294
+
295
+ Args:
296
+ session_id: Session identifier.
297
+
298
+ Returns:
299
+ Session record or None if not found.
300
+
301
+ Notes:
302
+ DuckDB returns datetime objects for TIMESTAMPTZ columns.
303
+ JSON is parsed from database storage.
304
+ """
305
+ return await async_(self._get_session)(session_id)
306
+
307
+ def _update_session_state(self, session_id: str, state: "dict[str, Any]") -> None:
308
+ """Synchronous implementation of update_session_state."""
309
+ now = datetime.now(timezone.utc)
310
+ state_json = to_json(state)
311
+
312
+ sql = f"""
313
+ UPDATE {self._session_table}
314
+ SET state = ?, update_time = ?
315
+ WHERE id = ?
316
+ """
317
+
318
+ with self._config.provide_connection() as conn:
319
+ conn.execute(sql, (state_json, now, session_id))
320
+ conn.commit()
321
+
322
+ async def update_session_state(self, session_id: str, state: "dict[str, Any]") -> None:
323
+ """Update session state.
324
+
325
+ Args:
326
+ session_id: Session identifier.
327
+ state: New state dictionary (replaces existing state).
328
+
329
+ Notes:
330
+ This replaces the entire state dictionary.
331
+ Update time is automatically set to current UTC timestamp.
332
+ """
333
+ await async_(self._update_session_state)(session_id, state)
334
+
335
+ def _delete_session(self, session_id: str) -> None:
336
+ """Synchronous implementation of delete_session."""
337
+ delete_events_sql = f"DELETE FROM {self._events_table} WHERE session_id = ?"
338
+ delete_session_sql = f"DELETE FROM {self._session_table} WHERE id = ?"
339
+
340
+ with self._config.provide_connection() as conn:
341
+ conn.execute(delete_events_sql, (session_id,))
342
+ conn.execute(delete_session_sql, (session_id,))
343
+ conn.commit()
344
+
345
+ async def delete_session(self, session_id: str) -> None:
346
+ """Delete session and all associated events.
347
+
348
+ Args:
349
+ session_id: Session identifier.
350
+
351
+ Notes:
352
+ DuckDB doesn't support CASCADE in foreign keys, so we manually delete events first.
353
+ """
354
+ await async_(self._delete_session)(session_id)
355
+
356
+ def _list_sessions(self, app_name: str, user_id: "str | None" = None) -> "list[SessionRecord]":
357
+ """Synchronous implementation of list_sessions."""
358
+ if user_id is None:
359
+ sql = f"""
360
+ SELECT id, app_name, user_id, state, create_time, update_time
361
+ FROM {self._session_table}
362
+ WHERE app_name = ?
363
+ ORDER BY update_time DESC
364
+ """
365
+ params: tuple[str, ...] = (app_name,)
366
+ else:
367
+ sql = f"""
368
+ SELECT id, app_name, user_id, state, create_time, update_time
369
+ FROM {self._session_table}
370
+ WHERE app_name = ? AND user_id = ?
371
+ ORDER BY update_time DESC
372
+ """
373
+ params = (app_name, user_id)
374
+
375
+ try:
376
+ with self._config.provide_connection() as conn:
377
+ cursor = conn.execute(sql, params)
378
+ rows = cursor.fetchall()
379
+
380
+ return [
381
+ SessionRecord(
382
+ id=row[0],
383
+ app_name=row[1],
384
+ user_id=row[2],
385
+ state=from_json(row[3]) if row[3] else {},
386
+ create_time=row[4],
387
+ update_time=row[5],
388
+ )
389
+ for row in rows
390
+ ]
391
+ except Exception as e:
392
+ if DUCKDB_TABLE_NOT_FOUND_ERROR in str(e):
393
+ return []
394
+ raise
395
+
396
+ async def list_sessions(self, app_name: str, user_id: str | None = None) -> "list[SessionRecord]":
397
+ """List sessions for an app, optionally filtered by user.
398
+
399
+ Args:
400
+ app_name: Application name.
401
+ user_id: User identifier. If None, lists all sessions for the app.
402
+
403
+ Returns:
404
+ List of session records ordered by update_time DESC.
405
+
406
+ Notes:
407
+ Uses composite index on (app_name, user_id) when user_id is provided.
408
+ """
409
+ return await async_(self._list_sessions)(app_name, user_id)
410
+
411
+ def _append_event(self, event_record: EventRecord) -> None:
412
+ """Synchronous implementation of append_event."""
413
+ event_json_str = to_json(event_record["event_json"])
414
+
415
+ sql = f"""
416
+ INSERT INTO {self._events_table}
417
+ (session_id, invocation_id, author, timestamp, event_json)
418
+ VALUES (?, ?, ?, ?, ?)
419
+ """
420
+
421
+ with self._config.provide_connection() as conn:
422
+ conn.execute(
423
+ sql,
424
+ (
425
+ event_record["session_id"],
426
+ event_record["invocation_id"],
427
+ event_record["author"],
428
+ event_record["timestamp"],
429
+ event_json_str,
430
+ ),
431
+ )
432
+ conn.commit()
433
+
434
+ async def append_event(self, event_record: EventRecord) -> None:
435
+ """Append an event to a session.
436
+
437
+ Args:
438
+ event_record: Event record with 5 keys (session_id, invocation_id,
439
+ author, timestamp, event_json).
440
+ """
441
+ await async_(self._append_event)(event_record)
442
+
443
+ def _append_event_and_update_state(
444
+ self, event_record: EventRecord, session_id: str, state: "dict[str, Any]"
445
+ ) -> SessionRecord:
446
+ """Synchronous implementation of append_event_and_update_state."""
447
+ now = datetime.now(timezone.utc)
448
+ state_json = to_json(state)
449
+ event_json_str = to_json(event_record["event_json"])
450
+
451
+ insert_sql = f"""
452
+ INSERT INTO {self._events_table}
453
+ (session_id, invocation_id, author, timestamp, event_json)
454
+ VALUES (?, ?, ?, ?, ?)
455
+ """
456
+
457
+ update_sql = f"""
458
+ UPDATE {self._session_table}
459
+ SET state = ?, update_time = ?
460
+ WHERE id = ?
461
+ RETURNING id, app_name, user_id, state, create_time, update_time
462
+ """
463
+
464
+ with self._config.provide_connection() as conn:
465
+ conn.execute(
466
+ insert_sql,
467
+ (
468
+ event_record["session_id"],
469
+ event_record["invocation_id"],
470
+ event_record["author"],
471
+ event_record["timestamp"],
472
+ event_json_str,
473
+ ),
474
+ )
475
+ cursor = conn.execute(update_sql, (state_json, now, session_id))
476
+ row = cursor.fetchone()
477
+ conn.commit()
478
+
479
+ if row is None:
480
+ msg = f"Session {session_id} not found during append_event_and_update_state."
481
+ raise ValueError(msg)
482
+
483
+ session_id_val, app_name, user_id, state_data, create_time, update_time = row
484
+ return SessionRecord(
485
+ id=session_id_val,
486
+ app_name=app_name,
487
+ user_id=user_id,
488
+ state=from_json(state_data) if isinstance(state_data, str) else state_data,
489
+ create_time=create_time,
490
+ update_time=update_time,
491
+ )
492
+
493
+ async def append_event_and_update_state(
494
+ self, event_record: EventRecord, session_id: str, state: "dict[str, Any]"
495
+ ) -> SessionRecord:
496
+ """Atomically append an event and update the session's durable state.
497
+
498
+ The event insert and state update succeed together or fail together
499
+ within a single DuckDB transaction; the updated SessionRecord is
500
+ returned via UPDATE...RETURNING.
501
+
502
+ Args:
503
+ event_record: Event record to store (5-key shape).
504
+ session_id: Session identifier whose state should be updated.
505
+ state: Post-append durable state snapshot (``temp:`` keys already
506
+ stripped by the service layer).
507
+ """
508
+ return await async_(self._append_event_and_update_state)(event_record, session_id, state)
509
+
510
+ def _get_events(
511
+ self, session_id: str, after_timestamp: "datetime | None" = None, limit: "int | None" = None
512
+ ) -> "list[EventRecord]":
513
+ """Synchronous implementation of get_events."""
514
+ where_clauses = ["session_id = ?"]
515
+ params: list[Any] = [session_id]
516
+
517
+ if after_timestamp is not None:
518
+ where_clauses.append("timestamp > ?")
519
+ params.append(after_timestamp)
520
+
521
+ where_clause = " AND ".join(where_clauses)
522
+ limit_clause = f" LIMIT {limit}" if limit else ""
523
+
524
+ sql = f"""
525
+ SELECT session_id, invocation_id, author, timestamp, event_json
526
+ FROM {self._events_table}
527
+ WHERE {where_clause}
528
+ ORDER BY timestamp ASC{limit_clause}
529
+ """
530
+
531
+ try:
532
+ with self._config.provide_connection() as conn:
533
+ cursor = conn.execute(sql, params)
534
+ rows = cursor.fetchall()
535
+
536
+ return [
537
+ EventRecord(
538
+ session_id=row[0],
539
+ invocation_id=row[1],
540
+ author=row[2],
541
+ timestamp=row[3],
542
+ event_json=from_json(row[4]) if isinstance(row[4], str) else row[4],
543
+ )
544
+ for row in rows
545
+ ]
546
+ except Exception as e:
547
+ if DUCKDB_TABLE_NOT_FOUND_ERROR in str(e):
548
+ return []
549
+ raise
550
+
551
+ async def get_events(
552
+ self, session_id: str, after_timestamp: "datetime | None" = None, limit: "int | None" = None
553
+ ) -> "list[EventRecord]":
554
+ """Get events for a session.
555
+
556
+ Args:
557
+ session_id: Session identifier.
558
+ after_timestamp: Only return events after this time.
559
+ limit: Maximum number of events to return.
560
+
561
+ Returns:
562
+ List of event records ordered by timestamp ASC.
563
+ """
564
+ return await async_(self._get_events)(session_id, after_timestamp, limit)
565
+
566
+
567
+ class DuckdbADKMemoryStore(BaseAsyncADKMemoryStore["DuckDBConfig"]):
568
+ """DuckDB ADK memory store using synchronous DuckDB driver with async wrappers.
569
+
570
+ Implements memory entry storage for Google Agent Development Kit
571
+ using DuckDB's synchronous driver with async wrappers via ``async_()``.
572
+ Provides:
573
+ - Session memory storage with native JSON type
574
+ - Simple ILIKE search or BM25 full-text search via FTS extension
575
+ - Native TIMESTAMP type support
576
+ - Deduplication via event_id unique constraint
577
+ - Efficient upserts using INSERT OR IGNORE
578
+ - Columnar storage for analytical queries
579
+
580
+ Args:
581
+ config: DuckDBConfig with extension_config["adk"] settings.
582
+
583
+ Example:
584
+ from sqlspec.adapters.duckdb import DuckDBConfig
585
+ from sqlspec.adapters.duckdb.adk import DuckdbADKMemoryStore
586
+
587
+ config = DuckDBConfig(
588
+ database="app.ddb",
589
+ extension_config={
590
+ "adk": {
591
+ "memory_table": "adk_memory_entries",
592
+ "memory_max_results": 20,
593
+ }
594
+ }
595
+ )
596
+ store = DuckdbADKMemoryStore(config)
597
+ await store.ensure_tables()
598
+
599
+ Notes:
600
+ - Uses DuckDB native JSON type (not JSONB)
601
+ - TIMESTAMP for date/time storage with microsecond precision
602
+ - event_id UNIQUE constraint for deduplication
603
+ - Composite index on (app_name, user_id, timestamp DESC)
604
+ - FTS uses match_bm25() for BM25-ranked results (not @@ operator)
605
+ - FTS index is refreshed after inserts, not on every search
606
+ - Columnar storage provides excellent analytical query performance
607
+ - Optimized for OLAP workloads; for high-concurrency writes use PostgreSQL
608
+ - Configuration is read from config.extension_config["adk"]
609
+ """
610
+
611
+ __slots__ = ()
612
+
613
+ def __init__(self, config: "DuckDBConfig") -> None:
614
+ """Initialize DuckDB ADK memory store.
615
+
616
+ Args:
617
+ config: DuckDBConfig instance.
618
+
619
+ Notes:
620
+ Configuration is read from config.extension_config["adk"]:
621
+ - memory_table: Memory table name (default: "adk_memory_entries")
622
+ - memory_use_fts: Enable full-text search when supported (default: False)
623
+ - memory_max_results: Max search results (default: 20)
624
+ - owner_id_column: Optional owner FK column DDL (default: None)
625
+ - enable_memory: Whether memory is enabled (default: True)
626
+ """
627
+ super().__init__(config)
628
+
629
+ def _ensure_fts_extension(self, conn: Any) -> bool:
630
+ """Ensure the DuckDB FTS extension is available for this connection."""
631
+ with contextlib.suppress(Exception):
632
+ conn.execute("INSTALL fts")
633
+
634
+ try:
635
+ conn.execute("LOAD fts")
636
+ except Exception as exc:
637
+ logger.debug("DuckDB FTS extension unavailable: %s", exc)
638
+ return False
639
+
640
+ return True
641
+
642
+ def _create_fts_index(self, conn: Any) -> None:
643
+ """Create FTS index for the memory table."""
644
+ if not self._ensure_fts_extension(conn):
645
+ return
646
+
647
+ try:
648
+ conn.execute(
649
+ f"PRAGMA create_fts_index('{self._memory_table}', 'id', 'content_text', "
650
+ f"stemmer='porter', stopwords='english', strip_accents=1, lower=1)"
651
+ )
652
+ conn.commit()
653
+ except Exception as exc:
654
+ logger.debug("Failed to create DuckDB FTS index: %s", exc)
655
+
656
+ def _refresh_fts_index(self, conn: Any) -> None:
657
+ """Rebuild the FTS index to reflect recent inserts.
658
+
659
+ DuckDB FTS indexes do not auto-update. This must be called after
660
+ insert/update/delete operations, NOT on every search.
661
+ """
662
+ if not self._ensure_fts_extension(conn):
663
+ return
664
+
665
+ try:
666
+ conn.execute(
667
+ f"PRAGMA create_fts_index('{self._memory_table}', 'id', 'content_text', "
668
+ f"overwrite=1, stemmer='porter', stopwords='english', strip_accents=1, lower=1)"
669
+ )
670
+ conn.commit()
671
+ except Exception as exc:
672
+ logger.debug("Failed to refresh DuckDB FTS index: %s", exc)
673
+
674
+ async def _get_create_memory_table_sql(self) -> str:
675
+ """Get DuckDB CREATE TABLE SQL for memory entries.
676
+
677
+ Returns:
678
+ SQL statement to create memory table with indexes.
679
+ """
680
+ owner_id_line = ""
681
+ if self._owner_id_column_ddl:
682
+ owner_id_line = f",\n {self._owner_id_column_ddl}"
683
+
684
+ return f"""
685
+ CREATE TABLE IF NOT EXISTS {self._memory_table} (
686
+ id VARCHAR(128) PRIMARY KEY,
687
+ session_id VARCHAR(128) NOT NULL,
688
+ app_name VARCHAR(128) NOT NULL,
689
+ user_id VARCHAR(128) NOT NULL,
690
+ event_id VARCHAR(128) NOT NULL UNIQUE,
691
+ author VARCHAR(256){owner_id_line},
692
+ timestamp TIMESTAMP NOT NULL,
693
+ content_json JSON NOT NULL,
694
+ content_text TEXT NOT NULL,
695
+ metadata_json JSON,
696
+ inserted_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
697
+ );
698
+
699
+ CREATE INDEX IF NOT EXISTS idx_{self._memory_table}_app_user_time
700
+ ON {self._memory_table}(app_name, user_id, timestamp DESC);
701
+
702
+ CREATE INDEX IF NOT EXISTS idx_{self._memory_table}_session
703
+ ON {self._memory_table}(session_id);
704
+ """
705
+
706
+ def _get_drop_memory_table_sql(self) -> "list[str]":
707
+ """Get DuckDB DROP TABLE SQL statements."""
708
+ return [f"DROP TABLE IF EXISTS {self._memory_table}"]
709
+
710
+ def _create_tables(self) -> None:
711
+ """Synchronous implementation of create_tables."""
712
+ if not self._enabled:
713
+ return
714
+
715
+ ddl = self.__get_create_memory_table_sql_sync()
716
+ with self._config.provide_connection() as conn:
717
+ conn.execute(ddl)
718
+ if self._use_fts:
719
+ self._create_fts_index(conn)
720
+
721
+ def __get_create_memory_table_sql_sync(self) -> str:
722
+ """Synchronous version of DDL generation for use in _create_tables."""
723
+ owner_id_line = ""
724
+ if self._owner_id_column_ddl:
725
+ owner_id_line = f",\n {self._owner_id_column_ddl}"
726
+
727
+ return f"""
728
+ CREATE TABLE IF NOT EXISTS {self._memory_table} (
729
+ id VARCHAR(128) PRIMARY KEY,
730
+ session_id VARCHAR(128) NOT NULL,
731
+ app_name VARCHAR(128) NOT NULL,
732
+ user_id VARCHAR(128) NOT NULL,
733
+ event_id VARCHAR(128) NOT NULL UNIQUE,
734
+ author VARCHAR(256){owner_id_line},
735
+ timestamp TIMESTAMP NOT NULL,
736
+ content_json JSON NOT NULL,
737
+ content_text TEXT NOT NULL,
738
+ metadata_json JSON,
739
+ inserted_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
740
+ );
741
+
742
+ CREATE INDEX IF NOT EXISTS idx_{self._memory_table}_app_user_time
743
+ ON {self._memory_table}(app_name, user_id, timestamp DESC);
744
+
745
+ CREATE INDEX IF NOT EXISTS idx_{self._memory_table}_session
746
+ ON {self._memory_table}(session_id);
747
+ """
748
+
749
+ async def create_tables(self) -> None:
750
+ """Create the memory table and indexes if they don't exist."""
751
+ await async_(self._create_tables)()
752
+
753
+ def _insert_memory_entries(self, entries: "list[MemoryRecord]", owner_id: "object | None" = None) -> int:
754
+ """Synchronous implementation of insert_memory_entries."""
755
+ if not self._enabled:
756
+ msg = "Memory store is disabled"
757
+ raise RuntimeError(msg)
758
+
759
+ if not entries:
760
+ return 0
761
+
762
+ inserted_count = 0
763
+ if self._owner_id_column_name:
764
+ sql = f"""
765
+ INSERT INTO {self._memory_table} (
766
+ id, session_id, app_name, user_id, event_id, author,
767
+ {self._owner_id_column_name}, timestamp, content_json,
768
+ content_text, metadata_json, inserted_at
769
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
770
+ ON CONFLICT(event_id) DO NOTHING RETURNING 1
771
+ """
772
+ else:
773
+ sql = f"""
774
+ INSERT INTO {self._memory_table} (
775
+ id, session_id, app_name, user_id, event_id, author,
776
+ timestamp, content_json, content_text, metadata_json, inserted_at
777
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
778
+ ON CONFLICT(event_id) DO NOTHING RETURNING 1
779
+ """
780
+
781
+ with self._config.provide_connection() as conn:
782
+ for entry in entries:
783
+ params: tuple[Any, ...]
784
+ if self._owner_id_column_name:
785
+ params = (
786
+ entry["id"],
787
+ entry["session_id"],
788
+ entry["app_name"],
789
+ entry["user_id"],
790
+ entry["event_id"],
791
+ entry["author"],
792
+ owner_id,
793
+ entry["timestamp"],
794
+ to_json(entry["content_json"]),
795
+ entry["content_text"],
796
+ to_json(entry["metadata_json"]),
797
+ entry["inserted_at"],
798
+ )
799
+ else:
800
+ params = (
801
+ entry["id"],
802
+ entry["session_id"],
803
+ entry["app_name"],
804
+ entry["user_id"],
805
+ entry["event_id"],
806
+ entry["author"],
807
+ entry["timestamp"],
808
+ to_json(entry["content_json"]),
809
+ entry["content_text"],
810
+ to_json(entry["metadata_json"]),
811
+ entry["inserted_at"],
812
+ )
813
+ result = conn.execute(sql, params)
814
+ inserted_count += len(result.fetchall())
815
+ conn.commit()
816
+
817
+ # Refresh FTS index after inserts, not on search
818
+ if self._use_fts and inserted_count > 0:
819
+ self._refresh_fts_index(conn)
820
+
821
+ return inserted_count
822
+
823
+ async def insert_memory_entries(self, entries: "list[MemoryRecord]", owner_id: "object | None" = None) -> int:
824
+ """Bulk insert memory entries with deduplication.
825
+
826
+ After successful inserts, refreshes the FTS index if FTS is enabled.
827
+ """
828
+ return await async_(self._insert_memory_entries)(entries, owner_id)
829
+
830
+ def _search_entries(
831
+ self, query: str, app_name: str, user_id: str, limit: "int | None" = None
832
+ ) -> "list[MemoryRecord]":
833
+ """Synchronous implementation of search_entries."""
834
+ if not self._enabled:
835
+ msg = "Memory store is disabled"
836
+ raise RuntimeError(msg)
837
+
838
+ if not query:
839
+ return []
840
+
841
+ limit_value = limit or self._max_results
842
+ use_fts = self._use_fts
843
+
844
+ with self._config.provide_connection() as conn:
845
+ if use_fts and not self._ensure_fts_extension(conn):
846
+ use_fts = False
847
+
848
+ if use_fts:
849
+ # Use match_bm25() -- the correct DuckDB FTS syntax
850
+ sql = f"""
851
+ SELECT m.*
852
+ FROM {self._memory_table} m
853
+ JOIN (
854
+ SELECT id, fts_main_{self._memory_table}.match_bm25(id, ?, fields := 'content_text') AS score
855
+ FROM {self._memory_table}
856
+ ) fts ON m.id = fts.id
857
+ WHERE m.app_name = ? AND m.user_id = ? AND fts.score IS NOT NULL
858
+ ORDER BY fts.score DESC
859
+ LIMIT ?
860
+ """
861
+ params = (query, app_name, user_id, limit_value)
862
+ else:
863
+ sql = f"""
864
+ SELECT * FROM {self._memory_table}
865
+ WHERE app_name = ? AND user_id = ? AND content_text ILIKE ?
866
+ ORDER BY timestamp DESC
867
+ LIMIT ?
868
+ """
869
+ params = (app_name, user_id, f"%{query}%", limit_value)
870
+
871
+ rows = conn.execute(sql, params).fetchall()
872
+ columns = [col[0] for col in conn.description or []]
873
+ records: list[MemoryRecord] = []
874
+ for row in rows:
875
+ record = cast("MemoryRecord", dict(zip(columns, row, strict=False)))
876
+ content_value = record["content_json"]
877
+ if isinstance(content_value, (str, bytes)):
878
+ record["content_json"] = from_json(content_value)
879
+ metadata_value = record.get("metadata_json")
880
+ if isinstance(metadata_value, (str, bytes)):
881
+ record["metadata_json"] = from_json(metadata_value)
882
+ records.append(record)
883
+ return records
884
+
885
+ async def search_entries(
886
+ self, query: str, app_name: str, user_id: str, limit: "int | None" = None
887
+ ) -> "list[MemoryRecord]":
888
+ """Search memory entries by text query.
889
+
890
+ When FTS is enabled, uses ``match_bm25()`` for BM25-ranked results.
891
+ Falls back to ILIKE for simple substring matching.
892
+ """
893
+ return await async_(self._search_entries)(query, app_name, user_id, limit)
894
+
895
+ def _delete_entries_by_session(self, session_id: str) -> int:
896
+ """Synchronous implementation of delete_entries_by_session."""
897
+ if not self._enabled:
898
+ msg = "Memory store is disabled"
899
+ raise RuntimeError(msg)
900
+
901
+ sql = f"DELETE FROM {self._memory_table} WHERE session_id = ? RETURNING 1"
902
+ with self._config.provide_connection() as conn:
903
+ result = conn.execute(sql, (session_id,))
904
+ deleted_count = len(result.fetchall())
905
+ conn.commit()
906
+ if self._use_fts and deleted_count > 0:
907
+ self._refresh_fts_index(conn)
908
+ return deleted_count
909
+
910
+ async def delete_entries_by_session(self, session_id: str) -> int:
911
+ """Delete all memory entries for a specific session."""
912
+ return await async_(self._delete_entries_by_session)(session_id)
913
+
914
+ def _delete_entries_older_than(self, days: int) -> int:
915
+ """Synchronous implementation of delete_entries_older_than."""
916
+ if not self._enabled:
917
+ msg = "Memory store is disabled"
918
+ raise RuntimeError(msg)
919
+
920
+ sql = f"""
921
+ DELETE FROM {self._memory_table}
922
+ WHERE inserted_at < (CURRENT_TIMESTAMP - INTERVAL '{days} days')
923
+ RETURNING 1
924
+ """
925
+ with self._config.provide_connection() as conn:
926
+ result = conn.execute(sql)
927
+ deleted_count = len(result.fetchall())
928
+ conn.commit()
929
+ if self._use_fts and deleted_count > 0:
930
+ self._refresh_fts_index(conn)
931
+ return deleted_count
932
+
933
+ async def delete_entries_older_than(self, days: int) -> int:
934
+ """Delete memory entries older than specified days."""
935
+ return await async_(self._delete_entries_older_than)(days)