sqlspec 0.36.0__cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ac8f31065839703b4e70__mypyc.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/__init__.py +140 -0
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +14 -0
- sqlspec/_serialization.py +315 -0
- sqlspec/_typing.py +700 -0
- sqlspec/adapters/__init__.py +0 -0
- sqlspec/adapters/adbc/__init__.py +5 -0
- sqlspec/adapters/adbc/_typing.py +82 -0
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +1273 -0
- sqlspec/adapters/adbc/config.py +295 -0
- sqlspec/adapters/adbc/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/adbc/core.py +735 -0
- sqlspec/adapters/adbc/data_dictionary.py +334 -0
- sqlspec/adapters/adbc/driver.py +529 -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 +502 -0
- sqlspec/adapters/adbc/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/adbc/type_converter.py +140 -0
- sqlspec/adapters/aiosqlite/__init__.py +25 -0
- sqlspec/adapters/aiosqlite/_typing.py +82 -0
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +818 -0
- sqlspec/adapters/aiosqlite/config.py +334 -0
- sqlspec/adapters/aiosqlite/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/aiosqlite/core.py +315 -0
- sqlspec/adapters/aiosqlite/data_dictionary.py +208 -0
- sqlspec/adapters/aiosqlite/driver.py +313 -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.py +533 -0
- sqlspec/adapters/asyncmy/__init__.py +21 -0
- sqlspec/adapters/asyncmy/_typing.py +87 -0
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +703 -0
- sqlspec/adapters/asyncmy/config.py +302 -0
- sqlspec/adapters/asyncmy/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/asyncmy/core.py +360 -0
- sqlspec/adapters/asyncmy/data_dictionary.py +124 -0
- sqlspec/adapters/asyncmy/driver.py +383 -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 +19 -0
- sqlspec/adapters/asyncpg/_typing.py +88 -0
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +748 -0
- sqlspec/adapters/asyncpg/config.py +569 -0
- sqlspec/adapters/asyncpg/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/asyncpg/core.py +367 -0
- sqlspec/adapters/asyncpg/data_dictionary.py +162 -0
- sqlspec/adapters/asyncpg/driver.py +487 -0
- sqlspec/adapters/asyncpg/events/__init__.py +6 -0
- sqlspec/adapters/asyncpg/events/backend.py +286 -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 +14 -0
- sqlspec/adapters/bigquery/_typing.py +86 -0
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +827 -0
- sqlspec/adapters/bigquery/config.py +353 -0
- sqlspec/adapters/bigquery/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/bigquery/core.py +715 -0
- sqlspec/adapters/bigquery/data_dictionary.py +128 -0
- sqlspec/adapters/bigquery/driver.py +548 -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.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/bigquery/type_converter.py +107 -0
- sqlspec/adapters/cockroach_asyncpg/__init__.py +24 -0
- sqlspec/adapters/cockroach_asyncpg/_typing.py +72 -0
- sqlspec/adapters/cockroach_asyncpg/adk/__init__.py +3 -0
- sqlspec/adapters/cockroach_asyncpg/adk/store.py +410 -0
- sqlspec/adapters/cockroach_asyncpg/config.py +238 -0
- sqlspec/adapters/cockroach_asyncpg/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/cockroach_asyncpg/core.py +55 -0
- sqlspec/adapters/cockroach_asyncpg/data_dictionary.py +107 -0
- sqlspec/adapters/cockroach_asyncpg/driver.py +144 -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 +38 -0
- sqlspec/adapters/cockroach_psycopg/_typing.py +129 -0
- sqlspec/adapters/cockroach_psycopg/adk/__init__.py +13 -0
- sqlspec/adapters/cockroach_psycopg/adk/store.py +868 -0
- sqlspec/adapters/cockroach_psycopg/config.py +484 -0
- sqlspec/adapters/cockroach_psycopg/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/cockroach_psycopg/core.py +63 -0
- sqlspec/adapters/cockroach_psycopg/data_dictionary.py +215 -0
- sqlspec/adapters/cockroach_psycopg/driver.py +284 -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 +325 -0
- sqlspec/adapters/duckdb/__init__.py +25 -0
- sqlspec/adapters/duckdb/_typing.py +81 -0
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +850 -0
- sqlspec/adapters/duckdb/config.py +463 -0
- sqlspec/adapters/duckdb/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/duckdb/core.py +257 -0
- sqlspec/adapters/duckdb/data_dictionary.py +140 -0
- sqlspec/adapters/duckdb/driver.py +430 -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.py +293 -0
- sqlspec/adapters/duckdb/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/duckdb/type_converter.py +118 -0
- sqlspec/adapters/mock/__init__.py +72 -0
- sqlspec/adapters/mock/_typing.py +147 -0
- sqlspec/adapters/mock/config.py +483 -0
- sqlspec/adapters/mock/core.py +319 -0
- sqlspec/adapters/mock/data_dictionary.py +366 -0
- sqlspec/adapters/mock/driver.py +721 -0
- sqlspec/adapters/mysqlconnector/__init__.py +36 -0
- sqlspec/adapters/mysqlconnector/_typing.py +141 -0
- sqlspec/adapters/mysqlconnector/adk/__init__.py +15 -0
- sqlspec/adapters/mysqlconnector/adk/store.py +1060 -0
- sqlspec/adapters/mysqlconnector/config.py +394 -0
- sqlspec/adapters/mysqlconnector/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/mysqlconnector/core.py +303 -0
- sqlspec/adapters/mysqlconnector/data_dictionary.py +235 -0
- sqlspec/adapters/mysqlconnector/driver.py +483 -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 +60 -0
- sqlspec/adapters/oracledb/_numpy_handlers.py +141 -0
- sqlspec/adapters/oracledb/_typing.py +182 -0
- sqlspec/adapters/oracledb/_uuid_handlers.py +166 -0
- sqlspec/adapters/oracledb/adk/__init__.py +10 -0
- sqlspec/adapters/oracledb/adk/store.py +2369 -0
- sqlspec/adapters/oracledb/config.py +550 -0
- sqlspec/adapters/oracledb/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/oracledb/core.py +543 -0
- sqlspec/adapters/oracledb/data_dictionary.py +536 -0
- sqlspec/adapters/oracledb/driver.py +1229 -0
- sqlspec/adapters/oracledb/events/__init__.py +16 -0
- sqlspec/adapters/oracledb/events/backend.py +347 -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 +535 -0
- sqlspec/adapters/oracledb/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/oracledb/type_converter.py +211 -0
- sqlspec/adapters/psqlpy/__init__.py +17 -0
- sqlspec/adapters/psqlpy/_typing.py +79 -0
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +766 -0
- sqlspec/adapters/psqlpy/config.py +304 -0
- sqlspec/adapters/psqlpy/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/psqlpy/core.py +480 -0
- sqlspec/adapters/psqlpy/data_dictionary.py +126 -0
- sqlspec/adapters/psqlpy/driver.py +438 -0
- sqlspec/adapters/psqlpy/events/__init__.py +6 -0
- sqlspec/adapters/psqlpy/events/backend.py +310 -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.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/psqlpy/type_converter.py +113 -0
- sqlspec/adapters/psycopg/__init__.py +32 -0
- sqlspec/adapters/psycopg/_typing.py +164 -0
- sqlspec/adapters/psycopg/adk/__init__.py +10 -0
- sqlspec/adapters/psycopg/adk/store.py +1387 -0
- sqlspec/adapters/psycopg/config.py +576 -0
- sqlspec/adapters/psycopg/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/psycopg/core.py +450 -0
- sqlspec/adapters/psycopg/data_dictionary.py +289 -0
- sqlspec/adapters/psycopg/driver.py +975 -0
- sqlspec/adapters/psycopg/events/__init__.py +20 -0
- sqlspec/adapters/psycopg/events/backend.py +458 -0
- sqlspec/adapters/psycopg/events/store.py +42 -0
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +552 -0
- sqlspec/adapters/psycopg/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/psycopg/type_converter.py +93 -0
- sqlspec/adapters/pymysql/__init__.py +21 -0
- sqlspec/adapters/pymysql/_typing.py +71 -0
- sqlspec/adapters/pymysql/adk/__init__.py +5 -0
- sqlspec/adapters/pymysql/adk/store.py +540 -0
- sqlspec/adapters/pymysql/config.py +195 -0
- sqlspec/adapters/pymysql/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/pymysql/core.py +299 -0
- sqlspec/adapters/pymysql/data_dictionary.py +122 -0
- sqlspec/adapters/pymysql/driver.py +259 -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.py +137 -0
- sqlspec/adapters/spanner/__init__.py +40 -0
- sqlspec/adapters/spanner/_typing.py +86 -0
- sqlspec/adapters/spanner/adk/__init__.py +5 -0
- sqlspec/adapters/spanner/adk/store.py +732 -0
- sqlspec/adapters/spanner/config.py +352 -0
- sqlspec/adapters/spanner/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/spanner/core.py +188 -0
- sqlspec/adapters/spanner/data_dictionary.py +120 -0
- sqlspec/adapters/spanner/dialect/__init__.py +6 -0
- sqlspec/adapters/spanner/dialect/_spangres.py +57 -0
- sqlspec/adapters/spanner/dialect/_spanner.py +130 -0
- sqlspec/adapters/spanner/driver.py +373 -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.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/spanner/type_converter.py +331 -0
- sqlspec/adapters/sqlite/__init__.py +19 -0
- sqlspec/adapters/sqlite/_typing.py +80 -0
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +958 -0
- sqlspec/adapters/sqlite/config.py +280 -0
- sqlspec/adapters/sqlite/core.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/sqlite/core.py +312 -0
- sqlspec/adapters/sqlite/data_dictionary.py +202 -0
- sqlspec/adapters/sqlite/driver.py +359 -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.py +198 -0
- sqlspec/adapters/sqlite/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/adapters/sqlite/type_converter.py +114 -0
- sqlspec/base.py +747 -0
- sqlspec/builder/__init__.py +179 -0
- sqlspec/builder/_base.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_base.py +1022 -0
- sqlspec/builder/_column.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_column.py +521 -0
- sqlspec/builder/_ddl.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_ddl.py +1642 -0
- sqlspec/builder/_delete.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_delete.py +95 -0
- sqlspec/builder/_dml.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_dml.py +365 -0
- sqlspec/builder/_explain.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_explain.py +579 -0
- sqlspec/builder/_expression_wrappers.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_expression_wrappers.py +46 -0
- sqlspec/builder/_factory.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_factory.py +1697 -0
- sqlspec/builder/_insert.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_insert.py +328 -0
- sqlspec/builder/_join.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_join.py +499 -0
- sqlspec/builder/_merge.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_merge.py +821 -0
- sqlspec/builder/_parsing_utils.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_parsing_utils.py +297 -0
- sqlspec/builder/_select.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_select.py +1660 -0
- sqlspec/builder/_temporal.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_temporal.py +139 -0
- sqlspec/builder/_update.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/builder/_update.py +173 -0
- sqlspec/builder/_vector_expressions.py +267 -0
- sqlspec/cli.py +911 -0
- sqlspec/config.py +1755 -0
- sqlspec/core/__init__.py +374 -0
- sqlspec/core/_correlation.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/_correlation.py +176 -0
- sqlspec/core/cache.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/cache.py +1069 -0
- sqlspec/core/compiler.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/compiler.py +954 -0
- sqlspec/core/explain.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/explain.py +275 -0
- sqlspec/core/filters.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/filters.py +952 -0
- sqlspec/core/hashing.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/hashing.py +262 -0
- sqlspec/core/metrics.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/metrics.py +83 -0
- sqlspec/core/parameters/__init__.py +71 -0
- sqlspec/core/parameters/_alignment.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters/_alignment.py +270 -0
- sqlspec/core/parameters/_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters/_converter.py +543 -0
- sqlspec/core/parameters/_processor.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters/_processor.py +505 -0
- sqlspec/core/parameters/_registry.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters/_registry.py +206 -0
- sqlspec/core/parameters/_transformers.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters/_transformers.py +292 -0
- sqlspec/core/parameters/_types.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters/_types.py +499 -0
- sqlspec/core/parameters/_validator.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters/_validator.py +180 -0
- sqlspec/core/pipeline.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/pipeline.py +319 -0
- sqlspec/core/query_modifiers.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/query_modifiers.py +437 -0
- sqlspec/core/result/__init__.py +23 -0
- sqlspec/core/result/_base.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/result/_base.py +1121 -0
- sqlspec/core/result/_io.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/result/_io.py +28 -0
- sqlspec/core/splitter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/splitter.py +966 -0
- sqlspec/core/stack.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/stack.py +163 -0
- sqlspec/core/statement.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/statement.py +1503 -0
- sqlspec/core/type_converter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/type_converter.py +339 -0
- sqlspec/data_dictionary/__init__.py +22 -0
- sqlspec/data_dictionary/_loader.py +123 -0
- sqlspec/data_dictionary/_registry.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/_registry.py +74 -0
- sqlspec/data_dictionary/_types.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/_types.py +121 -0
- sqlspec/data_dictionary/dialects/__init__.py +21 -0
- sqlspec/data_dictionary/dialects/bigquery.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/dialects/bigquery.py +49 -0
- sqlspec/data_dictionary/dialects/cockroachdb.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/dialects/cockroachdb.py +43 -0
- sqlspec/data_dictionary/dialects/duckdb.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/dialects/duckdb.py +47 -0
- sqlspec/data_dictionary/dialects/mysql.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/dialects/mysql.py +42 -0
- sqlspec/data_dictionary/dialects/oracle.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/dialects/oracle.py +34 -0
- sqlspec/data_dictionary/dialects/postgres.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/dialects/postgres.py +46 -0
- sqlspec/data_dictionary/dialects/spanner.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/dialects/spanner.py +37 -0
- sqlspec/data_dictionary/dialects/sqlite.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/data_dictionary/dialects/sqlite.py +42 -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/driver/__init__.py +32 -0
- sqlspec/driver/_async.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/driver/_async.py +1737 -0
- sqlspec/driver/_common.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/driver/_common.py +1478 -0
- sqlspec/driver/_sql_helpers.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/driver/_sql_helpers.py +148 -0
- sqlspec/driver/_storage_helpers.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/driver/_storage_helpers.py +144 -0
- sqlspec/driver/_sync.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/driver/_sync.py +1710 -0
- sqlspec/exceptions.py +338 -0
- sqlspec/extensions/__init__.py +0 -0
- sqlspec/extensions/adk/__init__.py +70 -0
- sqlspec/extensions/adk/_types.py +51 -0
- sqlspec/extensions/adk/converters.py +172 -0
- sqlspec/extensions/adk/memory/__init__.py +69 -0
- sqlspec/extensions/adk/memory/_types.py +30 -0
- sqlspec/extensions/adk/memory/converters.py +149 -0
- sqlspec/extensions/adk/memory/service.py +217 -0
- sqlspec/extensions/adk/memory/store.py +569 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +246 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +225 -0
- sqlspec/extensions/adk/store.py +567 -0
- sqlspec/extensions/events/__init__.py +51 -0
- sqlspec/extensions/events/_channel.py +703 -0
- sqlspec/extensions/events/_hints.py +45 -0
- sqlspec/extensions/events/_models.py +23 -0
- sqlspec/extensions/events/_payload.py +69 -0
- sqlspec/extensions/events/_protocols.py +134 -0
- sqlspec/extensions/events/_queue.py +461 -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 +19 -0
- sqlspec/extensions/fastapi/extension.py +351 -0
- sqlspec/extensions/fastapi/providers.py +607 -0
- sqlspec/extensions/flask/__init__.py +37 -0
- sqlspec/extensions/flask/_state.py +76 -0
- sqlspec/extensions/flask/_utils.py +71 -0
- sqlspec/extensions/flask/extension.py +519 -0
- sqlspec/extensions/litestar/__init__.py +28 -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 +671 -0
- sqlspec/extensions/litestar/providers.py +526 -0
- sqlspec/extensions/litestar/store.py +296 -0
- sqlspec/extensions/otel/__init__.py +58 -0
- sqlspec/extensions/prometheus/__init__.py +113 -0
- sqlspec/extensions/starlette/__init__.py +19 -0
- sqlspec/extensions/starlette/_state.py +30 -0
- sqlspec/extensions/starlette/_utils.py +96 -0
- sqlspec/extensions/starlette/extension.py +346 -0
- sqlspec/extensions/starlette/middleware.py +235 -0
- sqlspec/loader.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/loader.py +702 -0
- sqlspec/migrations/__init__.py +36 -0
- sqlspec/migrations/base.py +731 -0
- sqlspec/migrations/commands.py +1232 -0
- sqlspec/migrations/context.py +157 -0
- sqlspec/migrations/fix.py +204 -0
- sqlspec/migrations/loaders.py +443 -0
- sqlspec/migrations/runner.py +1172 -0
- sqlspec/migrations/templates.py +234 -0
- sqlspec/migrations/tracker.py +611 -0
- sqlspec/migrations/utils.py +256 -0
- sqlspec/migrations/validation.py +207 -0
- sqlspec/migrations/version.py +446 -0
- sqlspec/observability/__init__.py +55 -0
- sqlspec/observability/_common.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_common.py +77 -0
- sqlspec/observability/_config.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_config.py +348 -0
- sqlspec/observability/_diagnostics.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_diagnostics.py +74 -0
- sqlspec/observability/_dispatcher.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_dispatcher.py +152 -0
- sqlspec/observability/_formatters/__init__.py +13 -0
- sqlspec/observability/_formatters/_aws.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_formatters/_aws.py +102 -0
- sqlspec/observability/_formatters/_azure.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_formatters/_azure.py +96 -0
- sqlspec/observability/_formatters/_base.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_formatters/_base.py +57 -0
- sqlspec/observability/_formatters/_gcp.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_formatters/_gcp.py +131 -0
- sqlspec/observability/_formatting.py +58 -0
- sqlspec/observability/_observer.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_observer.py +357 -0
- sqlspec/observability/_runtime.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_runtime.py +420 -0
- sqlspec/observability/_sampling.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_sampling.py +188 -0
- sqlspec/observability/_spans.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/observability/_spans.py +161 -0
- sqlspec/protocols.py +916 -0
- sqlspec/py.typed +0 -0
- sqlspec/storage/__init__.py +48 -0
- sqlspec/storage/_utils.py +104 -0
- sqlspec/storage/backends/__init__.py +1 -0
- sqlspec/storage/backends/base.py +253 -0
- sqlspec/storage/backends/fsspec.py +529 -0
- sqlspec/storage/backends/local.py +441 -0
- sqlspec/storage/backends/obstore.py +916 -0
- sqlspec/storage/errors.py +104 -0
- sqlspec/storage/pipeline.py +582 -0
- sqlspec/storage/registry.py +301 -0
- sqlspec/typing.py +395 -0
- sqlspec/utils/__init__.py +7 -0
- sqlspec/utils/arrow_helpers.py +318 -0
- sqlspec/utils/config_tools.py +332 -0
- sqlspec/utils/correlation.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/correlation.py +134 -0
- sqlspec/utils/deprecation.py +190 -0
- sqlspec/utils/fixtures.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/fixtures.py +258 -0
- sqlspec/utils/logging.py +222 -0
- sqlspec/utils/module_loader.py +306 -0
- sqlspec/utils/portal.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/portal.py +375 -0
- sqlspec/utils/schema.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/schema.py +485 -0
- sqlspec/utils/serializers.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/serializers.py +408 -0
- sqlspec/utils/singleton.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/singleton.py +41 -0
- sqlspec/utils/sync_tools.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/sync_tools.py +311 -0
- sqlspec/utils/text.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/text.py +108 -0
- sqlspec/utils/type_converters.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/type_converters.py +128 -0
- sqlspec/utils/type_guards.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/type_guards.py +1360 -0
- sqlspec/utils/uuids.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/uuids.py +225 -0
- sqlspec-0.36.0.dist-info/METADATA +205 -0
- sqlspec-0.36.0.dist-info/RECORD +531 -0
- sqlspec-0.36.0.dist-info/WHEEL +7 -0
- sqlspec-0.36.0.dist-info/entry_points.txt +2 -0
- sqlspec-0.36.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,850 @@
|
|
|
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 BaseSyncADKStore, EventRecord, SessionRecord
|
|
20
|
+
from sqlspec.extensions.adk.memory.store import BaseSyncADKMemoryStore
|
|
21
|
+
from sqlspec.utils.logging import get_logger
|
|
22
|
+
from sqlspec.utils.serializers import from_json, to_json
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from sqlspec.adapters.duckdb.config import DuckDBConfig
|
|
26
|
+
from sqlspec.extensions.adk import MemoryRecord
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
__all__ = ("DuckdbADKMemoryStore", "DuckdbADKStore")
|
|
30
|
+
|
|
31
|
+
logger = get_logger("sqlspec.adapters.duckdb.adk.store")
|
|
32
|
+
|
|
33
|
+
DUCKDB_TABLE_NOT_FOUND_ERROR: Final = "does not exist"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DuckdbADKStore(BaseSyncADKStore["DuckDBConfig"]):
|
|
37
|
+
"""DuckDB ADK store for Google Agent Development Kit.
|
|
38
|
+
|
|
39
|
+
Implements session and event storage for Google Agent Development Kit
|
|
40
|
+
using DuckDB's synchronous driver. Provides:
|
|
41
|
+
- Session state management with native JSON type
|
|
42
|
+
- Event history tracking with BLOB-serialized actions
|
|
43
|
+
- Native TIMESTAMP type support
|
|
44
|
+
- Foreign key constraints (manual cascade in delete_session)
|
|
45
|
+
- Columnar storage for analytical queries
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
config: DuckDBConfig with extension_config["adk"] settings.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
from sqlspec.adapters.duckdb import DuckDBConfig
|
|
52
|
+
from sqlspec.adapters.duckdb.adk import DuckdbADKStore
|
|
53
|
+
|
|
54
|
+
config = DuckDBConfig(
|
|
55
|
+
database="sessions.ddb",
|
|
56
|
+
extension_config={
|
|
57
|
+
"adk": {
|
|
58
|
+
"session_table": "my_sessions",
|
|
59
|
+
"events_table": "my_events",
|
|
60
|
+
"owner_id_column": "tenant_id INTEGER REFERENCES tenants(id)"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
store = DuckdbADKStore(config)
|
|
65
|
+
store.ensure_tables()
|
|
66
|
+
|
|
67
|
+
session = store.create_session(
|
|
68
|
+
session_id="session-123",
|
|
69
|
+
app_name="my-app",
|
|
70
|
+
user_id="user-456",
|
|
71
|
+
state={"context": "conversation"}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
Notes:
|
|
75
|
+
- Uses DuckDB native JSON type (not JSONB)
|
|
76
|
+
- TIMESTAMP for date/time storage with microsecond precision
|
|
77
|
+
- BLOB for binary actions data
|
|
78
|
+
- BOOLEAN native type support
|
|
79
|
+
- Columnar storage provides excellent analytical query performance
|
|
80
|
+
- DuckDB doesn't support CASCADE in foreign keys (manual cascade required)
|
|
81
|
+
- Optimized for OLAP workloads; for high-concurrency writes use PostgreSQL
|
|
82
|
+
- Configuration is read from config.extension_config["adk"]
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
__slots__ = ()
|
|
86
|
+
|
|
87
|
+
def __init__(self, config: "DuckDBConfig") -> None:
|
|
88
|
+
"""Initialize DuckDB ADK store.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
config: DuckDBConfig instance.
|
|
92
|
+
|
|
93
|
+
Notes:
|
|
94
|
+
Configuration is read from config.extension_config["adk"]:
|
|
95
|
+
- session_table: Sessions table name (default: "adk_sessions")
|
|
96
|
+
- events_table: Events table name (default: "adk_events")
|
|
97
|
+
- owner_id_column: Optional owner FK column DDL (default: None)
|
|
98
|
+
"""
|
|
99
|
+
super().__init__(config)
|
|
100
|
+
|
|
101
|
+
def _get_create_sessions_table_sql(self) -> str:
|
|
102
|
+
"""Get DuckDB CREATE TABLE SQL for sessions.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
SQL statement to create adk_sessions table with indexes.
|
|
106
|
+
|
|
107
|
+
Notes:
|
|
108
|
+
- VARCHAR for IDs and names
|
|
109
|
+
- JSON type for state storage (DuckDB native)
|
|
110
|
+
- TIMESTAMP for create_time and update_time
|
|
111
|
+
- CURRENT_TIMESTAMP for defaults
|
|
112
|
+
- Optional owner ID column for multi-tenant scenarios
|
|
113
|
+
- Composite index on (app_name, user_id) for listing
|
|
114
|
+
- Index on update_time DESC for recent session queries
|
|
115
|
+
"""
|
|
116
|
+
owner_id_line = ""
|
|
117
|
+
if self._owner_id_column_ddl:
|
|
118
|
+
owner_id_line = f",\n {self._owner_id_column_ddl}"
|
|
119
|
+
|
|
120
|
+
return f"""
|
|
121
|
+
CREATE TABLE IF NOT EXISTS {self._session_table} (
|
|
122
|
+
id VARCHAR PRIMARY KEY,
|
|
123
|
+
app_name VARCHAR NOT NULL,
|
|
124
|
+
user_id VARCHAR NOT NULL{owner_id_line},
|
|
125
|
+
state JSON NOT NULL,
|
|
126
|
+
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
127
|
+
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
128
|
+
);
|
|
129
|
+
CREATE INDEX IF NOT EXISTS idx_{self._session_table}_app_user ON {self._session_table}(app_name, user_id);
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_{self._session_table}_update_time ON {self._session_table}(update_time DESC);
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
def _get_create_events_table_sql(self) -> str:
|
|
134
|
+
"""Get DuckDB CREATE TABLE SQL for events.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
SQL statement to create adk_events table with indexes.
|
|
138
|
+
|
|
139
|
+
Notes:
|
|
140
|
+
- VARCHAR for string fields
|
|
141
|
+
- BLOB for pickled actions
|
|
142
|
+
- JSON for content, grounding_metadata, custom_metadata, long_running_tool_ids_json
|
|
143
|
+
- BOOLEAN for flags
|
|
144
|
+
- Foreign key constraint (DuckDB doesn't support CASCADE)
|
|
145
|
+
- Index on (session_id, timestamp ASC) for ordered event retrieval
|
|
146
|
+
- Manual cascade delete required in delete_session method
|
|
147
|
+
"""
|
|
148
|
+
return f"""
|
|
149
|
+
CREATE TABLE IF NOT EXISTS {self._events_table} (
|
|
150
|
+
id VARCHAR PRIMARY KEY,
|
|
151
|
+
session_id VARCHAR NOT NULL,
|
|
152
|
+
app_name VARCHAR NOT NULL,
|
|
153
|
+
user_id VARCHAR NOT NULL,
|
|
154
|
+
invocation_id VARCHAR,
|
|
155
|
+
author VARCHAR,
|
|
156
|
+
actions BLOB,
|
|
157
|
+
long_running_tool_ids_json JSON,
|
|
158
|
+
branch VARCHAR,
|
|
159
|
+
timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
160
|
+
content JSON,
|
|
161
|
+
grounding_metadata JSON,
|
|
162
|
+
custom_metadata JSON,
|
|
163
|
+
partial BOOLEAN,
|
|
164
|
+
turn_complete BOOLEAN,
|
|
165
|
+
interrupted BOOLEAN,
|
|
166
|
+
error_code VARCHAR,
|
|
167
|
+
error_message VARCHAR,
|
|
168
|
+
FOREIGN KEY (session_id) REFERENCES {self._session_table}(id)
|
|
169
|
+
);
|
|
170
|
+
CREATE INDEX IF NOT EXISTS idx_{self._events_table}_session ON {self._events_table}(session_id, timestamp ASC);
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def _get_drop_tables_sql(self) -> "list[str]":
|
|
174
|
+
"""Get DuckDB DROP TABLE SQL statements.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
List of SQL statements to drop tables and indexes.
|
|
178
|
+
|
|
179
|
+
Notes:
|
|
180
|
+
Order matters: drop events table (child) before sessions (parent).
|
|
181
|
+
DuckDB automatically drops indexes when dropping tables.
|
|
182
|
+
"""
|
|
183
|
+
return [f"DROP TABLE IF EXISTS {self._events_table}", f"DROP TABLE IF EXISTS {self._session_table}"]
|
|
184
|
+
|
|
185
|
+
def create_tables(self) -> None:
|
|
186
|
+
"""Create both sessions and events tables if they don't exist."""
|
|
187
|
+
with self._config.provide_connection() as conn:
|
|
188
|
+
conn.execute(self._get_create_sessions_table_sql())
|
|
189
|
+
conn.execute(self._get_create_events_table_sql())
|
|
190
|
+
|
|
191
|
+
def create_session(
|
|
192
|
+
self, session_id: str, app_name: str, user_id: str, state: "dict[str, Any]", owner_id: "Any | None" = None
|
|
193
|
+
) -> SessionRecord:
|
|
194
|
+
"""Create a new session.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
session_id: Unique session identifier.
|
|
198
|
+
app_name: Application name.
|
|
199
|
+
user_id: User identifier.
|
|
200
|
+
state: Initial session state.
|
|
201
|
+
owner_id: Optional owner ID value for owner_id_column (if configured).
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Created session record.
|
|
205
|
+
|
|
206
|
+
Notes:
|
|
207
|
+
Uses current UTC timestamp for create_time and update_time.
|
|
208
|
+
State is JSON-serialized using SQLSpec serializers.
|
|
209
|
+
"""
|
|
210
|
+
now = datetime.now(timezone.utc)
|
|
211
|
+
state_json = to_json(state)
|
|
212
|
+
|
|
213
|
+
params: tuple[Any, ...]
|
|
214
|
+
if self._owner_id_column_name:
|
|
215
|
+
sql = f"""
|
|
216
|
+
INSERT INTO {self._session_table}
|
|
217
|
+
(id, app_name, user_id, {self._owner_id_column_name}, state, create_time, update_time)
|
|
218
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
219
|
+
"""
|
|
220
|
+
params = (session_id, app_name, user_id, owner_id, state_json, now, now)
|
|
221
|
+
else:
|
|
222
|
+
sql = f"""
|
|
223
|
+
INSERT INTO {self._session_table} (id, app_name, user_id, state, create_time, update_time)
|
|
224
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
225
|
+
"""
|
|
226
|
+
params = (session_id, app_name, user_id, state_json, now, now)
|
|
227
|
+
|
|
228
|
+
with self._config.provide_connection() as conn:
|
|
229
|
+
conn.execute(sql, params)
|
|
230
|
+
conn.commit()
|
|
231
|
+
|
|
232
|
+
return SessionRecord(
|
|
233
|
+
id=session_id, app_name=app_name, user_id=user_id, state=state, create_time=now, update_time=now
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def get_session(self, session_id: str) -> "SessionRecord | None":
|
|
237
|
+
"""Get session by ID.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
session_id: Session identifier.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
Session record or None if not found.
|
|
244
|
+
|
|
245
|
+
Notes:
|
|
246
|
+
DuckDB returns datetime objects for TIMESTAMP columns.
|
|
247
|
+
JSON is parsed from database storage.
|
|
248
|
+
"""
|
|
249
|
+
sql = f"""
|
|
250
|
+
SELECT id, app_name, user_id, state, create_time, update_time
|
|
251
|
+
FROM {self._session_table}
|
|
252
|
+
WHERE id = ?
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
with self._config.provide_connection() as conn:
|
|
257
|
+
cursor = conn.execute(sql, (session_id,))
|
|
258
|
+
row = cursor.fetchone()
|
|
259
|
+
|
|
260
|
+
if row is None:
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
session_id_val, app_name, user_id, state_data, create_time, update_time = row
|
|
264
|
+
|
|
265
|
+
state = from_json(state_data) if state_data else {}
|
|
266
|
+
|
|
267
|
+
return SessionRecord(
|
|
268
|
+
id=session_id_val,
|
|
269
|
+
app_name=app_name,
|
|
270
|
+
user_id=user_id,
|
|
271
|
+
state=state,
|
|
272
|
+
create_time=create_time,
|
|
273
|
+
update_time=update_time,
|
|
274
|
+
)
|
|
275
|
+
except Exception as e:
|
|
276
|
+
if DUCKDB_TABLE_NOT_FOUND_ERROR in str(e):
|
|
277
|
+
return None
|
|
278
|
+
raise
|
|
279
|
+
|
|
280
|
+
def update_session_state(self, session_id: str, state: "dict[str, Any]") -> None:
|
|
281
|
+
"""Update session state.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
session_id: Session identifier.
|
|
285
|
+
state: New state dictionary (replaces existing state).
|
|
286
|
+
|
|
287
|
+
Notes:
|
|
288
|
+
This replaces the entire state dictionary.
|
|
289
|
+
Update time is automatically set to current UTC timestamp.
|
|
290
|
+
"""
|
|
291
|
+
now = datetime.now(timezone.utc)
|
|
292
|
+
state_json = to_json(state)
|
|
293
|
+
|
|
294
|
+
sql = f"""
|
|
295
|
+
UPDATE {self._session_table}
|
|
296
|
+
SET state = ?, update_time = ?
|
|
297
|
+
WHERE id = ?
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
with self._config.provide_connection() as conn:
|
|
301
|
+
conn.execute(sql, (state_json, now, session_id))
|
|
302
|
+
conn.commit()
|
|
303
|
+
|
|
304
|
+
def delete_session(self, session_id: str) -> None:
|
|
305
|
+
"""Delete session and all associated events.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
session_id: Session identifier.
|
|
309
|
+
|
|
310
|
+
Notes:
|
|
311
|
+
DuckDB doesn't support CASCADE in foreign keys, so we manually delete events first.
|
|
312
|
+
"""
|
|
313
|
+
delete_events_sql = f"DELETE FROM {self._events_table} WHERE session_id = ?"
|
|
314
|
+
delete_session_sql = f"DELETE FROM {self._session_table} WHERE id = ?"
|
|
315
|
+
|
|
316
|
+
with self._config.provide_connection() as conn:
|
|
317
|
+
conn.execute(delete_events_sql, (session_id,))
|
|
318
|
+
conn.execute(delete_session_sql, (session_id,))
|
|
319
|
+
conn.commit()
|
|
320
|
+
|
|
321
|
+
def list_sessions(self, app_name: str, user_id: str | None = None) -> "list[SessionRecord]":
|
|
322
|
+
"""List sessions for an app, optionally filtered by user.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
app_name: Application name.
|
|
326
|
+
user_id: User identifier. If None, lists all sessions for the app.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
List of session records ordered by update_time DESC.
|
|
330
|
+
|
|
331
|
+
Notes:
|
|
332
|
+
Uses composite index on (app_name, user_id) when user_id is provided.
|
|
333
|
+
"""
|
|
334
|
+
if user_id is None:
|
|
335
|
+
sql = f"""
|
|
336
|
+
SELECT id, app_name, user_id, state, create_time, update_time
|
|
337
|
+
FROM {self._session_table}
|
|
338
|
+
WHERE app_name = ?
|
|
339
|
+
ORDER BY update_time DESC
|
|
340
|
+
"""
|
|
341
|
+
params: tuple[str, ...] = (app_name,)
|
|
342
|
+
else:
|
|
343
|
+
sql = f"""
|
|
344
|
+
SELECT id, app_name, user_id, state, create_time, update_time
|
|
345
|
+
FROM {self._session_table}
|
|
346
|
+
WHERE app_name = ? AND user_id = ?
|
|
347
|
+
ORDER BY update_time DESC
|
|
348
|
+
"""
|
|
349
|
+
params = (app_name, user_id)
|
|
350
|
+
|
|
351
|
+
try:
|
|
352
|
+
with self._config.provide_connection() as conn:
|
|
353
|
+
cursor = conn.execute(sql, params)
|
|
354
|
+
rows = cursor.fetchall()
|
|
355
|
+
|
|
356
|
+
return [
|
|
357
|
+
SessionRecord(
|
|
358
|
+
id=row[0],
|
|
359
|
+
app_name=row[1],
|
|
360
|
+
user_id=row[2],
|
|
361
|
+
state=from_json(row[3]) if row[3] else {},
|
|
362
|
+
create_time=row[4],
|
|
363
|
+
update_time=row[5],
|
|
364
|
+
)
|
|
365
|
+
for row in rows
|
|
366
|
+
]
|
|
367
|
+
except Exception as e:
|
|
368
|
+
if DUCKDB_TABLE_NOT_FOUND_ERROR in str(e):
|
|
369
|
+
return []
|
|
370
|
+
raise
|
|
371
|
+
|
|
372
|
+
def create_event(
|
|
373
|
+
self,
|
|
374
|
+
event_id: str,
|
|
375
|
+
session_id: str,
|
|
376
|
+
app_name: str,
|
|
377
|
+
user_id: str,
|
|
378
|
+
author: "str | None" = None,
|
|
379
|
+
actions: "bytes | None" = None,
|
|
380
|
+
content: "dict[str, Any] | None" = None,
|
|
381
|
+
**kwargs: Any,
|
|
382
|
+
) -> EventRecord:
|
|
383
|
+
"""Create a new event.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
event_id: Unique event identifier.
|
|
387
|
+
session_id: Session identifier.
|
|
388
|
+
app_name: Application name.
|
|
389
|
+
user_id: User identifier.
|
|
390
|
+
author: Event author (user/assistant/system).
|
|
391
|
+
actions: Pickled actions object.
|
|
392
|
+
content: Event content (JSON).
|
|
393
|
+
**kwargs: Additional optional fields.
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
Created event record.
|
|
397
|
+
|
|
398
|
+
Notes:
|
|
399
|
+
Uses current UTC timestamp if not provided in kwargs.
|
|
400
|
+
JSON fields are serialized using SQLSpec serializers.
|
|
401
|
+
"""
|
|
402
|
+
timestamp = kwargs.get("timestamp", datetime.now(timezone.utc))
|
|
403
|
+
content_json = to_json(content) if content else None
|
|
404
|
+
grounding_metadata = kwargs.get("grounding_metadata")
|
|
405
|
+
grounding_metadata_json = to_json(grounding_metadata) if grounding_metadata else None
|
|
406
|
+
custom_metadata = kwargs.get("custom_metadata")
|
|
407
|
+
custom_metadata_json = to_json(custom_metadata) if custom_metadata else None
|
|
408
|
+
|
|
409
|
+
sql = f"""
|
|
410
|
+
INSERT INTO {self._events_table} (
|
|
411
|
+
id, session_id, app_name, user_id, invocation_id, author, actions,
|
|
412
|
+
long_running_tool_ids_json, branch, timestamp, content,
|
|
413
|
+
grounding_metadata, custom_metadata, partial, turn_complete,
|
|
414
|
+
interrupted, error_code, error_message
|
|
415
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
416
|
+
"""
|
|
417
|
+
|
|
418
|
+
with self._config.provide_connection() as conn:
|
|
419
|
+
conn.execute(
|
|
420
|
+
sql,
|
|
421
|
+
(
|
|
422
|
+
event_id,
|
|
423
|
+
session_id,
|
|
424
|
+
app_name,
|
|
425
|
+
user_id,
|
|
426
|
+
kwargs.get("invocation_id"),
|
|
427
|
+
author,
|
|
428
|
+
actions,
|
|
429
|
+
kwargs.get("long_running_tool_ids_json"),
|
|
430
|
+
kwargs.get("branch"),
|
|
431
|
+
timestamp,
|
|
432
|
+
content_json,
|
|
433
|
+
grounding_metadata_json,
|
|
434
|
+
custom_metadata_json,
|
|
435
|
+
kwargs.get("partial"),
|
|
436
|
+
kwargs.get("turn_complete"),
|
|
437
|
+
kwargs.get("interrupted"),
|
|
438
|
+
kwargs.get("error_code"),
|
|
439
|
+
kwargs.get("error_message"),
|
|
440
|
+
),
|
|
441
|
+
)
|
|
442
|
+
conn.commit()
|
|
443
|
+
|
|
444
|
+
return EventRecord(
|
|
445
|
+
id=event_id,
|
|
446
|
+
session_id=session_id,
|
|
447
|
+
app_name=app_name,
|
|
448
|
+
user_id=user_id,
|
|
449
|
+
invocation_id=kwargs.get("invocation_id", ""),
|
|
450
|
+
author=author or "",
|
|
451
|
+
actions=actions or b"",
|
|
452
|
+
long_running_tool_ids_json=kwargs.get("long_running_tool_ids_json"),
|
|
453
|
+
branch=kwargs.get("branch"),
|
|
454
|
+
timestamp=timestamp,
|
|
455
|
+
content=content,
|
|
456
|
+
grounding_metadata=grounding_metadata,
|
|
457
|
+
custom_metadata=custom_metadata,
|
|
458
|
+
partial=kwargs.get("partial"),
|
|
459
|
+
turn_complete=kwargs.get("turn_complete"),
|
|
460
|
+
interrupted=kwargs.get("interrupted"),
|
|
461
|
+
error_code=kwargs.get("error_code"),
|
|
462
|
+
error_message=kwargs.get("error_message"),
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
def get_event(self, event_id: str) -> "EventRecord | None":
|
|
466
|
+
"""Get event by ID.
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
event_id: Event identifier.
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
Event record or None if not found.
|
|
473
|
+
"""
|
|
474
|
+
sql = f"""
|
|
475
|
+
SELECT id, session_id, app_name, user_id, invocation_id, author, actions,
|
|
476
|
+
long_running_tool_ids_json, branch, timestamp, content,
|
|
477
|
+
grounding_metadata, custom_metadata, partial, turn_complete,
|
|
478
|
+
interrupted, error_code, error_message
|
|
479
|
+
FROM {self._events_table}
|
|
480
|
+
WHERE id = ?
|
|
481
|
+
"""
|
|
482
|
+
|
|
483
|
+
try:
|
|
484
|
+
with self._config.provide_connection() as conn:
|
|
485
|
+
cursor = conn.execute(sql, (event_id,))
|
|
486
|
+
row = cursor.fetchone()
|
|
487
|
+
|
|
488
|
+
if row is None:
|
|
489
|
+
return None
|
|
490
|
+
|
|
491
|
+
return EventRecord(
|
|
492
|
+
id=row[0],
|
|
493
|
+
session_id=row[1],
|
|
494
|
+
app_name=row[2],
|
|
495
|
+
user_id=row[3],
|
|
496
|
+
invocation_id=row[4],
|
|
497
|
+
author=row[5],
|
|
498
|
+
actions=bytes(row[6]) if row[6] else b"",
|
|
499
|
+
long_running_tool_ids_json=row[7],
|
|
500
|
+
branch=row[8],
|
|
501
|
+
timestamp=row[9],
|
|
502
|
+
content=from_json(row[10]) if row[10] else None,
|
|
503
|
+
grounding_metadata=from_json(row[11]) if row[11] else None,
|
|
504
|
+
custom_metadata=from_json(row[12]) if row[12] else None,
|
|
505
|
+
partial=row[13],
|
|
506
|
+
turn_complete=row[14],
|
|
507
|
+
interrupted=row[15],
|
|
508
|
+
error_code=row[16],
|
|
509
|
+
error_message=row[17],
|
|
510
|
+
)
|
|
511
|
+
except Exception as e:
|
|
512
|
+
if DUCKDB_TABLE_NOT_FOUND_ERROR in str(e):
|
|
513
|
+
return None
|
|
514
|
+
raise
|
|
515
|
+
|
|
516
|
+
def list_events(self, session_id: str) -> "list[EventRecord]":
|
|
517
|
+
"""List events for a session ordered by timestamp.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
session_id: Session identifier.
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
List of event records ordered by timestamp ASC.
|
|
524
|
+
"""
|
|
525
|
+
sql = f"""
|
|
526
|
+
SELECT id, session_id, app_name, user_id, invocation_id, author, actions,
|
|
527
|
+
long_running_tool_ids_json, branch, timestamp, content,
|
|
528
|
+
grounding_metadata, custom_metadata, partial, turn_complete,
|
|
529
|
+
interrupted, error_code, error_message
|
|
530
|
+
FROM {self._events_table}
|
|
531
|
+
WHERE session_id = ?
|
|
532
|
+
ORDER BY timestamp ASC
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
try:
|
|
536
|
+
with self._config.provide_connection() as conn:
|
|
537
|
+
cursor = conn.execute(sql, (session_id,))
|
|
538
|
+
rows = cursor.fetchall()
|
|
539
|
+
|
|
540
|
+
return [
|
|
541
|
+
EventRecord(
|
|
542
|
+
id=row[0],
|
|
543
|
+
session_id=row[1],
|
|
544
|
+
app_name=row[2],
|
|
545
|
+
user_id=row[3],
|
|
546
|
+
invocation_id=row[4],
|
|
547
|
+
author=row[5],
|
|
548
|
+
actions=bytes(row[6]) if row[6] else b"",
|
|
549
|
+
long_running_tool_ids_json=row[7],
|
|
550
|
+
branch=row[8],
|
|
551
|
+
timestamp=row[9],
|
|
552
|
+
content=from_json(row[10]) if row[10] else None,
|
|
553
|
+
grounding_metadata=from_json(row[11]) if row[11] else None,
|
|
554
|
+
custom_metadata=from_json(row[12]) if row[12] else None,
|
|
555
|
+
partial=row[13],
|
|
556
|
+
turn_complete=row[14],
|
|
557
|
+
interrupted=row[15],
|
|
558
|
+
error_code=row[16],
|
|
559
|
+
error_message=row[17],
|
|
560
|
+
)
|
|
561
|
+
for row in rows
|
|
562
|
+
]
|
|
563
|
+
except Exception as e:
|
|
564
|
+
if DUCKDB_TABLE_NOT_FOUND_ERROR in str(e):
|
|
565
|
+
return []
|
|
566
|
+
raise
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
class DuckdbADKMemoryStore(BaseSyncADKMemoryStore["DuckDBConfig"]):
|
|
570
|
+
"""DuckDB ADK memory store using synchronous DuckDB driver.
|
|
571
|
+
|
|
572
|
+
Implements memory entry storage for Google Agent Development Kit
|
|
573
|
+
using DuckDB's synchronous driver. Provides:
|
|
574
|
+
- Session memory storage with native JSON type
|
|
575
|
+
- Simple ILIKE search
|
|
576
|
+
- Native TIMESTAMP type support
|
|
577
|
+
- Deduplication via event_id unique constraint
|
|
578
|
+
- Efficient upserts using INSERT OR IGNORE
|
|
579
|
+
- Columnar storage for analytical queries
|
|
580
|
+
|
|
581
|
+
Args:
|
|
582
|
+
config: DuckDBConfig with extension_config["adk"] settings.
|
|
583
|
+
|
|
584
|
+
Example:
|
|
585
|
+
from sqlspec.adapters.duckdb import DuckDBConfig
|
|
586
|
+
from sqlspec.adapters.duckdb.adk.store import DuckdbADKMemoryStore
|
|
587
|
+
|
|
588
|
+
config = DuckDBConfig(
|
|
589
|
+
database="app.ddb",
|
|
590
|
+
extension_config={
|
|
591
|
+
"adk": {
|
|
592
|
+
"memory_table": "adk_memory_entries",
|
|
593
|
+
"memory_max_results": 20,
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
)
|
|
597
|
+
store = DuckdbADKMemoryStore(config)
|
|
598
|
+
store.ensure_tables()
|
|
599
|
+
|
|
600
|
+
Notes:
|
|
601
|
+
- Uses DuckDB native JSON type (not JSONB)
|
|
602
|
+
- TIMESTAMP for date/time storage with microsecond precision
|
|
603
|
+
- event_id UNIQUE constraint for deduplication
|
|
604
|
+
- Composite index on (app_name, user_id, timestamp DESC)
|
|
605
|
+
- Columnar storage provides excellent analytical query performance
|
|
606
|
+
- Optimized for OLAP workloads; for high-concurrency writes use PostgreSQL
|
|
607
|
+
- Configuration is read from config.extension_config["adk"]
|
|
608
|
+
"""
|
|
609
|
+
|
|
610
|
+
__slots__ = ()
|
|
611
|
+
|
|
612
|
+
def __init__(self, config: "DuckDBConfig") -> None:
|
|
613
|
+
"""Initialize DuckDB ADK memory store.
|
|
614
|
+
|
|
615
|
+
Args:
|
|
616
|
+
config: DuckDBConfig instance.
|
|
617
|
+
|
|
618
|
+
Notes:
|
|
619
|
+
Configuration is read from config.extension_config["adk"]:
|
|
620
|
+
- memory_table: Memory table name (default: "adk_memory_entries")
|
|
621
|
+
- memory_use_fts: Enable full-text search when supported (default: False)
|
|
622
|
+
- memory_max_results: Max search results (default: 20)
|
|
623
|
+
- owner_id_column: Optional owner FK column DDL (default: None)
|
|
624
|
+
- enable_memory: Whether memory is enabled (default: True)
|
|
625
|
+
"""
|
|
626
|
+
super().__init__(config)
|
|
627
|
+
|
|
628
|
+
def _ensure_fts_extension(self, conn: Any) -> bool:
|
|
629
|
+
"""Ensure the DuckDB FTS extension is available for this connection."""
|
|
630
|
+
with contextlib.suppress(Exception):
|
|
631
|
+
conn.execute("INSTALL fts")
|
|
632
|
+
|
|
633
|
+
try:
|
|
634
|
+
conn.execute("LOAD fts")
|
|
635
|
+
except Exception as exc:
|
|
636
|
+
logger.debug("DuckDB FTS extension unavailable: %s", exc)
|
|
637
|
+
return False
|
|
638
|
+
|
|
639
|
+
return True
|
|
640
|
+
|
|
641
|
+
def _create_fts_index(self, conn: Any) -> None:
|
|
642
|
+
"""Create FTS index for the memory table."""
|
|
643
|
+
if not self._ensure_fts_extension(conn):
|
|
644
|
+
return
|
|
645
|
+
|
|
646
|
+
try:
|
|
647
|
+
conn.execute(f"PRAGMA create_fts_index('{self._memory_table}', 'id', 'content_text')")
|
|
648
|
+
except Exception as exc:
|
|
649
|
+
logger.debug("Failed to create DuckDB FTS index: %s", exc)
|
|
650
|
+
|
|
651
|
+
def _refresh_fts_index(self, conn: Any) -> None:
|
|
652
|
+
"""Rebuild the FTS index to reflect recent changes."""
|
|
653
|
+
if not self._ensure_fts_extension(conn):
|
|
654
|
+
return
|
|
655
|
+
|
|
656
|
+
with contextlib.suppress(Exception):
|
|
657
|
+
conn.execute(f"PRAGMA drop_fts_index('{self._memory_table}')")
|
|
658
|
+
|
|
659
|
+
try:
|
|
660
|
+
conn.execute(f"PRAGMA create_fts_index('{self._memory_table}', 'id', 'content_text')")
|
|
661
|
+
except Exception as exc:
|
|
662
|
+
logger.debug("Failed to refresh DuckDB FTS index: %s", exc)
|
|
663
|
+
|
|
664
|
+
def _get_create_memory_table_sql(self) -> str:
|
|
665
|
+
"""Get DuckDB CREATE TABLE SQL for memory entries.
|
|
666
|
+
|
|
667
|
+
Returns:
|
|
668
|
+
SQL statement to create memory table with indexes.
|
|
669
|
+
"""
|
|
670
|
+
owner_id_line = ""
|
|
671
|
+
if self._owner_id_column_ddl:
|
|
672
|
+
owner_id_line = f",\n {self._owner_id_column_ddl}"
|
|
673
|
+
|
|
674
|
+
return f"""
|
|
675
|
+
CREATE TABLE IF NOT EXISTS {self._memory_table} (
|
|
676
|
+
id VARCHAR(128) PRIMARY KEY,
|
|
677
|
+
session_id VARCHAR(128) NOT NULL,
|
|
678
|
+
app_name VARCHAR(128) NOT NULL,
|
|
679
|
+
user_id VARCHAR(128) NOT NULL,
|
|
680
|
+
event_id VARCHAR(128) NOT NULL UNIQUE,
|
|
681
|
+
author VARCHAR(256){owner_id_line},
|
|
682
|
+
timestamp TIMESTAMP NOT NULL,
|
|
683
|
+
content_json JSON NOT NULL,
|
|
684
|
+
content_text TEXT NOT NULL,
|
|
685
|
+
metadata_json JSON,
|
|
686
|
+
inserted_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
CREATE INDEX IF NOT EXISTS idx_{self._memory_table}_app_user_time
|
|
690
|
+
ON {self._memory_table}(app_name, user_id, timestamp DESC);
|
|
691
|
+
|
|
692
|
+
CREATE INDEX IF NOT EXISTS idx_{self._memory_table}_session
|
|
693
|
+
ON {self._memory_table}(session_id);
|
|
694
|
+
"""
|
|
695
|
+
|
|
696
|
+
def _get_drop_memory_table_sql(self) -> "list[str]":
|
|
697
|
+
"""Get DuckDB DROP TABLE SQL statements."""
|
|
698
|
+
return [f"DROP TABLE IF EXISTS {self._memory_table}"]
|
|
699
|
+
|
|
700
|
+
def create_tables(self) -> None:
|
|
701
|
+
"""Create the memory table and indexes if they don't exist."""
|
|
702
|
+
if not self._enabled:
|
|
703
|
+
return
|
|
704
|
+
|
|
705
|
+
with self._config.provide_connection() as conn:
|
|
706
|
+
conn.execute(self._get_create_memory_table_sql())
|
|
707
|
+
if self._use_fts:
|
|
708
|
+
self._create_fts_index(conn)
|
|
709
|
+
|
|
710
|
+
def insert_memory_entries(self, entries: "list[MemoryRecord]", owner_id: "object | None" = None) -> int:
|
|
711
|
+
"""Bulk insert memory entries with deduplication."""
|
|
712
|
+
if not self._enabled:
|
|
713
|
+
msg = "Memory store is disabled"
|
|
714
|
+
raise RuntimeError(msg)
|
|
715
|
+
|
|
716
|
+
if not entries:
|
|
717
|
+
return 0
|
|
718
|
+
|
|
719
|
+
inserted_count = 0
|
|
720
|
+
if self._owner_id_column_name:
|
|
721
|
+
sql = f"""
|
|
722
|
+
INSERT INTO {self._memory_table} (
|
|
723
|
+
id, session_id, app_name, user_id, event_id, author,
|
|
724
|
+
{self._owner_id_column_name}, timestamp, content_json,
|
|
725
|
+
content_text, metadata_json, inserted_at
|
|
726
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
727
|
+
ON CONFLICT(event_id) DO NOTHING RETURNING 1
|
|
728
|
+
"""
|
|
729
|
+
else:
|
|
730
|
+
sql = f"""
|
|
731
|
+
INSERT INTO {self._memory_table} (
|
|
732
|
+
id, session_id, app_name, user_id, event_id, author,
|
|
733
|
+
timestamp, content_json, content_text, metadata_json, inserted_at
|
|
734
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
735
|
+
ON CONFLICT(event_id) DO NOTHING RETURNING 1
|
|
736
|
+
"""
|
|
737
|
+
|
|
738
|
+
with self._config.provide_connection() as conn:
|
|
739
|
+
for entry in entries:
|
|
740
|
+
params: tuple[Any, ...]
|
|
741
|
+
if self._owner_id_column_name:
|
|
742
|
+
params = (
|
|
743
|
+
entry["id"],
|
|
744
|
+
entry["session_id"],
|
|
745
|
+
entry["app_name"],
|
|
746
|
+
entry["user_id"],
|
|
747
|
+
entry["event_id"],
|
|
748
|
+
entry["author"],
|
|
749
|
+
owner_id,
|
|
750
|
+
entry["timestamp"],
|
|
751
|
+
to_json(entry["content_json"]),
|
|
752
|
+
entry["content_text"],
|
|
753
|
+
to_json(entry["metadata_json"]),
|
|
754
|
+
entry["inserted_at"],
|
|
755
|
+
)
|
|
756
|
+
else:
|
|
757
|
+
params = (
|
|
758
|
+
entry["id"],
|
|
759
|
+
entry["session_id"],
|
|
760
|
+
entry["app_name"],
|
|
761
|
+
entry["user_id"],
|
|
762
|
+
entry["event_id"],
|
|
763
|
+
entry["author"],
|
|
764
|
+
entry["timestamp"],
|
|
765
|
+
to_json(entry["content_json"]),
|
|
766
|
+
entry["content_text"],
|
|
767
|
+
to_json(entry["metadata_json"]),
|
|
768
|
+
entry["inserted_at"],
|
|
769
|
+
)
|
|
770
|
+
result = conn.execute(sql, params)
|
|
771
|
+
inserted_count += len(result.fetchall())
|
|
772
|
+
conn.commit()
|
|
773
|
+
return inserted_count
|
|
774
|
+
|
|
775
|
+
def search_entries(
|
|
776
|
+
self, query: str, app_name: str, user_id: str, limit: "int | None" = None
|
|
777
|
+
) -> "list[MemoryRecord]":
|
|
778
|
+
"""Search memory entries by text query."""
|
|
779
|
+
if not self._enabled:
|
|
780
|
+
msg = "Memory store is disabled"
|
|
781
|
+
raise RuntimeError(msg)
|
|
782
|
+
|
|
783
|
+
if not query:
|
|
784
|
+
return []
|
|
785
|
+
|
|
786
|
+
limit_value = limit or self._max_results
|
|
787
|
+
if self._use_fts:
|
|
788
|
+
sql = f"""
|
|
789
|
+
SELECT * FROM {self._memory_table}
|
|
790
|
+
WHERE app_name = ? AND user_id = ? AND content_text @@ ?
|
|
791
|
+
ORDER BY timestamp DESC
|
|
792
|
+
LIMIT ?
|
|
793
|
+
"""
|
|
794
|
+
params = (app_name, user_id, query, limit_value)
|
|
795
|
+
else:
|
|
796
|
+
sql = f"""
|
|
797
|
+
SELECT * FROM {self._memory_table}
|
|
798
|
+
WHERE app_name = ? AND user_id = ? AND content_text ILIKE ?
|
|
799
|
+
ORDER BY timestamp DESC
|
|
800
|
+
LIMIT ?
|
|
801
|
+
"""
|
|
802
|
+
params = (app_name, user_id, f"%{query}%", limit_value)
|
|
803
|
+
|
|
804
|
+
with self._config.provide_connection() as conn:
|
|
805
|
+
rows = conn.execute(sql, params).fetchall()
|
|
806
|
+
columns = [col[0] for col in conn.description or []]
|
|
807
|
+
records: list[MemoryRecord] = []
|
|
808
|
+
for row in rows:
|
|
809
|
+
record = cast("MemoryRecord", dict(zip(columns, row, strict=False)))
|
|
810
|
+
content_value = record["content_json"]
|
|
811
|
+
if isinstance(content_value, (str, bytes)):
|
|
812
|
+
record["content_json"] = from_json(content_value)
|
|
813
|
+
metadata_value = record.get("metadata_json")
|
|
814
|
+
if isinstance(metadata_value, (str, bytes)):
|
|
815
|
+
record["metadata_json"] = from_json(metadata_value)
|
|
816
|
+
records.append(record)
|
|
817
|
+
if self._use_fts:
|
|
818
|
+
with self._config.provide_connection() as conn:
|
|
819
|
+
self._refresh_fts_index(conn)
|
|
820
|
+
return records
|
|
821
|
+
|
|
822
|
+
def delete_entries_by_session(self, session_id: str) -> int:
|
|
823
|
+
"""Delete all memory entries for a specific session."""
|
|
824
|
+
if not self._enabled:
|
|
825
|
+
msg = "Memory store is disabled"
|
|
826
|
+
raise RuntimeError(msg)
|
|
827
|
+
|
|
828
|
+
sql = f"DELETE FROM {self._memory_table} WHERE session_id = ? RETURNING 1"
|
|
829
|
+
with self._config.provide_connection() as conn:
|
|
830
|
+
result = conn.execute(sql, (session_id,))
|
|
831
|
+
deleted_count = len(result.fetchall())
|
|
832
|
+
conn.commit()
|
|
833
|
+
return deleted_count
|
|
834
|
+
|
|
835
|
+
def delete_entries_older_than(self, days: int) -> int:
|
|
836
|
+
"""Delete memory entries older than specified days."""
|
|
837
|
+
if not self._enabled:
|
|
838
|
+
msg = "Memory store is disabled"
|
|
839
|
+
raise RuntimeError(msg)
|
|
840
|
+
|
|
841
|
+
sql = f"""
|
|
842
|
+
DELETE FROM {self._memory_table}
|
|
843
|
+
WHERE inserted_at < (CURRENT_TIMESTAMP - INTERVAL '{days} days')
|
|
844
|
+
RETURNING 1
|
|
845
|
+
"""
|
|
846
|
+
with self._config.provide_connection() as conn:
|
|
847
|
+
result = conn.execute(sql)
|
|
848
|
+
deleted_count = len(result.fetchall())
|
|
849
|
+
conn.commit()
|
|
850
|
+
return deleted_count
|