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,330 @@
|
|
|
1
|
+
"""DuckDB sync session store for Litestar integration."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timedelta, timezone
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from sqlspec.extensions.litestar.store import BaseSQLSpecStore
|
|
7
|
+
from sqlspec.utils.sync_tools import async_
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from sqlspec.adapters.duckdb.config import DuckDBConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = ("DuckdbStore",)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DuckdbStore(BaseSQLSpecStore["DuckDBConfig"]):
|
|
17
|
+
"""DuckDB session store using synchronous DuckDB driver.
|
|
18
|
+
|
|
19
|
+
Implements server-side session storage for Litestar using DuckDB
|
|
20
|
+
via the synchronous duckdb driver. Uses Litestar's sync_to_thread
|
|
21
|
+
utility to provide an async interface compatible with the Store protocol.
|
|
22
|
+
|
|
23
|
+
Provides efficient session management with:
|
|
24
|
+
- Sync operations wrapped for async compatibility
|
|
25
|
+
- INSERT OR REPLACE for UPSERT functionality
|
|
26
|
+
- Native TIMESTAMP type support
|
|
27
|
+
- Automatic expiration handling
|
|
28
|
+
- Efficient cleanup of expired sessions
|
|
29
|
+
- Columnar storage optimized for analytical queries
|
|
30
|
+
|
|
31
|
+
Note:
|
|
32
|
+
DuckDB is primarily designed for analytical (OLAP) workloads.
|
|
33
|
+
For high-concurrency OLTP session stores, consider PostgreSQL adapters.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
config: DuckDBConfig instance.
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
from sqlspec.adapters.duckdb import DuckDBConfig
|
|
40
|
+
from sqlspec.adapters.duckdb.litestar.store import DuckdbStore
|
|
41
|
+
|
|
42
|
+
config = DuckDBConfig()
|
|
43
|
+
store = DuckdbStore(config)
|
|
44
|
+
await store.create_table()
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
__slots__ = ()
|
|
48
|
+
|
|
49
|
+
def __init__(self, config: "DuckDBConfig") -> None:
|
|
50
|
+
"""Initialize DuckDB session store.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
config: DuckDBConfig instance.
|
|
54
|
+
|
|
55
|
+
Notes:
|
|
56
|
+
Table name is read from config.extension_config["litestar"]["session_table"].
|
|
57
|
+
"""
|
|
58
|
+
super().__init__(config)
|
|
59
|
+
|
|
60
|
+
def _get_create_table_sql(self) -> str:
|
|
61
|
+
"""Get DuckDB CREATE TABLE SQL.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
SQL statement to create the sessions table with proper indexes.
|
|
65
|
+
|
|
66
|
+
Notes:
|
|
67
|
+
- Uses TIMESTAMP type for expires_at (DuckDB native datetime type)
|
|
68
|
+
- TIMESTAMP supports ISO 8601 format and direct comparisons
|
|
69
|
+
- Columnar storage makes this efficient for analytical queries
|
|
70
|
+
- DuckDB does not support partial indexes, so full index is created
|
|
71
|
+
"""
|
|
72
|
+
return f"""
|
|
73
|
+
CREATE TABLE IF NOT EXISTS {self._table_name} (
|
|
74
|
+
session_id VARCHAR PRIMARY KEY,
|
|
75
|
+
data BLOB NOT NULL,
|
|
76
|
+
expires_at TIMESTAMP,
|
|
77
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
78
|
+
updated_at TIMESTAMP DEFAULT NOW()
|
|
79
|
+
);
|
|
80
|
+
CREATE INDEX IF NOT EXISTS idx_{self._table_name}_expires_at
|
|
81
|
+
ON {self._table_name}(expires_at);
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def _get_drop_table_sql(self) -> "list[str]":
|
|
85
|
+
"""Get DuckDB DROP TABLE SQL statements.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
List of SQL statements to drop indexes and table.
|
|
89
|
+
"""
|
|
90
|
+
return [f"DROP INDEX IF EXISTS idx_{self._table_name}_expires_at", f"DROP TABLE IF EXISTS {self._table_name}"]
|
|
91
|
+
|
|
92
|
+
def _datetime_to_timestamp(self, dt: "datetime | None") -> "str | None":
|
|
93
|
+
"""Convert datetime to ISO 8601 string for DuckDB TIMESTAMP storage.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
dt: Datetime to convert (must be UTC-aware).
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
ISO 8601 formatted string, or None if dt is None.
|
|
100
|
+
|
|
101
|
+
Notes:
|
|
102
|
+
DuckDB's TIMESTAMP type accepts ISO 8601 format strings.
|
|
103
|
+
This enables efficient storage and comparison operations.
|
|
104
|
+
"""
|
|
105
|
+
if dt is None:
|
|
106
|
+
return None
|
|
107
|
+
return dt.isoformat()
|
|
108
|
+
|
|
109
|
+
def _timestamp_to_datetime(self, ts: "str | datetime | None") -> "datetime | None":
|
|
110
|
+
"""Convert TIMESTAMP string back to datetime.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
ts: ISO 8601 timestamp string or datetime object.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
UTC-aware datetime, or None if ts is None.
|
|
117
|
+
"""
|
|
118
|
+
if ts is None:
|
|
119
|
+
return None
|
|
120
|
+
if isinstance(ts, datetime):
|
|
121
|
+
if ts.tzinfo is None:
|
|
122
|
+
return ts.replace(tzinfo=timezone.utc)
|
|
123
|
+
return ts
|
|
124
|
+
dt = datetime.fromisoformat(ts)
|
|
125
|
+
if dt.tzinfo is None:
|
|
126
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
127
|
+
return dt
|
|
128
|
+
|
|
129
|
+
def _create_table(self) -> None:
|
|
130
|
+
"""Synchronous implementation of create_table."""
|
|
131
|
+
sql = self._get_create_table_sql()
|
|
132
|
+
with self._config.provide_session() as driver:
|
|
133
|
+
driver.execute_script(sql)
|
|
134
|
+
self._log_table_created()
|
|
135
|
+
|
|
136
|
+
async def create_table(self) -> None:
|
|
137
|
+
"""Create the session table if it doesn't exist."""
|
|
138
|
+
await async_(self._create_table)()
|
|
139
|
+
|
|
140
|
+
def _get(self, key: str, renew_for: "int | timedelta | None" = None) -> "bytes | None":
|
|
141
|
+
"""Synchronous implementation of get."""
|
|
142
|
+
sql = f"""
|
|
143
|
+
SELECT data, expires_at FROM {self._table_name}
|
|
144
|
+
WHERE session_id = ?
|
|
145
|
+
AND (expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP)
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
with self._config.provide_connection() as conn:
|
|
149
|
+
cursor = conn.execute(sql, (key,))
|
|
150
|
+
row = cursor.fetchone()
|
|
151
|
+
|
|
152
|
+
if row is None:
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
data, expires_at_str = row
|
|
156
|
+
|
|
157
|
+
if renew_for is not None and expires_at_str is not None:
|
|
158
|
+
new_expires_at = self._calculate_expires_at(renew_for)
|
|
159
|
+
new_expires_at_str = self._datetime_to_timestamp(new_expires_at)
|
|
160
|
+
if new_expires_at_str is not None:
|
|
161
|
+
update_sql = f"""
|
|
162
|
+
UPDATE {self._table_name}
|
|
163
|
+
SET expires_at = ?, updated_at = NOW()
|
|
164
|
+
WHERE session_id = ?
|
|
165
|
+
"""
|
|
166
|
+
conn.execute(update_sql, (new_expires_at_str, key))
|
|
167
|
+
conn.commit()
|
|
168
|
+
|
|
169
|
+
return bytes(data)
|
|
170
|
+
|
|
171
|
+
async def get(self, key: str, renew_for: "int | timedelta | None" = None) -> "bytes | None":
|
|
172
|
+
"""Get a session value by key.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
key: Session ID to retrieve.
|
|
176
|
+
renew_for: If given, renew the expiry time for this duration.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Session data as bytes if found and not expired, None otherwise.
|
|
180
|
+
"""
|
|
181
|
+
return await async_(self._get)(key, renew_for)
|
|
182
|
+
|
|
183
|
+
def _set(self, key: str, value: "str | bytes", expires_in: "int | timedelta | None" = None) -> None:
|
|
184
|
+
"""Synchronous implementation of set.
|
|
185
|
+
|
|
186
|
+
Notes:
|
|
187
|
+
Stores expires_at as TIMESTAMP (ISO 8601 string) for DuckDB native support.
|
|
188
|
+
Uses INSERT ON CONFLICT instead of INSERT OR REPLACE to ensure all columns
|
|
189
|
+
are properly updated. created_at uses DEFAULT on insert, updated_at gets
|
|
190
|
+
current timestamp on both insert and update.
|
|
191
|
+
"""
|
|
192
|
+
data = self._value_to_bytes(value)
|
|
193
|
+
expires_at = self._calculate_expires_at(expires_in)
|
|
194
|
+
expires_at_str = self._datetime_to_timestamp(expires_at)
|
|
195
|
+
|
|
196
|
+
sql = f"""
|
|
197
|
+
INSERT INTO {self._table_name} (session_id, data, expires_at)
|
|
198
|
+
VALUES (?, ?, ?)
|
|
199
|
+
ON CONFLICT (session_id)
|
|
200
|
+
DO UPDATE SET
|
|
201
|
+
data = EXCLUDED.data,
|
|
202
|
+
expires_at = EXCLUDED.expires_at,
|
|
203
|
+
updated_at = NOW()
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
with self._config.provide_connection() as conn:
|
|
207
|
+
conn.execute(sql, (key, data, expires_at_str))
|
|
208
|
+
conn.commit()
|
|
209
|
+
|
|
210
|
+
async def set(self, key: str, value: "str | bytes", expires_in: "int | timedelta | None" = None) -> None:
|
|
211
|
+
"""Store a session value.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
key: Session ID.
|
|
215
|
+
value: Session data.
|
|
216
|
+
expires_in: Time until expiration.
|
|
217
|
+
"""
|
|
218
|
+
await async_(self._set)(key, value, expires_in)
|
|
219
|
+
|
|
220
|
+
def _delete(self, key: str) -> None:
|
|
221
|
+
"""Synchronous implementation of delete."""
|
|
222
|
+
sql = f"DELETE FROM {self._table_name} WHERE session_id = ?"
|
|
223
|
+
|
|
224
|
+
with self._config.provide_connection() as conn:
|
|
225
|
+
conn.execute(sql, (key,))
|
|
226
|
+
conn.commit()
|
|
227
|
+
|
|
228
|
+
async def delete(self, key: str) -> None:
|
|
229
|
+
"""Delete a session by key.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
key: Session ID to delete.
|
|
233
|
+
"""
|
|
234
|
+
await async_(self._delete)(key)
|
|
235
|
+
|
|
236
|
+
def _delete_all(self) -> None:
|
|
237
|
+
"""Synchronous implementation of delete_all."""
|
|
238
|
+
sql = f"DELETE FROM {self._table_name}"
|
|
239
|
+
|
|
240
|
+
with self._config.provide_connection() as conn:
|
|
241
|
+
conn.execute(sql)
|
|
242
|
+
conn.commit()
|
|
243
|
+
self._log_delete_all()
|
|
244
|
+
|
|
245
|
+
async def delete_all(self) -> None:
|
|
246
|
+
"""Delete all sessions from the store."""
|
|
247
|
+
await async_(self._delete_all)()
|
|
248
|
+
|
|
249
|
+
def _exists(self, key: str) -> bool:
|
|
250
|
+
"""Synchronous implementation of exists."""
|
|
251
|
+
sql = f"""
|
|
252
|
+
SELECT 1 FROM {self._table_name}
|
|
253
|
+
WHERE session_id = ?
|
|
254
|
+
AND (expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP)
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
with self._config.provide_connection() as conn:
|
|
258
|
+
cursor = conn.execute(sql, (key,))
|
|
259
|
+
result = cursor.fetchone()
|
|
260
|
+
return result is not None
|
|
261
|
+
|
|
262
|
+
async def exists(self, key: str) -> bool:
|
|
263
|
+
"""Check if a session key exists and is not expired.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
key: Session ID to check.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
True if the session exists and is not expired.
|
|
270
|
+
"""
|
|
271
|
+
return await async_(self._exists)(key)
|
|
272
|
+
|
|
273
|
+
def _expires_in(self, key: str) -> "int | None":
|
|
274
|
+
"""Synchronous implementation of expires_in."""
|
|
275
|
+
sql = f"""
|
|
276
|
+
SELECT expires_at FROM {self._table_name}
|
|
277
|
+
WHERE session_id = ?
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
with self._config.provide_connection() as conn:
|
|
281
|
+
cursor = conn.execute(sql, (key,))
|
|
282
|
+
row = cursor.fetchone()
|
|
283
|
+
|
|
284
|
+
if row is None or row[0] is None:
|
|
285
|
+
return None
|
|
286
|
+
|
|
287
|
+
expires_at_str = row[0]
|
|
288
|
+
expires_at = self._timestamp_to_datetime(expires_at_str)
|
|
289
|
+
|
|
290
|
+
if expires_at is None:
|
|
291
|
+
return None
|
|
292
|
+
|
|
293
|
+
now = datetime.now(timezone.utc)
|
|
294
|
+
|
|
295
|
+
if expires_at <= now:
|
|
296
|
+
return 0
|
|
297
|
+
|
|
298
|
+
delta = expires_at - now
|
|
299
|
+
return int(delta.total_seconds())
|
|
300
|
+
|
|
301
|
+
async def expires_in(self, key: str) -> "int | None":
|
|
302
|
+
"""Get the time in seconds until the session expires.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
key: Session ID to check.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Seconds until expiration, or None if no expiry or key doesn't exist.
|
|
309
|
+
"""
|
|
310
|
+
return await async_(self._expires_in)(key)
|
|
311
|
+
|
|
312
|
+
def _delete_expired(self) -> int:
|
|
313
|
+
"""Synchronous implementation of delete_expired."""
|
|
314
|
+
sql = f"DELETE FROM {self._table_name} WHERE expires_at <= CURRENT_TIMESTAMP"
|
|
315
|
+
|
|
316
|
+
with self._config.provide_connection() as conn:
|
|
317
|
+
cursor = conn.execute(sql)
|
|
318
|
+
count = cursor.fetchone()
|
|
319
|
+
row_count = count[0] if count else 0
|
|
320
|
+
if row_count > 0:
|
|
321
|
+
self._log_delete_expired(row_count)
|
|
322
|
+
return row_count
|
|
323
|
+
|
|
324
|
+
async def delete_expired(self) -> int:
|
|
325
|
+
"""Delete all expired sessions.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Number of sessions deleted.
|
|
329
|
+
"""
|
|
330
|
+
return await async_(self._delete_expired)()
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""DuckDB connection pool with thread-local connections."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
from contextlib import contextmanager, suppress
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Final, cast
|
|
7
|
+
|
|
8
|
+
import duckdb
|
|
9
|
+
|
|
10
|
+
from sqlspec.adapters.duckdb._typing import DuckDBConnection
|
|
11
|
+
from sqlspec.utils.logging import get_logger
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from collections.abc import Callable, Generator
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
logger = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
DEFAULT_MIN_POOL: Final[int] = 1
|
|
20
|
+
DEFAULT_MAX_POOL: Final[int] = 4
|
|
21
|
+
POOL_TIMEOUT: Final[float] = 30.0
|
|
22
|
+
POOL_RECYCLE: Final[int] = 86400
|
|
23
|
+
HEALTH_CHECK_INTERVAL: Final[float] = 30.0
|
|
24
|
+
|
|
25
|
+
__all__ = ("DuckDBConnectionPool",)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DuckDBConnectionPool:
|
|
29
|
+
"""Thread-local connection manager for DuckDB.
|
|
30
|
+
|
|
31
|
+
Uses thread-local storage to ensure each thread gets its own DuckDB connection,
|
|
32
|
+
preventing the thread-safety issues that cause segmentation faults when
|
|
33
|
+
multiple cursors share the same connection concurrently.
|
|
34
|
+
|
|
35
|
+
This design trades traditional pooling for thread safety, which is essential
|
|
36
|
+
for DuckDB since connections and cursors are not thread-safe.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
__slots__ = (
|
|
40
|
+
"_connection_config",
|
|
41
|
+
"_connection_times",
|
|
42
|
+
"_created_connections",
|
|
43
|
+
"_extension_flags",
|
|
44
|
+
"_extensions",
|
|
45
|
+
"_health_check_interval",
|
|
46
|
+
"_lock",
|
|
47
|
+
"_on_connection_create",
|
|
48
|
+
"_recycle",
|
|
49
|
+
"_secrets",
|
|
50
|
+
"_thread_local",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
connection_config: "dict[str, Any]",
|
|
56
|
+
pool_recycle_seconds: int = POOL_RECYCLE,
|
|
57
|
+
health_check_interval: float = HEALTH_CHECK_INTERVAL,
|
|
58
|
+
extensions: "list[dict[str, Any]] | None" = None,
|
|
59
|
+
extension_flags: "dict[str, Any] | None" = None,
|
|
60
|
+
secrets: "list[dict[str, Any]] | None" = None,
|
|
61
|
+
on_connection_create: "Callable[[DuckDBConnection], None] | None" = None,
|
|
62
|
+
) -> None:
|
|
63
|
+
"""Initialize the thread-local connection manager.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
connection_config: DuckDB connection configuration
|
|
67
|
+
pool_recycle_seconds: Connection recycle time in seconds
|
|
68
|
+
health_check_interval: Seconds of idle time before running health check
|
|
69
|
+
extensions: List of extensions to install/load
|
|
70
|
+
extension_flags: Connection-level SET statements applied after creation
|
|
71
|
+
secrets: List of secrets to create
|
|
72
|
+
on_connection_create: Callback executed when connection is created
|
|
73
|
+
"""
|
|
74
|
+
self._connection_config = connection_config
|
|
75
|
+
self._recycle = pool_recycle_seconds
|
|
76
|
+
self._health_check_interval = health_check_interval
|
|
77
|
+
self._extensions = extensions or []
|
|
78
|
+
self._extension_flags = extension_flags or {}
|
|
79
|
+
self._secrets = secrets or []
|
|
80
|
+
self._on_connection_create = on_connection_create
|
|
81
|
+
self._thread_local = threading.local()
|
|
82
|
+
self._lock = threading.RLock()
|
|
83
|
+
self._created_connections = 0
|
|
84
|
+
self._connection_times: dict[int, float] = {}
|
|
85
|
+
|
|
86
|
+
def _create_connection(self) -> DuckDBConnection:
|
|
87
|
+
"""Create a new DuckDB connection with extensions and secrets."""
|
|
88
|
+
connect_parameters = {}
|
|
89
|
+
config_dict = {}
|
|
90
|
+
|
|
91
|
+
for key, value in self._connection_config.items():
|
|
92
|
+
if key in {"database", "read_only"}:
|
|
93
|
+
connect_parameters[key] = value
|
|
94
|
+
else:
|
|
95
|
+
config_dict[key] = value
|
|
96
|
+
|
|
97
|
+
if config_dict:
|
|
98
|
+
connect_parameters["config"] = config_dict
|
|
99
|
+
|
|
100
|
+
connection = duckdb.connect(**connect_parameters)
|
|
101
|
+
|
|
102
|
+
self._apply_extension_flags(connection)
|
|
103
|
+
|
|
104
|
+
for ext_config in self._extensions:
|
|
105
|
+
ext_name = ext_config.get("name")
|
|
106
|
+
if not ext_name:
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
install_kwargs = {}
|
|
110
|
+
if "version" in ext_config:
|
|
111
|
+
install_kwargs["version"] = ext_config["version"]
|
|
112
|
+
if "repository" in ext_config:
|
|
113
|
+
install_kwargs["repository"] = ext_config["repository"]
|
|
114
|
+
if ext_config.get("force_install", False):
|
|
115
|
+
install_kwargs["force_install"] = True
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
if install_kwargs:
|
|
119
|
+
connection.install_extension(ext_name, **install_kwargs)
|
|
120
|
+
connection.load_extension(ext_name)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.debug("Failed to load DuckDB extension %s: %s", ext_name, e)
|
|
123
|
+
|
|
124
|
+
for secret_config in self._secrets:
|
|
125
|
+
secret_type = secret_config.get("secret_type")
|
|
126
|
+
secret_name = secret_config.get("name")
|
|
127
|
+
secret_value = secret_config.get("value")
|
|
128
|
+
|
|
129
|
+
if not (secret_type and secret_name and secret_value):
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
value_pairs = []
|
|
133
|
+
for key, value in secret_value.items():
|
|
134
|
+
escaped_value = str(value).replace("'", "''")
|
|
135
|
+
value_pairs.append(f"'{key}' = '{escaped_value}'")
|
|
136
|
+
value_string = ", ".join(value_pairs)
|
|
137
|
+
scope_clause = ""
|
|
138
|
+
if "scope" in secret_config:
|
|
139
|
+
scope_clause = f" SCOPE '{secret_config['scope']}'"
|
|
140
|
+
|
|
141
|
+
sql = f"""
|
|
142
|
+
CREATE SECRET {secret_name} (
|
|
143
|
+
TYPE {secret_type},
|
|
144
|
+
{value_string}
|
|
145
|
+
){scope_clause}
|
|
146
|
+
"""
|
|
147
|
+
with suppress(Exception):
|
|
148
|
+
connection.execute(sql)
|
|
149
|
+
|
|
150
|
+
if self._on_connection_create:
|
|
151
|
+
with suppress(Exception):
|
|
152
|
+
self._on_connection_create(connection)
|
|
153
|
+
|
|
154
|
+
conn_id = id(connection)
|
|
155
|
+
with self._lock:
|
|
156
|
+
self._created_connections += 1
|
|
157
|
+
self._connection_times[conn_id] = time.time()
|
|
158
|
+
|
|
159
|
+
return connection
|
|
160
|
+
|
|
161
|
+
def _apply_extension_flags(self, connection: DuckDBConnection) -> None:
|
|
162
|
+
"""Apply connection-level extension flags via SET statements."""
|
|
163
|
+
|
|
164
|
+
if not self._extension_flags:
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
for key, value in self._extension_flags.items():
|
|
168
|
+
if not key or not key.replace("_", "").isalnum():
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
normalized = self._normalize_flag_value(value)
|
|
172
|
+
try:
|
|
173
|
+
connection.execute(f"SET {key} = {normalized}")
|
|
174
|
+
except Exception as exc: # pragma: no cover - best-effort guard
|
|
175
|
+
logger.debug("Failed to set DuckDB flag %s: %s", key, exc)
|
|
176
|
+
|
|
177
|
+
@staticmethod
|
|
178
|
+
def _normalize_flag_value(value: Any) -> str:
|
|
179
|
+
"""Convert Python value to DuckDB SET literal."""
|
|
180
|
+
|
|
181
|
+
if isinstance(value, bool):
|
|
182
|
+
return "TRUE" if value else "FALSE"
|
|
183
|
+
if isinstance(value, (int, float)):
|
|
184
|
+
return str(value)
|
|
185
|
+
escaped = str(value).replace("'", "''")
|
|
186
|
+
return f"'{escaped}'"
|
|
187
|
+
|
|
188
|
+
def _get_thread_connection(self) -> DuckDBConnection:
|
|
189
|
+
"""Get or create a connection for the current thread.
|
|
190
|
+
|
|
191
|
+
Each thread gets its own dedicated DuckDB connection to prevent
|
|
192
|
+
thread-safety issues with concurrent cursor operations.
|
|
193
|
+
"""
|
|
194
|
+
thread_state = self._thread_local.__dict__
|
|
195
|
+
if "connection" not in thread_state:
|
|
196
|
+
self._thread_local.connection = self._create_connection()
|
|
197
|
+
self._thread_local.created_at = time.time()
|
|
198
|
+
self._thread_local.last_used = time.time()
|
|
199
|
+
return cast("DuckDBConnection", self._thread_local.connection)
|
|
200
|
+
|
|
201
|
+
if self._recycle > 0 and time.time() - self._thread_local.created_at > self._recycle:
|
|
202
|
+
with suppress(Exception):
|
|
203
|
+
self._thread_local.connection.close()
|
|
204
|
+
self._thread_local.connection = self._create_connection()
|
|
205
|
+
self._thread_local.created_at = time.time()
|
|
206
|
+
self._thread_local.last_used = time.time()
|
|
207
|
+
return cast("DuckDBConnection", self._thread_local.connection)
|
|
208
|
+
|
|
209
|
+
idle_time = time.time() - thread_state.get("last_used", 0)
|
|
210
|
+
if idle_time > self._health_check_interval and not self._is_connection_alive(self._thread_local.connection):
|
|
211
|
+
logger.debug("DuckDB connection failed health check after %.1fs idle, recreating", idle_time)
|
|
212
|
+
with suppress(Exception):
|
|
213
|
+
self._thread_local.connection.close()
|
|
214
|
+
self._thread_local.connection = self._create_connection()
|
|
215
|
+
self._thread_local.created_at = time.time()
|
|
216
|
+
|
|
217
|
+
self._thread_local.last_used = time.time()
|
|
218
|
+
return cast("DuckDBConnection", self._thread_local.connection)
|
|
219
|
+
|
|
220
|
+
def _close_thread_connection(self) -> None:
|
|
221
|
+
"""Close the connection for the current thread."""
|
|
222
|
+
thread_state = self._thread_local.__dict__
|
|
223
|
+
if "connection" in thread_state:
|
|
224
|
+
with suppress(Exception):
|
|
225
|
+
self._thread_local.connection.close()
|
|
226
|
+
del self._thread_local.connection
|
|
227
|
+
if "created_at" in thread_state:
|
|
228
|
+
del self._thread_local.created_at
|
|
229
|
+
if "last_used" in thread_state:
|
|
230
|
+
del self._thread_local.last_used
|
|
231
|
+
|
|
232
|
+
def _is_connection_alive(self, connection: DuckDBConnection) -> bool:
|
|
233
|
+
"""Check if a connection is still alive and usable.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
connection: Connection to check
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
True if connection is alive, False otherwise
|
|
240
|
+
"""
|
|
241
|
+
try:
|
|
242
|
+
cursor = connection.cursor()
|
|
243
|
+
cursor.close()
|
|
244
|
+
except Exception:
|
|
245
|
+
return False
|
|
246
|
+
return True
|
|
247
|
+
|
|
248
|
+
@contextmanager
|
|
249
|
+
def get_connection(self) -> "Generator[DuckDBConnection, None, None]":
|
|
250
|
+
"""Get a thread-local connection.
|
|
251
|
+
|
|
252
|
+
Each thread gets its own dedicated DuckDB connection to prevent
|
|
253
|
+
thread-safety issues with concurrent cursor operations.
|
|
254
|
+
|
|
255
|
+
Yields:
|
|
256
|
+
DuckDBConnection: A thread-local connection.
|
|
257
|
+
"""
|
|
258
|
+
connection = self._get_thread_connection()
|
|
259
|
+
try:
|
|
260
|
+
yield connection
|
|
261
|
+
except Exception:
|
|
262
|
+
self._close_thread_connection()
|
|
263
|
+
raise
|
|
264
|
+
|
|
265
|
+
def close(self) -> None:
|
|
266
|
+
"""Close the thread-local connection if it exists."""
|
|
267
|
+
self._close_thread_connection()
|
|
268
|
+
|
|
269
|
+
def size(self) -> int:
|
|
270
|
+
"""Get current pool size (always 1 for thread-local)."""
|
|
271
|
+
return 1 if "connection" in self._thread_local.__dict__ else 0
|
|
272
|
+
|
|
273
|
+
def checked_out(self) -> int:
|
|
274
|
+
"""Get number of checked out connections (always 0 for thread-local)."""
|
|
275
|
+
return 0
|
|
276
|
+
|
|
277
|
+
def acquire(self) -> DuckDBConnection:
|
|
278
|
+
"""Acquire a thread-local connection.
|
|
279
|
+
|
|
280
|
+
Each thread gets its own dedicated DuckDB connection to prevent
|
|
281
|
+
thread-safety issues with concurrent cursor operations.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
DuckDBConnection: A thread-local connection
|
|
285
|
+
"""
|
|
286
|
+
return self._get_thread_connection()
|
|
287
|
+
|
|
288
|
+
def release(self, connection: DuckDBConnection) -> None:
|
|
289
|
+
"""Release a connection (no-op for thread-local connections).
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
connection: The connection to release (ignored)
|
|
293
|
+
"""
|
|
Binary file
|