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,758 @@
|
|
|
1
|
+
"""Spanner ADK store."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from datetime import datetime, timedelta, timezone
|
|
5
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Protocol, cast
|
|
6
|
+
|
|
7
|
+
from google.cloud.spanner_v1 import param_types
|
|
8
|
+
|
|
9
|
+
from sqlspec.adapters.spanner.config import SpannerSyncConfig
|
|
10
|
+
from sqlspec.extensions.adk import BaseAsyncADKStore, EventRecord, SessionRecord
|
|
11
|
+
from sqlspec.extensions.adk.memory.store import BaseAsyncADKMemoryStore
|
|
12
|
+
from sqlspec.protocols import SpannerParamTypesProtocol
|
|
13
|
+
from sqlspec.utils.serializers import from_json, to_json
|
|
14
|
+
from sqlspec.utils.sync_tools import async_, run_
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from google.cloud.spanner_v1.database import Database
|
|
18
|
+
from google.cloud.spanner_v1.transaction import Transaction
|
|
19
|
+
|
|
20
|
+
from sqlspec.config import ADKConfig
|
|
21
|
+
from sqlspec.extensions.adk import MemoryRecord
|
|
22
|
+
SPANNER_PARAM_TYPES: SpannerParamTypesProtocol = cast("SpannerParamTypesProtocol", param_types)
|
|
23
|
+
|
|
24
|
+
__all__ = ("SpannerSyncADKMemoryStore", "SpannerSyncADKStore")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SpannerSyncADKStore(BaseAsyncADKStore[SpannerSyncConfig]):
|
|
28
|
+
"""Spanner ADK store backed by synchronous Spanner client."""
|
|
29
|
+
|
|
30
|
+
connector_name: ClassVar[str] = "spanner"
|
|
31
|
+
|
|
32
|
+
def __init__(self, config: SpannerSyncConfig) -> None:
|
|
33
|
+
super().__init__(config)
|
|
34
|
+
adk_config = cast("dict[str, Any]", config.extension_config.get("adk", {}))
|
|
35
|
+
self._shard_count: int = int(adk_config.get("shard_count", 0)) if adk_config.get("shard_count") else 0
|
|
36
|
+
self._session_table_options: str | None = adk_config.get("session_table_options")
|
|
37
|
+
self._events_table_options: str | None = adk_config.get("events_table_options")
|
|
38
|
+
self._expires_index_options: str | None = adk_config.get("expires_index_options")
|
|
39
|
+
self._session_row_deletion_policy = _spanner_row_deletion_policy(
|
|
40
|
+
adk_config, "session_ttl_seconds", "create_time"
|
|
41
|
+
)
|
|
42
|
+
self._events_row_deletion_policy = _spanner_row_deletion_policy(adk_config, "event_ttl_seconds", "timestamp")
|
|
43
|
+
|
|
44
|
+
def _database(self) -> "Database":
|
|
45
|
+
return self._config.get_database()
|
|
46
|
+
|
|
47
|
+
def _run_read(
|
|
48
|
+
self, sql: str, params: "dict[str, Any] | None" = None, types: "dict[str, Any] | None" = None
|
|
49
|
+
) -> "list[Any]":
|
|
50
|
+
with self._config.provide_connection() as snapshot:
|
|
51
|
+
result_set = cast("Any", snapshot).execute_sql(sql, params=params, param_types=types)
|
|
52
|
+
return list(result_set)
|
|
53
|
+
|
|
54
|
+
def _run_write(self, statements: "list[tuple[str, dict[str, Any], dict[str, Any]]]") -> None:
|
|
55
|
+
self._database().run_in_transaction(_SpannerWriteJob(statements)) # type: ignore[no-untyped-call]
|
|
56
|
+
|
|
57
|
+
def _session_param_types(self, include_owner: bool) -> "dict[str, Any]":
|
|
58
|
+
json_type = _json_param_type()
|
|
59
|
+
types: dict[str, Any] = {
|
|
60
|
+
"id": SPANNER_PARAM_TYPES.STRING,
|
|
61
|
+
"app_name": SPANNER_PARAM_TYPES.STRING,
|
|
62
|
+
"user_id": SPANNER_PARAM_TYPES.STRING,
|
|
63
|
+
"state": json_type,
|
|
64
|
+
}
|
|
65
|
+
if include_owner and self._owner_id_column_name:
|
|
66
|
+
types["owner_id"] = SPANNER_PARAM_TYPES.STRING
|
|
67
|
+
return types
|
|
68
|
+
|
|
69
|
+
def _event_param_types(self) -> "dict[str, Any]":
|
|
70
|
+
json_type = _json_param_type()
|
|
71
|
+
return {
|
|
72
|
+
"session_id": SPANNER_PARAM_TYPES.STRING,
|
|
73
|
+
"invocation_id": SPANNER_PARAM_TYPES.STRING,
|
|
74
|
+
"author": SPANNER_PARAM_TYPES.STRING,
|
|
75
|
+
"timestamp": SPANNER_PARAM_TYPES.TIMESTAMP,
|
|
76
|
+
"event_json": json_type,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
def _decode_state(self, raw: Any) -> Any:
|
|
80
|
+
if isinstance(raw, str):
|
|
81
|
+
return from_json(raw)
|
|
82
|
+
return raw
|
|
83
|
+
|
|
84
|
+
def _decode_json(self, raw: Any) -> Any:
|
|
85
|
+
if raw is None:
|
|
86
|
+
return None
|
|
87
|
+
if isinstance(raw, str):
|
|
88
|
+
return from_json(raw)
|
|
89
|
+
return raw
|
|
90
|
+
|
|
91
|
+
def _create_session(
|
|
92
|
+
self, session_id: str, app_name: str, user_id: str, state: "dict[str, Any]", owner_id: "Any | None" = None
|
|
93
|
+
) -> SessionRecord:
|
|
94
|
+
state_json = to_json(state)
|
|
95
|
+
params: dict[str, Any] = {"id": session_id, "app_name": app_name, "user_id": user_id, "state": state_json}
|
|
96
|
+
columns = "id, app_name, user_id, state, create_time, update_time"
|
|
97
|
+
values = "@id, @app_name, @user_id, @state, PENDING_COMMIT_TIMESTAMP(), PENDING_COMMIT_TIMESTAMP()"
|
|
98
|
+
if self._owner_id_column_name:
|
|
99
|
+
params["owner_id"] = owner_id
|
|
100
|
+
columns = f"id, app_name, user_id, {self._owner_id_column_name}, state, create_time, update_time"
|
|
101
|
+
values = (
|
|
102
|
+
"@id, @app_name, @user_id, @owner_id, @state, PENDING_COMMIT_TIMESTAMP(), PENDING_COMMIT_TIMESTAMP()"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
sql = f"""
|
|
106
|
+
INSERT INTO {self._session_table} ({columns})
|
|
107
|
+
VALUES ({values})
|
|
108
|
+
"""
|
|
109
|
+
self._run_write([(sql, params, self._session_param_types(self._owner_id_column_name is not None))])
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
"id": session_id,
|
|
113
|
+
"app_name": app_name,
|
|
114
|
+
"user_id": user_id,
|
|
115
|
+
"state": state,
|
|
116
|
+
"create_time": datetime.now(timezone.utc),
|
|
117
|
+
"update_time": datetime.now(timezone.utc),
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async def create_session(
|
|
121
|
+
self, session_id: str, app_name: str, user_id: str, state: "dict[str, Any]", owner_id: "Any | None" = None
|
|
122
|
+
) -> SessionRecord:
|
|
123
|
+
"""Create a new session."""
|
|
124
|
+
return await async_(self._create_session)(session_id, app_name, user_id, state, owner_id)
|
|
125
|
+
|
|
126
|
+
def _get_session(self, session_id: str) -> "SessionRecord | None":
|
|
127
|
+
sql = f"""
|
|
128
|
+
SELECT id, app_name, user_id, state, create_time, update_time{", " + self._owner_id_column_name if self._owner_id_column_name else ""}
|
|
129
|
+
FROM {self._session_table}
|
|
130
|
+
WHERE id = @id
|
|
131
|
+
"""
|
|
132
|
+
if self._shard_count > 1:
|
|
133
|
+
sql = f"{sql} AND shard_id = MOD(FARM_FINGERPRINT(@id), {self._shard_count})"
|
|
134
|
+
sql = f"{sql} LIMIT 1"
|
|
135
|
+
params = {"id": session_id}
|
|
136
|
+
rows = self._run_read(sql, params, {"id": SPANNER_PARAM_TYPES.STRING})
|
|
137
|
+
if not rows:
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
row = rows[0]
|
|
141
|
+
state_value = self._decode_state(row[3])
|
|
142
|
+
record: SessionRecord = {
|
|
143
|
+
"id": row[0],
|
|
144
|
+
"app_name": row[1],
|
|
145
|
+
"user_id": row[2],
|
|
146
|
+
"state": state_value,
|
|
147
|
+
"create_time": row[4],
|
|
148
|
+
"update_time": row[5],
|
|
149
|
+
}
|
|
150
|
+
return record
|
|
151
|
+
|
|
152
|
+
async def get_session(self, session_id: str) -> "SessionRecord | None":
|
|
153
|
+
"""Get session by ID."""
|
|
154
|
+
return await async_(self._get_session)(session_id)
|
|
155
|
+
|
|
156
|
+
def _update_session_state(self, session_id: str, state: "dict[str, Any]") -> None:
|
|
157
|
+
params = {"id": session_id, "state": to_json(state)}
|
|
158
|
+
json_type = _json_param_type()
|
|
159
|
+
sql = f"""
|
|
160
|
+
UPDATE {self._session_table}
|
|
161
|
+
SET state = @state, update_time = PENDING_COMMIT_TIMESTAMP()
|
|
162
|
+
WHERE id = @id
|
|
163
|
+
"""
|
|
164
|
+
if self._shard_count > 1:
|
|
165
|
+
sql = f"{sql} AND shard_id = MOD(FARM_FINGERPRINT(@id), {self._shard_count})"
|
|
166
|
+
self._run_write([(sql, params, {"id": SPANNER_PARAM_TYPES.STRING, "state": json_type})])
|
|
167
|
+
|
|
168
|
+
async def update_session_state(self, session_id: str, state: "dict[str, Any]") -> None:
|
|
169
|
+
"""Update session state."""
|
|
170
|
+
await async_(self._update_session_state)(session_id, state)
|
|
171
|
+
|
|
172
|
+
def _list_sessions(self, app_name: str, user_id: "str | None" = None) -> "list[SessionRecord]":
|
|
173
|
+
sql = f"""
|
|
174
|
+
SELECT id, app_name, user_id, state, create_time, update_time{", " + self._owner_id_column_name if self._owner_id_column_name else ""}
|
|
175
|
+
FROM {self._session_table}
|
|
176
|
+
WHERE app_name = @app_name
|
|
177
|
+
"""
|
|
178
|
+
params: dict[str, Any] = {"app_name": app_name}
|
|
179
|
+
types: dict[str, Any] = {"app_name": SPANNER_PARAM_TYPES.STRING}
|
|
180
|
+
if user_id is not None:
|
|
181
|
+
sql = f"{sql} AND user_id = @user_id"
|
|
182
|
+
params["user_id"] = user_id
|
|
183
|
+
types["user_id"] = SPANNER_PARAM_TYPES.STRING
|
|
184
|
+
if self._shard_count > 1:
|
|
185
|
+
sql = f"{sql} AND shard_id = MOD(FARM_FINGERPRINT(id), {self._shard_count})"
|
|
186
|
+
sql = f"{sql} ORDER BY update_time DESC"
|
|
187
|
+
|
|
188
|
+
rows = self._run_read(sql, params, types)
|
|
189
|
+
records: list[SessionRecord] = []
|
|
190
|
+
for row in rows:
|
|
191
|
+
state_value = self._decode_state(row[3])
|
|
192
|
+
record: SessionRecord = {
|
|
193
|
+
"id": row[0],
|
|
194
|
+
"app_name": row[1],
|
|
195
|
+
"user_id": row[2],
|
|
196
|
+
"state": state_value,
|
|
197
|
+
"create_time": row[4],
|
|
198
|
+
"update_time": row[5],
|
|
199
|
+
}
|
|
200
|
+
records.append(record)
|
|
201
|
+
return records
|
|
202
|
+
|
|
203
|
+
async def list_sessions(self, app_name: str, user_id: str | None = None) -> "list[SessionRecord]":
|
|
204
|
+
"""List sessions for an app."""
|
|
205
|
+
return await async_(self._list_sessions)(app_name, user_id)
|
|
206
|
+
|
|
207
|
+
def _delete_session(self, session_id: str) -> None:
|
|
208
|
+
shard_clause = (
|
|
209
|
+
f" AND shard_id = MOD(FARM_FINGERPRINT(@session_id), {self._shard_count})" if self._shard_count > 1 else ""
|
|
210
|
+
)
|
|
211
|
+
delete_events_sql = f"DELETE FROM {self._events_table} WHERE session_id = @session_id{shard_clause}"
|
|
212
|
+
delete_session_sql = f"DELETE FROM {self._session_table} WHERE id = @session_id{shard_clause}"
|
|
213
|
+
params = {"session_id": session_id}
|
|
214
|
+
types = {"session_id": SPANNER_PARAM_TYPES.STRING}
|
|
215
|
+
self._run_write([(delete_events_sql, params, types), (delete_session_sql, params, types)])
|
|
216
|
+
|
|
217
|
+
async def delete_session(self, session_id: str) -> None:
|
|
218
|
+
"""Delete session and associated events."""
|
|
219
|
+
await async_(self._delete_session)(session_id)
|
|
220
|
+
|
|
221
|
+
def _append_event_and_update_state(
|
|
222
|
+
self, event_record: "EventRecord", session_id: str, state: "dict[str, Any]"
|
|
223
|
+
) -> SessionRecord:
|
|
224
|
+
"""Atomically insert an event and update session state in one transaction.
|
|
225
|
+
|
|
226
|
+
Both the event INSERT and the session state UPDATE execute within a single
|
|
227
|
+
Spanner transaction so they succeed or fail together. A follow-up
|
|
228
|
+
single-use read returns the SessionRecord; we can't capture update_time
|
|
229
|
+
inside the write txn because PENDING_COMMIT_TIMESTAMP() only materialises
|
|
230
|
+
on commit.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
event_record: Event record to store.
|
|
234
|
+
session_id: Session whose state should be updated.
|
|
235
|
+
state: Post-append durable state snapshot.
|
|
236
|
+
"""
|
|
237
|
+
event_params: dict[str, Any] = {
|
|
238
|
+
"session_id": event_record["session_id"],
|
|
239
|
+
"invocation_id": event_record["invocation_id"],
|
|
240
|
+
"author": event_record["author"],
|
|
241
|
+
"timestamp": event_record["timestamp"],
|
|
242
|
+
"event_json": to_json(event_record["event_json"]),
|
|
243
|
+
}
|
|
244
|
+
insert_sql = f"""
|
|
245
|
+
INSERT INTO {self._events_table} (session_id, invocation_id, author, timestamp, event_json)
|
|
246
|
+
VALUES (@session_id, @invocation_id, @author, @timestamp, @event_json)
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
json_type = _json_param_type()
|
|
250
|
+
state_params: dict[str, Any] = {"id": session_id, "state": to_json(state)}
|
|
251
|
+
update_sql = f"""
|
|
252
|
+
UPDATE {self._session_table}
|
|
253
|
+
SET state = @state, update_time = PENDING_COMMIT_TIMESTAMP()
|
|
254
|
+
WHERE id = @id
|
|
255
|
+
"""
|
|
256
|
+
if self._shard_count > 1:
|
|
257
|
+
update_sql = f"{update_sql} AND shard_id = MOD(FARM_FINGERPRINT(@id), {self._shard_count})"
|
|
258
|
+
|
|
259
|
+
self._run_write([
|
|
260
|
+
(insert_sql, event_params, self._event_param_types()),
|
|
261
|
+
(update_sql, state_params, {"id": SPANNER_PARAM_TYPES.STRING, "state": json_type}),
|
|
262
|
+
])
|
|
263
|
+
|
|
264
|
+
record = self._get_session(session_id)
|
|
265
|
+
if record is None:
|
|
266
|
+
msg = f"Session {session_id} not found during append_event_and_update_state."
|
|
267
|
+
raise ValueError(msg)
|
|
268
|
+
return record
|
|
269
|
+
|
|
270
|
+
async def append_event_and_update_state(
|
|
271
|
+
self, event_record: EventRecord, session_id: str, state: "dict[str, Any]"
|
|
272
|
+
) -> SessionRecord:
|
|
273
|
+
"""Atomically append an event and update the session's durable state."""
|
|
274
|
+
return await async_(self._append_event_and_update_state)(event_record, session_id, state)
|
|
275
|
+
|
|
276
|
+
def _insert_event(self, event_record: "EventRecord") -> None:
|
|
277
|
+
event_params: dict[str, Any] = {
|
|
278
|
+
"session_id": event_record["session_id"],
|
|
279
|
+
"invocation_id": event_record["invocation_id"],
|
|
280
|
+
"author": event_record["author"],
|
|
281
|
+
"timestamp": event_record["timestamp"],
|
|
282
|
+
"event_json": to_json(event_record["event_json"]),
|
|
283
|
+
}
|
|
284
|
+
insert_sql = f"""
|
|
285
|
+
INSERT INTO {self._events_table} (session_id, invocation_id, author, timestamp, event_json)
|
|
286
|
+
VALUES (@session_id, @invocation_id, @author, @timestamp, @event_json)
|
|
287
|
+
"""
|
|
288
|
+
self._run_write([(insert_sql, event_params, self._event_param_types())])
|
|
289
|
+
|
|
290
|
+
def _get_events(
|
|
291
|
+
self, session_id: str, after_timestamp: "datetime | None" = None, limit: "int | None" = None
|
|
292
|
+
) -> "list[EventRecord]":
|
|
293
|
+
sql = f"""
|
|
294
|
+
SELECT session_id, invocation_id, author, timestamp, event_json
|
|
295
|
+
FROM {self._events_table}
|
|
296
|
+
WHERE session_id = @session_id
|
|
297
|
+
"""
|
|
298
|
+
if self._shard_count > 1:
|
|
299
|
+
sql = f"{sql} AND shard_id = MOD(FARM_FINGERPRINT(@session_id), {self._shard_count})"
|
|
300
|
+
params: dict[str, Any] = {"session_id": session_id}
|
|
301
|
+
types: dict[str, Any] = {"session_id": SPANNER_PARAM_TYPES.STRING}
|
|
302
|
+
if after_timestamp is not None:
|
|
303
|
+
sql = f"{sql} AND timestamp > @after_timestamp"
|
|
304
|
+
params["after_timestamp"] = after_timestamp
|
|
305
|
+
types["after_timestamp"] = SPANNER_PARAM_TYPES.TIMESTAMP
|
|
306
|
+
sql = f"{sql} ORDER BY timestamp ASC"
|
|
307
|
+
if limit is not None:
|
|
308
|
+
sql = f"{sql} LIMIT @limit"
|
|
309
|
+
params["limit"] = limit
|
|
310
|
+
types["limit"] = SPANNER_PARAM_TYPES.INT64
|
|
311
|
+
rows = self._run_read(sql, params, types)
|
|
312
|
+
return [
|
|
313
|
+
{
|
|
314
|
+
"session_id": row[0],
|
|
315
|
+
"invocation_id": row[1] or "",
|
|
316
|
+
"author": row[2] or "",
|
|
317
|
+
"timestamp": row[3],
|
|
318
|
+
"event_json": row[4],
|
|
319
|
+
}
|
|
320
|
+
for row in rows
|
|
321
|
+
]
|
|
322
|
+
|
|
323
|
+
async def get_events(
|
|
324
|
+
self, session_id: str, after_timestamp: "datetime | None" = None, limit: "int | None" = None
|
|
325
|
+
) -> "list[EventRecord]":
|
|
326
|
+
"""Get events for a session."""
|
|
327
|
+
return await async_(self._get_events)(session_id, after_timestamp, limit)
|
|
328
|
+
|
|
329
|
+
def _append_event(self, event_record: EventRecord) -> None:
|
|
330
|
+
"""Synchronous implementation of append_event."""
|
|
331
|
+
self._insert_event(event_record)
|
|
332
|
+
|
|
333
|
+
async def append_event(self, event_record: EventRecord) -> None:
|
|
334
|
+
"""Append an event to a session."""
|
|
335
|
+
await async_(self._append_event)(event_record)
|
|
336
|
+
|
|
337
|
+
def _create_tables(self) -> None:
|
|
338
|
+
database = self._database()
|
|
339
|
+
existing_tables = {t.table_id for t in database.list_tables()} # type: ignore[no-untyped-call]
|
|
340
|
+
|
|
341
|
+
ddl_statements: list[str] = []
|
|
342
|
+
if self._session_table not in existing_tables:
|
|
343
|
+
ddl_statements.append(run_(self._get_create_sessions_table_sql)())
|
|
344
|
+
if self._events_table not in existing_tables:
|
|
345
|
+
ddl_statements.append(run_(self._get_create_events_table_sql)())
|
|
346
|
+
|
|
347
|
+
if ddl_statements:
|
|
348
|
+
database.update_ddl(ddl_statements).result(300) # type: ignore[no-untyped-call]
|
|
349
|
+
|
|
350
|
+
async def create_tables(self) -> None:
|
|
351
|
+
"""Create tables if they don't exist."""
|
|
352
|
+
await async_(self._create_tables)()
|
|
353
|
+
|
|
354
|
+
async def _get_create_sessions_table_sql(self) -> str:
|
|
355
|
+
owner_line = ""
|
|
356
|
+
if self._owner_id_column_ddl:
|
|
357
|
+
owner_line = f",\n {self._owner_id_column_ddl}"
|
|
358
|
+
shard_column = ""
|
|
359
|
+
pk = "PRIMARY KEY (id)"
|
|
360
|
+
if self._shard_count > 1:
|
|
361
|
+
shard_column = f",\n shard_id INT64 AS (MOD(FARM_FINGERPRINT(id), {self._shard_count})) STORED"
|
|
362
|
+
pk = "PRIMARY KEY (shard_id, id)"
|
|
363
|
+
options = ""
|
|
364
|
+
if self._session_table_options:
|
|
365
|
+
options = f"\nOPTIONS ({self._session_table_options})"
|
|
366
|
+
return f"""
|
|
367
|
+
CREATE TABLE {self._session_table} (
|
|
368
|
+
id STRING(128) NOT NULL,
|
|
369
|
+
app_name STRING(128) NOT NULL,
|
|
370
|
+
user_id STRING(128) NOT NULL{owner_line},
|
|
371
|
+
state JSON NOT NULL,
|
|
372
|
+
create_time TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
|
|
373
|
+
update_time TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true){shard_column}
|
|
374
|
+
) {pk}{options}{self._session_row_deletion_policy}
|
|
375
|
+
"""
|
|
376
|
+
|
|
377
|
+
async def _get_create_events_table_sql(self) -> str:
|
|
378
|
+
shard_column = ""
|
|
379
|
+
pk = "PRIMARY KEY (session_id, timestamp)"
|
|
380
|
+
if self._shard_count > 1:
|
|
381
|
+
shard_column = f",\n shard_id INT64 AS (MOD(FARM_FINGERPRINT(session_id), {self._shard_count})) STORED"
|
|
382
|
+
pk = "PRIMARY KEY (shard_id, session_id, timestamp)"
|
|
383
|
+
options = ""
|
|
384
|
+
if self._events_table_options:
|
|
385
|
+
options = f"\nOPTIONS ({self._events_table_options})"
|
|
386
|
+
return f"""
|
|
387
|
+
CREATE TABLE {self._events_table} (
|
|
388
|
+
session_id STRING(128) NOT NULL,
|
|
389
|
+
invocation_id STRING(256) NOT NULL,
|
|
390
|
+
author STRING(128) NOT NULL,
|
|
391
|
+
timestamp TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
|
|
392
|
+
event_json JSON NOT NULL{shard_column}
|
|
393
|
+
) {pk}{options}{self._events_row_deletion_policy}
|
|
394
|
+
"""
|
|
395
|
+
|
|
396
|
+
def _get_drop_tables_sql(self) -> "list[str]":
|
|
397
|
+
return [f"DROP TABLE {self._events_table}", f"DROP TABLE {self._session_table}"]
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class SpannerSyncADKMemoryStore(BaseAsyncADKMemoryStore[SpannerSyncConfig]):
|
|
401
|
+
"""Spanner ADK memory store backed by synchronous Spanner client."""
|
|
402
|
+
|
|
403
|
+
connector_name: ClassVar[str] = "spanner"
|
|
404
|
+
|
|
405
|
+
def __init__(self, config: SpannerSyncConfig) -> None:
|
|
406
|
+
super().__init__(config)
|
|
407
|
+
adk_config = cast("ADKConfig", config.extension_config.get("adk", {}))
|
|
408
|
+
shard_count = adk_config.get("shard_count")
|
|
409
|
+
self._shard_count = int(shard_count) if isinstance(shard_count, int) else 0
|
|
410
|
+
self._memory_table_options: str | None = adk_config.get("memory_table_options")
|
|
411
|
+
self._memory_row_deletion_policy = _spanner_row_deletion_policy(
|
|
412
|
+
cast("dict[str, Any]", adk_config), "memory_ttl_seconds", "inserted_at"
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
def _database(self) -> "Database":
|
|
416
|
+
return self._config.get_database()
|
|
417
|
+
|
|
418
|
+
def _run_read(
|
|
419
|
+
self, sql: str, params: "dict[str, Any] | None" = None, types: "dict[str, Any] | None" = None
|
|
420
|
+
) -> "list[Any]":
|
|
421
|
+
with self._config.provide_connection() as snapshot:
|
|
422
|
+
reader = cast("_SpannerReadProtocol", snapshot)
|
|
423
|
+
result_set = reader.execute_sql(sql, params=params, param_types=types)
|
|
424
|
+
return list(result_set)
|
|
425
|
+
|
|
426
|
+
def _run_write(self, statements: "list[tuple[str, dict[str, Any], dict[str, Any]]]") -> None:
|
|
427
|
+
self._database().run_in_transaction(_SpannerMemoryWriteJob(statements)) # type: ignore[no-untyped-call]
|
|
428
|
+
|
|
429
|
+
def _execute_update(self, sql: str, params: "dict[str, Any]", types: "dict[str, Any]") -> int:
|
|
430
|
+
return int(self._database().run_in_transaction(_SpannerMemoryUpdateJob(sql, params, types))) # type: ignore[no-untyped-call]
|
|
431
|
+
|
|
432
|
+
def _memory_param_types(self, include_owner: bool) -> "dict[str, Any]":
|
|
433
|
+
types: dict[str, Any] = {
|
|
434
|
+
"id": SPANNER_PARAM_TYPES.STRING,
|
|
435
|
+
"session_id": SPANNER_PARAM_TYPES.STRING,
|
|
436
|
+
"app_name": SPANNER_PARAM_TYPES.STRING,
|
|
437
|
+
"user_id": SPANNER_PARAM_TYPES.STRING,
|
|
438
|
+
"event_id": SPANNER_PARAM_TYPES.STRING,
|
|
439
|
+
"author": SPANNER_PARAM_TYPES.STRING,
|
|
440
|
+
"timestamp": SPANNER_PARAM_TYPES.TIMESTAMP,
|
|
441
|
+
"content_json": _json_param_type(),
|
|
442
|
+
"content_text": SPANNER_PARAM_TYPES.STRING,
|
|
443
|
+
"metadata_json": _json_param_type(),
|
|
444
|
+
"inserted_at": SPANNER_PARAM_TYPES.TIMESTAMP,
|
|
445
|
+
}
|
|
446
|
+
if include_owner and self._owner_id_column_name:
|
|
447
|
+
types["owner_id"] = SPANNER_PARAM_TYPES.STRING
|
|
448
|
+
return types
|
|
449
|
+
|
|
450
|
+
def _decode_json(self, raw: Any) -> Any:
|
|
451
|
+
if raw is None:
|
|
452
|
+
return None
|
|
453
|
+
if isinstance(raw, str):
|
|
454
|
+
return from_json(raw)
|
|
455
|
+
return raw
|
|
456
|
+
|
|
457
|
+
def _create_tables(self) -> None:
|
|
458
|
+
if not self._enabled:
|
|
459
|
+
return
|
|
460
|
+
|
|
461
|
+
database = self._database()
|
|
462
|
+
existing_tables = {t.table_id for t in database.list_tables()} # type: ignore[no-untyped-call]
|
|
463
|
+
|
|
464
|
+
ddl_statements: list[str] = []
|
|
465
|
+
if self._memory_table not in existing_tables:
|
|
466
|
+
ddl_statements.extend(run_(self._get_create_memory_table_sql)())
|
|
467
|
+
|
|
468
|
+
if ddl_statements:
|
|
469
|
+
database.update_ddl(ddl_statements).result(300) # type: ignore[no-untyped-call]
|
|
470
|
+
|
|
471
|
+
async def create_tables(self) -> None:
|
|
472
|
+
"""Create tables if they don't exist."""
|
|
473
|
+
await async_(self._create_tables)()
|
|
474
|
+
|
|
475
|
+
async def _get_create_memory_table_sql(self) -> "list[str]":
|
|
476
|
+
owner_line = ""
|
|
477
|
+
if self._owner_id_column_ddl:
|
|
478
|
+
owner_line = f",\n {self._owner_id_column_ddl}"
|
|
479
|
+
|
|
480
|
+
fts_column_line = ""
|
|
481
|
+
fts_index = ""
|
|
482
|
+
if self._use_fts:
|
|
483
|
+
fts_column_line = "\n content_tokens TOKENLIST AS (TOKENIZE_FULLTEXT(content_text)) HIDDEN"
|
|
484
|
+
fts_index = f"CREATE SEARCH INDEX idx_{self._memory_table}_fts ON {self._memory_table}(content_tokens)"
|
|
485
|
+
|
|
486
|
+
shard_column = ""
|
|
487
|
+
pk = "PRIMARY KEY (id)"
|
|
488
|
+
if self._shard_count > 1:
|
|
489
|
+
shard_column = f",\n shard_id INT64 AS (MOD(FARM_FINGERPRINT(id), {self._shard_count})) STORED"
|
|
490
|
+
pk = "PRIMARY KEY (shard_id, id)"
|
|
491
|
+
options = ""
|
|
492
|
+
if self._memory_table_options:
|
|
493
|
+
options = f"\nOPTIONS ({self._memory_table_options})"
|
|
494
|
+
|
|
495
|
+
table_sql = f"""
|
|
496
|
+
CREATE TABLE {self._memory_table} (
|
|
497
|
+
id STRING(128) NOT NULL,
|
|
498
|
+
session_id STRING(128) NOT NULL,
|
|
499
|
+
app_name STRING(128) NOT NULL,
|
|
500
|
+
user_id STRING(128) NOT NULL,
|
|
501
|
+
event_id STRING(128) NOT NULL,
|
|
502
|
+
author STRING(256){owner_line},
|
|
503
|
+
timestamp TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
|
|
504
|
+
content_json JSON NOT NULL,
|
|
505
|
+
content_text STRING(MAX) NOT NULL,
|
|
506
|
+
metadata_json JSON,
|
|
507
|
+
inserted_at TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true){fts_column_line}{shard_column}
|
|
508
|
+
) {pk}{options}{self._memory_row_deletion_policy}
|
|
509
|
+
"""
|
|
510
|
+
|
|
511
|
+
app_user_idx = (
|
|
512
|
+
f"CREATE INDEX idx_{self._memory_table}_app_user_time "
|
|
513
|
+
f"ON {self._memory_table}(app_name, user_id, timestamp DESC)"
|
|
514
|
+
)
|
|
515
|
+
session_idx = f"CREATE INDEX idx_{self._memory_table}_session ON {self._memory_table}(session_id)"
|
|
516
|
+
|
|
517
|
+
statements = [table_sql, app_user_idx, session_idx]
|
|
518
|
+
if fts_index:
|
|
519
|
+
statements.append(fts_index)
|
|
520
|
+
return statements
|
|
521
|
+
|
|
522
|
+
def _get_drop_memory_table_sql(self) -> "list[str]":
|
|
523
|
+
"""Get SQL to drop the memory table and its indexes.
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
List of SQL statements to drop the memory table and associated indexes.
|
|
527
|
+
"""
|
|
528
|
+
statements: list[str] = []
|
|
529
|
+
if self._use_fts:
|
|
530
|
+
statements.append(f"DROP SEARCH INDEX idx_{self._memory_table}_fts")
|
|
531
|
+
statements.extend([
|
|
532
|
+
f"DROP INDEX idx_{self._memory_table}_session",
|
|
533
|
+
f"DROP INDEX idx_{self._memory_table}_app_user_time",
|
|
534
|
+
f"DROP TABLE {self._memory_table}",
|
|
535
|
+
])
|
|
536
|
+
return statements
|
|
537
|
+
|
|
538
|
+
def _insert_memory_entries(self, entries: "list[MemoryRecord]", owner_id: "object | None" = None) -> int:
|
|
539
|
+
if not self._enabled:
|
|
540
|
+
msg = "Memory store is disabled"
|
|
541
|
+
raise RuntimeError(msg)
|
|
542
|
+
|
|
543
|
+
if not entries:
|
|
544
|
+
return 0
|
|
545
|
+
|
|
546
|
+
inserted_count = 0
|
|
547
|
+
statements: list[tuple[str, dict[str, Any], dict[str, Any]]] = []
|
|
548
|
+
|
|
549
|
+
owner_column = f", {self._owner_id_column_name}" if self._owner_id_column_name else ""
|
|
550
|
+
owner_param = ", @owner_id" if self._owner_id_column_name else ""
|
|
551
|
+
|
|
552
|
+
insert_sql = f"""
|
|
553
|
+
INSERT INTO {self._memory_table} (
|
|
554
|
+
id, session_id, app_name, user_id, event_id, author{owner_column},
|
|
555
|
+
timestamp, content_json, content_text, metadata_json, inserted_at
|
|
556
|
+
) VALUES (
|
|
557
|
+
@id, @session_id, @app_name, @user_id, @event_id, @author{owner_param},
|
|
558
|
+
@timestamp, @content_json, @content_text, @metadata_json, @inserted_at
|
|
559
|
+
)
|
|
560
|
+
"""
|
|
561
|
+
|
|
562
|
+
for entry in entries:
|
|
563
|
+
if self._event_exists(entry["event_id"]):
|
|
564
|
+
continue
|
|
565
|
+
params = {
|
|
566
|
+
"id": entry["id"],
|
|
567
|
+
"session_id": entry["session_id"],
|
|
568
|
+
"app_name": entry["app_name"],
|
|
569
|
+
"user_id": entry["user_id"],
|
|
570
|
+
"event_id": entry["event_id"],
|
|
571
|
+
"author": entry["author"],
|
|
572
|
+
"timestamp": entry["timestamp"],
|
|
573
|
+
"content_json": to_json(entry["content_json"]),
|
|
574
|
+
"content_text": entry["content_text"],
|
|
575
|
+
"metadata_json": to_json(entry["metadata_json"]) if entry["metadata_json"] is not None else None,
|
|
576
|
+
"inserted_at": entry["inserted_at"],
|
|
577
|
+
}
|
|
578
|
+
if self._owner_id_column_name:
|
|
579
|
+
params["owner_id"] = str(owner_id) if owner_id is not None else None
|
|
580
|
+
statements.append((insert_sql, params, self._memory_param_types(self._owner_id_column_name is not None)))
|
|
581
|
+
inserted_count += 1
|
|
582
|
+
|
|
583
|
+
if statements:
|
|
584
|
+
self._run_write(statements)
|
|
585
|
+
return inserted_count
|
|
586
|
+
|
|
587
|
+
async def insert_memory_entries(self, entries: "list[MemoryRecord]", owner_id: "object | None" = None) -> int:
|
|
588
|
+
"""Bulk insert memory entries with deduplication."""
|
|
589
|
+
return await async_(self._insert_memory_entries)(entries, owner_id)
|
|
590
|
+
|
|
591
|
+
def _event_exists(self, event_id: str) -> bool:
|
|
592
|
+
sql = f"SELECT event_id FROM {self._memory_table} WHERE event_id = @event_id LIMIT 1"
|
|
593
|
+
rows = self._run_read(sql, {"event_id": event_id}, {"event_id": SPANNER_PARAM_TYPES.STRING})
|
|
594
|
+
return bool(rows)
|
|
595
|
+
|
|
596
|
+
def _search_entries(
|
|
597
|
+
self, query: str, app_name: str, user_id: str, limit: "int | None" = None
|
|
598
|
+
) -> "list[MemoryRecord]":
|
|
599
|
+
if not self._enabled:
|
|
600
|
+
msg = "Memory store is disabled"
|
|
601
|
+
raise RuntimeError(msg)
|
|
602
|
+
|
|
603
|
+
effective_limit = limit if limit is not None else self._max_results
|
|
604
|
+
|
|
605
|
+
if self._use_fts:
|
|
606
|
+
return self._search_entries_fts(query, app_name, user_id, effective_limit)
|
|
607
|
+
return self._search_entries_simple(query, app_name, user_id, effective_limit)
|
|
608
|
+
|
|
609
|
+
async def search_entries(
|
|
610
|
+
self, query: str, app_name: str, user_id: str, limit: "int | None" = None
|
|
611
|
+
) -> "list[MemoryRecord]":
|
|
612
|
+
"""Search memory entries by text query."""
|
|
613
|
+
return await async_(self._search_entries)(query, app_name, user_id, limit)
|
|
614
|
+
|
|
615
|
+
def _search_entries_fts(self, query: str, app_name: str, user_id: str, limit: int) -> "list[MemoryRecord]":
|
|
616
|
+
sql = f"""
|
|
617
|
+
SELECT id, session_id, app_name, user_id, event_id, author,
|
|
618
|
+
timestamp, content_json, content_text, metadata_json, inserted_at
|
|
619
|
+
FROM {self._memory_table}
|
|
620
|
+
WHERE app_name = @app_name
|
|
621
|
+
AND user_id = @user_id
|
|
622
|
+
AND SEARCH(content_tokens, @query)
|
|
623
|
+
ORDER BY timestamp DESC
|
|
624
|
+
LIMIT @limit
|
|
625
|
+
"""
|
|
626
|
+
params = {"app_name": app_name, "user_id": user_id, "query": query, "limit": limit}
|
|
627
|
+
types = {
|
|
628
|
+
"app_name": SPANNER_PARAM_TYPES.STRING,
|
|
629
|
+
"user_id": SPANNER_PARAM_TYPES.STRING,
|
|
630
|
+
"query": SPANNER_PARAM_TYPES.STRING,
|
|
631
|
+
"limit": SPANNER_PARAM_TYPES.INT64,
|
|
632
|
+
}
|
|
633
|
+
rows = self._run_read(sql, params, types)
|
|
634
|
+
return self._rows_to_records(rows)
|
|
635
|
+
|
|
636
|
+
def _search_entries_simple(self, query: str, app_name: str, user_id: str, limit: int) -> "list[MemoryRecord]":
|
|
637
|
+
sql = f"""
|
|
638
|
+
SELECT id, session_id, app_name, user_id, event_id, author,
|
|
639
|
+
timestamp, content_json, content_text, metadata_json, inserted_at
|
|
640
|
+
FROM {self._memory_table}
|
|
641
|
+
WHERE app_name = @app_name
|
|
642
|
+
AND user_id = @user_id
|
|
643
|
+
AND LOWER(content_text) LIKE @pattern
|
|
644
|
+
ORDER BY timestamp DESC
|
|
645
|
+
LIMIT @limit
|
|
646
|
+
"""
|
|
647
|
+
pattern = f"%{query.lower()}%"
|
|
648
|
+
params = {"app_name": app_name, "user_id": user_id, "pattern": pattern, "limit": limit}
|
|
649
|
+
types = {
|
|
650
|
+
"app_name": SPANNER_PARAM_TYPES.STRING,
|
|
651
|
+
"user_id": SPANNER_PARAM_TYPES.STRING,
|
|
652
|
+
"pattern": SPANNER_PARAM_TYPES.STRING,
|
|
653
|
+
"limit": SPANNER_PARAM_TYPES.INT64,
|
|
654
|
+
}
|
|
655
|
+
rows = self._run_read(sql, params, types)
|
|
656
|
+
return self._rows_to_records(rows)
|
|
657
|
+
|
|
658
|
+
def _delete_entries_by_session(self, session_id: str) -> int:
|
|
659
|
+
sql = f"DELETE FROM {self._memory_table} WHERE session_id = @session_id"
|
|
660
|
+
params = {"session_id": session_id}
|
|
661
|
+
types = {"session_id": SPANNER_PARAM_TYPES.STRING}
|
|
662
|
+
return self._execute_update(sql, params, types)
|
|
663
|
+
|
|
664
|
+
async def delete_entries_by_session(self, session_id: str) -> int:
|
|
665
|
+
"""Delete all memory entries for a specific session."""
|
|
666
|
+
return await async_(self._delete_entries_by_session)(session_id)
|
|
667
|
+
|
|
668
|
+
def _delete_entries_older_than(self, days: int) -> int:
|
|
669
|
+
cutoff = datetime.now(timezone.utc) - timedelta(days=days)
|
|
670
|
+
sql = f"DELETE FROM {self._memory_table} WHERE inserted_at < @cutoff"
|
|
671
|
+
params = {"cutoff": cutoff}
|
|
672
|
+
types = {"cutoff": SPANNER_PARAM_TYPES.TIMESTAMP}
|
|
673
|
+
return self._execute_update(sql, params, types)
|
|
674
|
+
|
|
675
|
+
async def delete_entries_older_than(self, days: int) -> int:
|
|
676
|
+
"""Delete memory entries older than specified days."""
|
|
677
|
+
return await async_(self._delete_entries_older_than)(days)
|
|
678
|
+
|
|
679
|
+
def _rows_to_records(self, rows: "list[Any]") -> "list[MemoryRecord]":
|
|
680
|
+
return [
|
|
681
|
+
{
|
|
682
|
+
"id": row[0],
|
|
683
|
+
"session_id": row[1],
|
|
684
|
+
"app_name": row[2],
|
|
685
|
+
"user_id": row[3],
|
|
686
|
+
"event_id": row[4],
|
|
687
|
+
"author": row[5],
|
|
688
|
+
"timestamp": row[6],
|
|
689
|
+
"content_json": self._decode_json(row[7]),
|
|
690
|
+
"content_text": row[8],
|
|
691
|
+
"metadata_json": self._decode_json(row[9]),
|
|
692
|
+
"inserted_at": row[10],
|
|
693
|
+
}
|
|
694
|
+
for row in rows
|
|
695
|
+
]
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
def _json_param_type() -> Any:
|
|
699
|
+
try:
|
|
700
|
+
return SPANNER_PARAM_TYPES.JSON
|
|
701
|
+
except AttributeError:
|
|
702
|
+
return SPANNER_PARAM_TYPES.STRING
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def _spanner_ttl_days(ttl_seconds: Any) -> int:
|
|
706
|
+
if not isinstance(ttl_seconds, int) or ttl_seconds <= 0:
|
|
707
|
+
return 0
|
|
708
|
+
return max(1, (ttl_seconds + 86_399) // 86_400)
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def _spanner_row_deletion_policy(adk_config: dict[str, Any], ttl_key: str, column: str) -> str:
|
|
712
|
+
retention = adk_config.get("retention")
|
|
713
|
+
if not isinstance(retention, dict):
|
|
714
|
+
return ""
|
|
715
|
+
ttl_days = _spanner_ttl_days(retention.get(ttl_key))
|
|
716
|
+
if ttl_days == 0:
|
|
717
|
+
return ""
|
|
718
|
+
return f"\nROW DELETION POLICY (OLDER_THAN({column}, INTERVAL {ttl_days} DAY))"
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
class _SpannerWriteJob:
|
|
722
|
+
__slots__ = ("_statements",)
|
|
723
|
+
|
|
724
|
+
def __init__(self, statements: "list[tuple[str, dict[str, Any], dict[str, Any]]]") -> None:
|
|
725
|
+
self._statements = statements
|
|
726
|
+
|
|
727
|
+
def __call__(self, transaction: "Transaction") -> None:
|
|
728
|
+
for sql, params, types in self._statements:
|
|
729
|
+
transaction.execute_update(sql, params=params, param_types=types) # type: ignore[no-untyped-call]
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
class _SpannerMemoryWriteJob:
|
|
733
|
+
__slots__ = ("_statements",)
|
|
734
|
+
|
|
735
|
+
def __init__(self, statements: "list[tuple[str, dict[str, Any], dict[str, Any]]]") -> None:
|
|
736
|
+
self._statements = statements
|
|
737
|
+
|
|
738
|
+
def __call__(self, transaction: "Transaction") -> None:
|
|
739
|
+
for sql, params, types in self._statements:
|
|
740
|
+
transaction.execute_update(sql, params=params, param_types=types) # type: ignore[no-untyped-call]
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
class _SpannerMemoryUpdateJob:
|
|
744
|
+
__slots__ = ("_params", "_sql", "_types")
|
|
745
|
+
|
|
746
|
+
def __init__(self, sql: str, params: "dict[str, Any]", types: "dict[str, Any]") -> None:
|
|
747
|
+
self._sql = sql
|
|
748
|
+
self._params = params
|
|
749
|
+
self._types = types
|
|
750
|
+
|
|
751
|
+
def __call__(self, transaction: "Transaction") -> int:
|
|
752
|
+
return int(transaction.execute_update(self._sql, params=self._params, param_types=self._types)) # type: ignore[no-untyped-call]
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
class _SpannerReadProtocol(Protocol):
|
|
756
|
+
def execute_sql(
|
|
757
|
+
self, sql: str, params: "dict[str, Any] | None" = None, param_types: "dict[str, Any] | None" = None
|
|
758
|
+
) -> Iterable[Any]: ...
|