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.
- f68e0789eb443ecb1c2c__mypyc.cp314-win_amd64.pyd +0 -0
- sqlspec/__init__.py +167 -0
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +14 -0
- sqlspec/_typing.py +714 -0
- sqlspec/adapters/__init__.py +0 -0
- sqlspec/adapters/adbc/__init__.py +13 -0
- sqlspec/adapters/adbc/_typing.py +106 -0
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +1280 -0
- sqlspec/adapters/adbc/config.py +378 -0
- sqlspec/adapters/adbc/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/adbc/core.py +922 -0
- sqlspec/adapters/adbc/data_dictionary.py +339 -0
- sqlspec/adapters/adbc/driver.py +534 -0
- sqlspec/adapters/adbc/events/__init__.py +5 -0
- sqlspec/adapters/adbc/events/store.py +285 -0
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +534 -0
- sqlspec/adapters/adbc/type_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/adbc/type_converter.py +142 -0
- sqlspec/adapters/aiomysql/__init__.py +21 -0
- sqlspec/adapters/aiomysql/_typing.py +137 -0
- sqlspec/adapters/aiomysql/adk/__init__.py +5 -0
- sqlspec/adapters/aiomysql/adk/store.py +678 -0
- sqlspec/adapters/aiomysql/config.py +305 -0
- sqlspec/adapters/aiomysql/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/aiomysql/core.py +536 -0
- sqlspec/adapters/aiomysql/data_dictionary.py +121 -0
- sqlspec/adapters/aiomysql/driver.py +386 -0
- sqlspec/adapters/aiomysql/events/__init__.py +5 -0
- sqlspec/adapters/aiomysql/events/store.py +104 -0
- sqlspec/adapters/aiomysql/litestar/__init__.py +5 -0
- sqlspec/adapters/aiomysql/litestar/store.py +314 -0
- sqlspec/adapters/aiosqlite/__init__.py +26 -0
- sqlspec/adapters/aiosqlite/_typing.py +109 -0
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +829 -0
- sqlspec/adapters/aiosqlite/config.py +315 -0
- sqlspec/adapters/aiosqlite/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/aiosqlite/core.py +315 -0
- sqlspec/adapters/aiosqlite/data_dictionary.py +202 -0
- sqlspec/adapters/aiosqlite/driver.py +311 -0
- sqlspec/adapters/aiosqlite/events/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/events/store.py +20 -0
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +279 -0
- sqlspec/adapters/aiosqlite/pool.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/aiosqlite/pool.py +734 -0
- sqlspec/adapters/asyncmy/__init__.py +21 -0
- sqlspec/adapters/asyncmy/_typing.py +113 -0
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +644 -0
- sqlspec/adapters/asyncmy/config.py +307 -0
- sqlspec/adapters/asyncmy/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/asyncmy/core.py +538 -0
- sqlspec/adapters/asyncmy/data_dictionary.py +122 -0
- sqlspec/adapters/asyncmy/driver.py +391 -0
- sqlspec/adapters/asyncmy/events/__init__.py +5 -0
- sqlspec/adapters/asyncmy/events/store.py +104 -0
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +26 -0
- sqlspec/adapters/asyncpg/_typing.py +103 -0
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +483 -0
- sqlspec/adapters/asyncpg/config.py +575 -0
- sqlspec/adapters/asyncpg/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/asyncpg/core.py +480 -0
- sqlspec/adapters/asyncpg/data_dictionary.py +157 -0
- sqlspec/adapters/asyncpg/driver.py +487 -0
- sqlspec/adapters/asyncpg/events/__init__.py +6 -0
- sqlspec/adapters/asyncpg/events/_hub.py +181 -0
- sqlspec/adapters/asyncpg/events/backend.py +210 -0
- sqlspec/adapters/asyncpg/events/store.py +40 -0
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +251 -0
- sqlspec/adapters/bigquery/__init__.py +15 -0
- sqlspec/adapters/bigquery/_typing.py +108 -0
- sqlspec/adapters/bigquery/config.py +362 -0
- sqlspec/adapters/bigquery/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/bigquery/core.py +768 -0
- sqlspec/adapters/bigquery/data_dictionary.py +120 -0
- sqlspec/adapters/bigquery/driver.py +542 -0
- sqlspec/adapters/bigquery/events/__init__.py +5 -0
- sqlspec/adapters/bigquery/events/store.py +139 -0
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +325 -0
- sqlspec/adapters/bigquery/type_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/bigquery/type_converter.py +107 -0
- sqlspec/adapters/cockroach_asyncpg/__init__.py +26 -0
- sqlspec/adapters/cockroach_asyncpg/_typing.py +73 -0
- sqlspec/adapters/cockroach_asyncpg/adk/__init__.py +3 -0
- sqlspec/adapters/cockroach_asyncpg/adk/store.py +465 -0
- sqlspec/adapters/cockroach_asyncpg/config.py +248 -0
- sqlspec/adapters/cockroach_asyncpg/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/cockroach_asyncpg/core.py +55 -0
- sqlspec/adapters/cockroach_asyncpg/data_dictionary.py +110 -0
- sqlspec/adapters/cockroach_asyncpg/driver.py +142 -0
- sqlspec/adapters/cockroach_asyncpg/events/__init__.py +3 -0
- sqlspec/adapters/cockroach_asyncpg/events/store.py +20 -0
- sqlspec/adapters/cockroach_asyncpg/litestar/__init__.py +3 -0
- sqlspec/adapters/cockroach_asyncpg/litestar/store.py +142 -0
- sqlspec/adapters/cockroach_psycopg/__init__.py +39 -0
- sqlspec/adapters/cockroach_psycopg/_typing.py +137 -0
- sqlspec/adapters/cockroach_psycopg/adk/__init__.py +13 -0
- sqlspec/adapters/cockroach_psycopg/adk/store.py +1039 -0
- sqlspec/adapters/cockroach_psycopg/config.py +511 -0
- sqlspec/adapters/cockroach_psycopg/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/cockroach_psycopg/core.py +63 -0
- sqlspec/adapters/cockroach_psycopg/data_dictionary.py +220 -0
- sqlspec/adapters/cockroach_psycopg/driver.py +273 -0
- sqlspec/adapters/cockroach_psycopg/events/__init__.py +6 -0
- sqlspec/adapters/cockroach_psycopg/events/store.py +34 -0
- sqlspec/adapters/cockroach_psycopg/litestar/__init__.py +3 -0
- sqlspec/adapters/cockroach_psycopg/litestar/store.py +327 -0
- sqlspec/adapters/duckdb/__init__.py +29 -0
- sqlspec/adapters/duckdb/_typing.py +104 -0
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +935 -0
- sqlspec/adapters/duckdb/config.py +386 -0
- sqlspec/adapters/duckdb/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/duckdb/core.py +332 -0
- sqlspec/adapters/duckdb/data_dictionary.py +140 -0
- sqlspec/adapters/duckdb/driver.py +426 -0
- sqlspec/adapters/duckdb/events/__init__.py +5 -0
- sqlspec/adapters/duckdb/events/store.py +57 -0
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +330 -0
- sqlspec/adapters/duckdb/pool.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/duckdb/pool.py +350 -0
- sqlspec/adapters/duckdb/type_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/duckdb/type_converter.py +118 -0
- sqlspec/adapters/mysqlconnector/__init__.py +39 -0
- sqlspec/adapters/mysqlconnector/_typing.py +186 -0
- sqlspec/adapters/mysqlconnector/adk/__init__.py +15 -0
- sqlspec/adapters/mysqlconnector/adk/store.py +1183 -0
- sqlspec/adapters/mysqlconnector/config.py +421 -0
- sqlspec/adapters/mysqlconnector/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/mysqlconnector/core.py +472 -0
- sqlspec/adapters/mysqlconnector/data_dictionary.py +230 -0
- sqlspec/adapters/mysqlconnector/driver.py +516 -0
- sqlspec/adapters/mysqlconnector/events/__init__.py +8 -0
- sqlspec/adapters/mysqlconnector/events/store.py +98 -0
- sqlspec/adapters/mysqlconnector/litestar/__init__.py +5 -0
- sqlspec/adapters/mysqlconnector/litestar/store.py +426 -0
- sqlspec/adapters/oracledb/__init__.py +39 -0
- sqlspec/adapters/oracledb/_json_handlers.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/oracledb/_json_handlers.py +196 -0
- sqlspec/adapters/oracledb/_param_types.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/oracledb/_param_types.py +46 -0
- sqlspec/adapters/oracledb/_typing.py +258 -0
- sqlspec/adapters/oracledb/_uuid_handlers.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/oracledb/_uuid_handlers.py +163 -0
- sqlspec/adapters/oracledb/_vector_handlers.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/oracledb/_vector_handlers.py +228 -0
- sqlspec/adapters/oracledb/adk/__init__.py +21 -0
- sqlspec/adapters/oracledb/adk/store.py +2453 -0
- sqlspec/adapters/oracledb/config.py +575 -0
- sqlspec/adapters/oracledb/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/oracledb/core.py +820 -0
- sqlspec/adapters/oracledb/data_dictionary.py +404 -0
- sqlspec/adapters/oracledb/driver.py +1277 -0
- sqlspec/adapters/oracledb/events/__init__.py +16 -0
- sqlspec/adapters/oracledb/events/_hub.py +345 -0
- sqlspec/adapters/oracledb/events/backend.py +300 -0
- sqlspec/adapters/oracledb/events/store.py +420 -0
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +781 -0
- sqlspec/adapters/oracledb/migrations.py +539 -0
- sqlspec/adapters/oracledb/type_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/oracledb/type_converter.py +211 -0
- sqlspec/adapters/psqlpy/__init__.py +18 -0
- sqlspec/adapters/psqlpy/_typing.py +121 -0
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +591 -0
- sqlspec/adapters/psqlpy/config.py +376 -0
- sqlspec/adapters/psqlpy/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/psqlpy/core.py +694 -0
- sqlspec/adapters/psqlpy/data_dictionary.py +121 -0
- sqlspec/adapters/psqlpy/driver.py +411 -0
- sqlspec/adapters/psqlpy/events/__init__.py +6 -0
- sqlspec/adapters/psqlpy/events/_hub.py +204 -0
- sqlspec/adapters/psqlpy/events/backend.py +210 -0
- sqlspec/adapters/psqlpy/events/store.py +20 -0
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +270 -0
- sqlspec/adapters/psqlpy/type_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/psqlpy/type_converter.py +113 -0
- sqlspec/adapters/psycopg/__init__.py +38 -0
- sqlspec/adapters/psycopg/_typing.py +218 -0
- sqlspec/adapters/psycopg/adk/__init__.py +10 -0
- sqlspec/adapters/psycopg/adk/store.py +1106 -0
- sqlspec/adapters/psycopg/config.py +695 -0
- sqlspec/adapters/psycopg/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/psycopg/core.py +520 -0
- sqlspec/adapters/psycopg/data_dictionary.py +278 -0
- sqlspec/adapters/psycopg/driver.py +1033 -0
- sqlspec/adapters/psycopg/events/__init__.py +20 -0
- sqlspec/adapters/psycopg/events/_hub.py +388 -0
- sqlspec/adapters/psycopg/events/backend.py +398 -0
- sqlspec/adapters/psycopg/events/store.py +42 -0
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/psycopg/type_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/psycopg/type_converter.py +93 -0
- sqlspec/adapters/pymysql/__init__.py +21 -0
- sqlspec/adapters/pymysql/_typing.py +92 -0
- sqlspec/adapters/pymysql/adk/__init__.py +5 -0
- sqlspec/adapters/pymysql/adk/store.py +657 -0
- sqlspec/adapters/pymysql/config.py +176 -0
- sqlspec/adapters/pymysql/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/pymysql/core.py +469 -0
- sqlspec/adapters/pymysql/data_dictionary.py +120 -0
- sqlspec/adapters/pymysql/driver.py +271 -0
- sqlspec/adapters/pymysql/events/__init__.py +5 -0
- sqlspec/adapters/pymysql/events/store.py +50 -0
- sqlspec/adapters/pymysql/litestar/__init__.py +5 -0
- sqlspec/adapters/pymysql/litestar/store.py +232 -0
- sqlspec/adapters/pymysql/pool.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/pymysql/pool.py +184 -0
- sqlspec/adapters/spanner/__init__.py +33 -0
- sqlspec/adapters/spanner/_typing.py +102 -0
- sqlspec/adapters/spanner/adk/__init__.py +5 -0
- sqlspec/adapters/spanner/adk/store.py +758 -0
- sqlspec/adapters/spanner/config.py +355 -0
- sqlspec/adapters/spanner/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/spanner/core.py +263 -0
- sqlspec/adapters/spanner/data_dictionary.py +120 -0
- sqlspec/adapters/spanner/driver.py +407 -0
- sqlspec/adapters/spanner/events/__init__.py +5 -0
- sqlspec/adapters/spanner/events/store.py +187 -0
- sqlspec/adapters/spanner/litestar/__init__.py +5 -0
- sqlspec/adapters/spanner/litestar/store.py +291 -0
- sqlspec/adapters/spanner/type_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/spanner/type_converter.py +342 -0
- sqlspec/adapters/sqlite/__init__.py +19 -0
- sqlspec/adapters/sqlite/_typing.py +123 -0
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +992 -0
- sqlspec/adapters/sqlite/config.py +240 -0
- sqlspec/adapters/sqlite/core.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/sqlite/core.py +357 -0
- sqlspec/adapters/sqlite/data_dictionary.py +198 -0
- sqlspec/adapters/sqlite/driver.py +527 -0
- sqlspec/adapters/sqlite/events/__init__.py +5 -0
- sqlspec/adapters/sqlite/events/store.py +20 -0
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +316 -0
- sqlspec/adapters/sqlite/pool.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/sqlite/pool.py +237 -0
- sqlspec/adapters/sqlite/type_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/adapters/sqlite/type_converter.py +114 -0
- sqlspec/base.py +832 -0
- sqlspec/builder/__init__.py +181 -0
- sqlspec/builder/_base.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_base.py +1071 -0
- sqlspec/builder/_column.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_column.py +521 -0
- sqlspec/builder/_ddl.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_ddl.py +1691 -0
- sqlspec/builder/_delete.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_delete.py +95 -0
- sqlspec/builder/_dml.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_dml.py +386 -0
- sqlspec/builder/_explain.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_explain.py +579 -0
- sqlspec/builder/_expression_wrappers.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_expression_wrappers.py +46 -0
- sqlspec/builder/_factory.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_factory.py +1884 -0
- sqlspec/builder/_insert.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_insert.py +405 -0
- sqlspec/builder/_join.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_join.py +489 -0
- sqlspec/builder/_merge.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_merge.py +823 -0
- sqlspec/builder/_parsing_utils.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_parsing_utils.py +295 -0
- sqlspec/builder/_select.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_select.py +1666 -0
- sqlspec/builder/_temporal.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_temporal.py +167 -0
- sqlspec/builder/_update.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_update.py +173 -0
- sqlspec/builder/_vector_distance.cp314-win_amd64.pyd +0 -0
- sqlspec/builder/_vector_distance.py +330 -0
- sqlspec/cli.py +1095 -0
- sqlspec/config.py +2383 -0
- sqlspec/core/__init__.py +372 -0
- sqlspec/core/_correlation.cp314-win_amd64.pyd +0 -0
- sqlspec/core/_correlation.py +176 -0
- sqlspec/core/_pagination.py +42 -0
- sqlspec/core/_pool.cp314-win_amd64.pyd +0 -0
- sqlspec/core/_pool.py +76 -0
- sqlspec/core/cache.cp314-win_amd64.pyd +0 -0
- sqlspec/core/cache.py +1085 -0
- sqlspec/core/compiler.cp314-win_amd64.pyd +0 -0
- sqlspec/core/compiler.py +1090 -0
- sqlspec/core/config_runtime.cp314-win_amd64.pyd +0 -0
- sqlspec/core/config_runtime.py +174 -0
- sqlspec/core/explain.cp314-win_amd64.pyd +0 -0
- sqlspec/core/explain.py +275 -0
- sqlspec/core/filters.cp314-win_amd64.pyd +0 -0
- sqlspec/core/filters.py +969 -0
- sqlspec/core/hashing.cp314-win_amd64.pyd +0 -0
- sqlspec/core/hashing.py +266 -0
- sqlspec/core/metrics.cp314-win_amd64.pyd +0 -0
- sqlspec/core/metrics.py +83 -0
- sqlspec/core/parameters/__init__.py +72 -0
- sqlspec/core/parameters/_alignment.cp314-win_amd64.pyd +0 -0
- sqlspec/core/parameters/_alignment.py +283 -0
- sqlspec/core/parameters/_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/core/parameters/_converter.py +554 -0
- sqlspec/core/parameters/_processor.cp314-win_amd64.pyd +0 -0
- sqlspec/core/parameters/_processor.py +1182 -0
- sqlspec/core/parameters/_registry.cp314-win_amd64.pyd +0 -0
- sqlspec/core/parameters/_registry.py +206 -0
- sqlspec/core/parameters/_transformers.cp314-win_amd64.pyd +0 -0
- sqlspec/core/parameters/_transformers.py +324 -0
- sqlspec/core/parameters/_types.cp314-win_amd64.pyd +0 -0
- sqlspec/core/parameters/_types.py +536 -0
- sqlspec/core/parameters/_validator.cp314-win_amd64.pyd +0 -0
- sqlspec/core/parameters/_validator.py +171 -0
- sqlspec/core/pipeline.cp314-win_amd64.pyd +0 -0
- sqlspec/core/pipeline.py +333 -0
- sqlspec/core/query_modifiers.cp314-win_amd64.pyd +0 -0
- sqlspec/core/query_modifiers.py +508 -0
- sqlspec/core/result/__init__.py +25 -0
- sqlspec/core/result/_base.cp314-win_amd64.pyd +0 -0
- sqlspec/core/result/_base.py +1232 -0
- sqlspec/core/result/_io.cp314-win_amd64.pyd +0 -0
- sqlspec/core/result/_io.py +28 -0
- sqlspec/core/splitter.cp314-win_amd64.pyd +0 -0
- sqlspec/core/splitter.py +1021 -0
- sqlspec/core/sqlcommenter.cp314-win_amd64.pyd +0 -0
- sqlspec/core/sqlcommenter.py +249 -0
- sqlspec/core/stack.cp314-win_amd64.pyd +0 -0
- sqlspec/core/stack.py +163 -0
- sqlspec/core/statement.cp314-win_amd64.pyd +0 -0
- sqlspec/core/statement.py +1865 -0
- sqlspec/core/type_converter.cp314-win_amd64.pyd +0 -0
- sqlspec/core/type_converter.py +340 -0
- sqlspec/data_dictionary/__init__.py +22 -0
- sqlspec/data_dictionary/_loader.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/_loader.py +138 -0
- sqlspec/data_dictionary/_registry.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/_registry.py +74 -0
- sqlspec/data_dictionary/_types.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/_types.py +121 -0
- sqlspec/data_dictionary/dialects/__init__.py +21 -0
- sqlspec/data_dictionary/dialects/bigquery.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/dialects/bigquery.py +81 -0
- sqlspec/data_dictionary/dialects/cockroachdb.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/dialects/cockroachdb.py +54 -0
- sqlspec/data_dictionary/dialects/duckdb.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/dialects/duckdb.py +47 -0
- sqlspec/data_dictionary/dialects/mysql.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/dialects/mysql.py +53 -0
- sqlspec/data_dictionary/dialects/oracle.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/dialects/oracle.py +197 -0
- sqlspec/data_dictionary/dialects/postgres.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/dialects/postgres.py +69 -0
- sqlspec/data_dictionary/dialects/spanner.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/dialects/spanner.py +37 -0
- sqlspec/data_dictionary/dialects/sqlite.cp314-win_amd64.pyd +0 -0
- sqlspec/data_dictionary/dialects/sqlite.py +59 -0
- sqlspec/data_dictionary/sql/.gitkeep +0 -0
- sqlspec/data_dictionary/sql/bigquery/columns.sql +23 -0
- sqlspec/data_dictionary/sql/bigquery/foreign_keys.sql +34 -0
- sqlspec/data_dictionary/sql/bigquery/indexes.sql +19 -0
- sqlspec/data_dictionary/sql/bigquery/tables.sql +33 -0
- sqlspec/data_dictionary/sql/bigquery/version.sql +3 -0
- sqlspec/data_dictionary/sql/cockroachdb/columns.sql +34 -0
- sqlspec/data_dictionary/sql/cockroachdb/foreign_keys.sql +40 -0
- sqlspec/data_dictionary/sql/cockroachdb/indexes.sql +32 -0
- sqlspec/data_dictionary/sql/cockroachdb/tables.sql +44 -0
- sqlspec/data_dictionary/sql/cockroachdb/version.sql +3 -0
- sqlspec/data_dictionary/sql/duckdb/columns.sql +23 -0
- sqlspec/data_dictionary/sql/duckdb/foreign_keys.sql +36 -0
- sqlspec/data_dictionary/sql/duckdb/indexes.sql +19 -0
- sqlspec/data_dictionary/sql/duckdb/tables.sql +38 -0
- sqlspec/data_dictionary/sql/duckdb/version.sql +3 -0
- sqlspec/data_dictionary/sql/mysql/columns.sql +23 -0
- sqlspec/data_dictionary/sql/mysql/foreign_keys.sql +28 -0
- sqlspec/data_dictionary/sql/mysql/indexes.sql +26 -0
- sqlspec/data_dictionary/sql/mysql/tables.sql +33 -0
- sqlspec/data_dictionary/sql/mysql/version.sql +3 -0
- sqlspec/data_dictionary/sql/oracle/columns.sql +23 -0
- sqlspec/data_dictionary/sql/oracle/foreign_keys.sql +48 -0
- sqlspec/data_dictionary/sql/oracle/indexes.sql +44 -0
- sqlspec/data_dictionary/sql/oracle/tables.sql +25 -0
- sqlspec/data_dictionary/sql/oracle/version.sql +20 -0
- sqlspec/data_dictionary/sql/postgres/columns.sql +34 -0
- sqlspec/data_dictionary/sql/postgres/foreign_keys.sql +40 -0
- sqlspec/data_dictionary/sql/postgres/indexes.sql +56 -0
- sqlspec/data_dictionary/sql/postgres/tables.sql +44 -0
- sqlspec/data_dictionary/sql/postgres/version.sql +3 -0
- sqlspec/data_dictionary/sql/spanner/columns.sql +23 -0
- sqlspec/data_dictionary/sql/spanner/foreign_keys.sql +70 -0
- sqlspec/data_dictionary/sql/spanner/indexes.sql +30 -0
- sqlspec/data_dictionary/sql/spanner/tables.sql +9 -0
- sqlspec/data_dictionary/sql/spanner/version.sql +3 -0
- sqlspec/data_dictionary/sql/sqlite/columns.sql +23 -0
- sqlspec/data_dictionary/sql/sqlite/foreign_keys.sql +22 -0
- sqlspec/data_dictionary/sql/sqlite/indexes.sql +7 -0
- sqlspec/data_dictionary/sql/sqlite/tables.sql +28 -0
- sqlspec/data_dictionary/sql/sqlite/version.sql +3 -0
- sqlspec/dialects/__init__.py +22 -0
- sqlspec/dialects/_compat.cp314-win_amd64.pyd +0 -0
- sqlspec/dialects/_compat.py +14 -0
- sqlspec/dialects/postgres/__init__.py +9 -0
- sqlspec/dialects/postgres/_generators.cp314-win_amd64.pyd +0 -0
- sqlspec/dialects/postgres/_generators.py +57 -0
- sqlspec/dialects/postgres/_operators.cp314-win_amd64.pyd +0 -0
- sqlspec/dialects/postgres/_operators.py +81 -0
- sqlspec/dialects/postgres/_paradedb.py +50 -0
- sqlspec/dialects/postgres/_pgvector.py +36 -0
- sqlspec/dialects/spanner/__init__.py +6 -0
- sqlspec/dialects/spanner/_generators.cp314-win_amd64.pyd +0 -0
- sqlspec/dialects/spanner/_generators.py +206 -0
- sqlspec/dialects/spanner/_spangres.py +77 -0
- sqlspec/dialects/spanner/_spanner.py +179 -0
- sqlspec/driver/__init__.py +49 -0
- sqlspec/driver/_async.cp314-win_amd64.pyd +0 -0
- sqlspec/driver/_async.py +1830 -0
- sqlspec/driver/_common.cp314-win_amd64.pyd +0 -0
- sqlspec/driver/_common.py +2292 -0
- sqlspec/driver/_exception_handler.cp314-win_amd64.pyd +0 -0
- sqlspec/driver/_exception_handler.py +108 -0
- sqlspec/driver/_query_cache.cp314-win_amd64.pyd +0 -0
- sqlspec/driver/_query_cache.py +96 -0
- sqlspec/driver/_sql_helpers.cp314-win_amd64.pyd +0 -0
- sqlspec/driver/_sql_helpers.py +139 -0
- sqlspec/driver/_storage_helpers.cp314-win_amd64.pyd +0 -0
- sqlspec/driver/_storage_helpers.py +153 -0
- sqlspec/driver/_sync.cp314-win_amd64.pyd +0 -0
- sqlspec/driver/_sync.py +1817 -0
- sqlspec/exceptions.cp314-win_amd64.pyd +0 -0
- sqlspec/exceptions.py +480 -0
- sqlspec/extensions/__init__.py +0 -0
- sqlspec/extensions/adk/__init__.py +84 -0
- sqlspec/extensions/adk/_config_utils.py +199 -0
- sqlspec/extensions/adk/_types.cp314-win_amd64.pyd +0 -0
- sqlspec/extensions/adk/_types.py +41 -0
- sqlspec/extensions/adk/artifact/__init__.py +57 -0
- sqlspec/extensions/adk/artifact/_types.cp314-win_amd64.pyd +0 -0
- sqlspec/extensions/adk/artifact/_types.py +32 -0
- sqlspec/extensions/adk/artifact/service.py +508 -0
- sqlspec/extensions/adk/artifact/store.py +361 -0
- sqlspec/extensions/adk/converters.py +212 -0
- sqlspec/extensions/adk/memory/__init__.py +69 -0
- sqlspec/extensions/adk/memory/_types.cp314-win_amd64.pyd +0 -0
- sqlspec/extensions/adk/memory/_types.py +30 -0
- sqlspec/extensions/adk/memory/converters.py +225 -0
- sqlspec/extensions/adk/memory/service.py +316 -0
- sqlspec/extensions/adk/memory/store.py +525 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +184 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +279 -0
- sqlspec/extensions/adk/store.py +590 -0
- sqlspec/extensions/events/__init__.py +51 -0
- sqlspec/extensions/events/_channel.py +703 -0
- sqlspec/extensions/events/_hints.cp314-win_amd64.pyd +0 -0
- sqlspec/extensions/events/_hints.py +45 -0
- sqlspec/extensions/events/_models.py +23 -0
- sqlspec/extensions/events/_payload.cp314-win_amd64.pyd +0 -0
- sqlspec/extensions/events/_payload.py +69 -0
- sqlspec/extensions/events/_protocols.py +134 -0
- sqlspec/extensions/events/_queue.py +462 -0
- sqlspec/extensions/events/_store.py +209 -0
- sqlspec/extensions/events/migrations/0001_create_event_queue.py +59 -0
- sqlspec/extensions/events/migrations/__init__.py +3 -0
- sqlspec/extensions/fastapi/__init__.py +22 -0
- sqlspec/extensions/fastapi/extension.py +391 -0
- sqlspec/extensions/fastapi/providers.cp314-win_amd64.pyd +0 -0
- sqlspec/extensions/fastapi/providers.py +712 -0
- sqlspec/extensions/flask/__init__.py +38 -0
- sqlspec/extensions/flask/_state.py +87 -0
- sqlspec/extensions/flask/_utils.py +71 -0
- sqlspec/extensions/flask/extension.py +539 -0
- sqlspec/extensions/litestar/__init__.py +31 -0
- sqlspec/extensions/litestar/_utils.py +52 -0
- sqlspec/extensions/litestar/channels.py +165 -0
- sqlspec/extensions/litestar/cli.py +102 -0
- sqlspec/extensions/litestar/config.py +90 -0
- sqlspec/extensions/litestar/handlers.py +316 -0
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +1066 -0
- sqlspec/extensions/litestar/providers.cp314-win_amd64.pyd +0 -0
- sqlspec/extensions/litestar/providers.py +784 -0
- sqlspec/extensions/litestar/store.py +298 -0
- sqlspec/extensions/otel/__init__.py +58 -0
- sqlspec/extensions/prometheus/__init__.py +113 -0
- sqlspec/extensions/sanic/__init__.py +19 -0
- sqlspec/extensions/sanic/_state.py +43 -0
- sqlspec/extensions/sanic/_utils.py +127 -0
- sqlspec/extensions/sanic/extension.py +647 -0
- sqlspec/extensions/starlette/__init__.py +22 -0
- sqlspec/extensions/starlette/_state.py +42 -0
- sqlspec/extensions/starlette/_utils.py +96 -0
- sqlspec/extensions/starlette/extension.py +374 -0
- sqlspec/extensions/starlette/middleware.py +281 -0
- sqlspec/loader.cp314-win_amd64.pyd +0 -0
- sqlspec/loader.py +727 -0
- sqlspec/migrations/__init__.py +39 -0
- sqlspec/migrations/base.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/base.py +862 -0
- sqlspec/migrations/commands.py +2151 -0
- sqlspec/migrations/context.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/context.py +157 -0
- sqlspec/migrations/fix.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/fix.py +204 -0
- sqlspec/migrations/loaders.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/loaders.py +443 -0
- sqlspec/migrations/runner.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/runner.py +1195 -0
- sqlspec/migrations/squash.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/squash.py +490 -0
- sqlspec/migrations/templates.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/templates.py +234 -0
- sqlspec/migrations/tracker.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/tracker.py +792 -0
- sqlspec/migrations/utils.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/utils.py +256 -0
- sqlspec/migrations/validation.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/validation.py +359 -0
- sqlspec/migrations/version.cp314-win_amd64.pyd +0 -0
- sqlspec/migrations/version.py +446 -0
- sqlspec/observability/__init__.py +57 -0
- sqlspec/observability/_common.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_common.py +77 -0
- sqlspec/observability/_config.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_config.py +364 -0
- sqlspec/observability/_diagnostics.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_diagnostics.py +74 -0
- sqlspec/observability/_dispatcher.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_dispatcher.py +200 -0
- sqlspec/observability/_formatters/__init__.py +13 -0
- sqlspec/observability/_formatters/_aws.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_formatters/_aws.py +102 -0
- sqlspec/observability/_formatters/_azure.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_formatters/_azure.py +96 -0
- sqlspec/observability/_formatters/_base.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_formatters/_base.py +57 -0
- sqlspec/observability/_formatters/_gcp.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_formatters/_gcp.py +131 -0
- sqlspec/observability/_formatting.py +58 -0
- sqlspec/observability/_observer.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_observer.py +361 -0
- sqlspec/observability/_runtime.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_runtime.py +461 -0
- sqlspec/observability/_sampling.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_sampling.py +188 -0
- sqlspec/observability/_spans.cp314-win_amd64.pyd +0 -0
- sqlspec/observability/_spans.py +161 -0
- sqlspec/protocols.py +955 -0
- sqlspec/py.typed +0 -0
- sqlspec/service.py +433 -0
- sqlspec/storage/__init__.py +48 -0
- sqlspec/storage/_arrow_payload.py +68 -0
- sqlspec/storage/_paths.cp314-win_amd64.pyd +0 -0
- sqlspec/storage/_paths.py +58 -0
- sqlspec/storage/_utils.py +46 -0
- sqlspec/storage/backends/__init__.py +1 -0
- sqlspec/storage/backends/base.cp314-win_amd64.pyd +0 -0
- sqlspec/storage/backends/base.py +374 -0
- sqlspec/storage/backends/fsspec.py +574 -0
- sqlspec/storage/backends/local.py +468 -0
- sqlspec/storage/backends/obstore.py +956 -0
- sqlspec/storage/errors.cp314-win_amd64.pyd +0 -0
- sqlspec/storage/errors.py +102 -0
- sqlspec/storage/pipeline.cp314-win_amd64.pyd +0 -0
- sqlspec/storage/pipeline.py +628 -0
- sqlspec/storage/registry.cp314-win_amd64.pyd +0 -0
- sqlspec/storage/registry.py +329 -0
- sqlspec/typing.py +405 -0
- sqlspec/utils/__init__.py +7 -0
- sqlspec/utils/arrow_helpers.py +384 -0
- sqlspec/utils/config_tools.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/config_tools.py +314 -0
- sqlspec/utils/correlation.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/correlation.py +134 -0
- sqlspec/utils/deprecation.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/deprecation.py +157 -0
- sqlspec/utils/dispatch.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/dispatch.py +101 -0
- sqlspec/utils/fixtures.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/fixtures.py +260 -0
- sqlspec/utils/logging.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/logging.py +251 -0
- sqlspec/utils/module_loader.py +306 -0
- sqlspec/utils/portal.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/portal.py +377 -0
- sqlspec/utils/schema.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/schema.py +1040 -0
- sqlspec/utils/serializers/__init__.py +30 -0
- sqlspec/utils/serializers/_json.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/serializers/_json.py +415 -0
- sqlspec/utils/serializers/_numpy.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/serializers/_numpy.py +65 -0
- sqlspec/utils/serializers/_schema.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/serializers/_schema.py +285 -0
- sqlspec/utils/singleton.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/singleton.py +41 -0
- sqlspec/utils/sync_tools.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/sync_tools.py +316 -0
- sqlspec/utils/text.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/text.py +109 -0
- sqlspec/utils/type_converters.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/type_converters.py +216 -0
- sqlspec/utils/type_guards.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/type_guards.py +1508 -0
- sqlspec/utils/uuids.cp314-win_amd64.pyd +0 -0
- sqlspec/utils/uuids.py +241 -0
- sqlspec-0.47.0.dist-info/METADATA +202 -0
- sqlspec-0.47.0.dist-info/RECORD +621 -0
- sqlspec-0.47.0.dist-info/WHEEL +4 -0
- sqlspec-0.47.0.dist-info/entry_points.txt +6 -0
- 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)
|