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,1660 @@
|
|
|
1
|
+
"""SELECT statement builder.
|
|
2
|
+
|
|
3
|
+
Provides a fluent interface for building SQL SELECT queries with
|
|
4
|
+
parameter binding and validation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# pyright: reportPrivateUsage=false, reportPrivateImportUsage=false
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Final, Union, cast
|
|
11
|
+
|
|
12
|
+
from mypy_extensions import trait
|
|
13
|
+
from sqlglot import exp
|
|
14
|
+
from typing_extensions import Self
|
|
15
|
+
|
|
16
|
+
from sqlspec.builder._base import BuiltQuery, QueryBuilder
|
|
17
|
+
from sqlspec.builder._explain import ExplainMixin
|
|
18
|
+
from sqlspec.builder._join import JoinClauseMixin
|
|
19
|
+
from sqlspec.builder._parsing_utils import (
|
|
20
|
+
extract_column_name,
|
|
21
|
+
extract_expression,
|
|
22
|
+
parse_column_expression,
|
|
23
|
+
parse_condition_expression,
|
|
24
|
+
parse_order_expression,
|
|
25
|
+
parse_table_expression,
|
|
26
|
+
to_expression,
|
|
27
|
+
)
|
|
28
|
+
from sqlspec.core import SQL, ParameterStyle, ParameterValidator, SQLResult
|
|
29
|
+
from sqlspec.exceptions import SQLBuilderError
|
|
30
|
+
from sqlspec.utils.type_guards import (
|
|
31
|
+
has_expression_and_sql,
|
|
32
|
+
has_parameter_builder,
|
|
33
|
+
has_sqlglot_expression,
|
|
34
|
+
is_expression,
|
|
35
|
+
is_iterable_parameters,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
BETWEEN_BOUND_COUNT = 2
|
|
39
|
+
PAIR_LENGTH = 2
|
|
40
|
+
TRIPLE_LENGTH = 3
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if TYPE_CHECKING:
|
|
44
|
+
from collections.abc import Callable
|
|
45
|
+
|
|
46
|
+
from sqlglot.dialects.dialect import DialectType
|
|
47
|
+
|
|
48
|
+
from sqlspec.builder._column import Column, ColumnExpression, FunctionColumn
|
|
49
|
+
from sqlspec.builder._expression_wrappers import ExpressionWrapper
|
|
50
|
+
from sqlspec.protocols import SQLBuilderProtocol
|
|
51
|
+
|
|
52
|
+
__all__ = (
|
|
53
|
+
"Case",
|
|
54
|
+
"CaseBuilder",
|
|
55
|
+
"CommonTableExpressionMixin",
|
|
56
|
+
"HavingClauseMixin",
|
|
57
|
+
"LimitOffsetClauseMixin",
|
|
58
|
+
"OrderByClauseMixin",
|
|
59
|
+
"PivotClauseMixin",
|
|
60
|
+
"ReturningClauseMixin",
|
|
61
|
+
"Select",
|
|
62
|
+
"SelectClauseMixin",
|
|
63
|
+
"SetOperationMixin",
|
|
64
|
+
"SubqueryBuilder",
|
|
65
|
+
"UnpivotClauseMixin",
|
|
66
|
+
"WhereClauseMixin",
|
|
67
|
+
"WindowFunctionBuilder",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def is_explicitly_quoted(identifier: Any) -> bool:
|
|
72
|
+
"""Detect if identifier was provided with explicit quotes."""
|
|
73
|
+
if not isinstance(identifier, str):
|
|
74
|
+
return False
|
|
75
|
+
stripped = identifier.strip()
|
|
76
|
+
return (stripped.startswith('"') and stripped.endswith('"')) or (
|
|
77
|
+
stripped.startswith("`") and stripped.endswith("`")
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _expr_eq(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
|
|
82
|
+
return exp.EQ(this=col, expression=placeholder)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _expr_neq(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
|
|
86
|
+
return exp.NEQ(this=col, expression=placeholder)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _expr_gt(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
|
|
90
|
+
return exp.GT(this=col, expression=placeholder)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _expr_gte(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
|
|
94
|
+
return exp.GTE(this=col, expression=placeholder)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _expr_lt(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
|
|
98
|
+
return exp.LT(this=col, expression=placeholder)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _expr_lte(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
|
|
102
|
+
return exp.LTE(this=col, expression=placeholder)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _expr_like_exp(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
|
|
106
|
+
return exp.Like(this=col, expression=placeholder)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _expr_like_method(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
|
|
110
|
+
return cast("exp.Expression", col.like(placeholder))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _expr_not_like(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
|
|
114
|
+
return exp.Not(this=exp.Like(this=col, expression=placeholder))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _expr_like_not(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
|
|
118
|
+
return exp.Not(this=cast("exp.Expression", col.like(placeholder)))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _expr_ilike(col: "exp.Expression", placeholder: "exp.Placeholder") -> "exp.Expression":
|
|
122
|
+
return cast("exp.Expression", col.ilike(placeholder))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
_SIMPLE_OPERATOR_MAP: dict[str, Any] = {
|
|
126
|
+
"=": _expr_eq,
|
|
127
|
+
"==": _expr_eq,
|
|
128
|
+
"!=": _expr_neq,
|
|
129
|
+
"<>": _expr_neq,
|
|
130
|
+
">": _expr_gt,
|
|
131
|
+
">=": _expr_gte,
|
|
132
|
+
"<": _expr_lt,
|
|
133
|
+
"<=": _expr_lte,
|
|
134
|
+
"LIKE": _expr_like_exp,
|
|
135
|
+
"NOT LIKE": _expr_not_like,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class Case:
|
|
140
|
+
"""Represent a SQL CASE expression with structured components."""
|
|
141
|
+
|
|
142
|
+
__slots__ = ("conditions", "default")
|
|
143
|
+
|
|
144
|
+
def __init__(self, *ifs: exp.Expression, default: exp.Expression | None = None) -> None:
|
|
145
|
+
self.conditions = list(ifs)
|
|
146
|
+
self.default = default
|
|
147
|
+
|
|
148
|
+
def when(self, condition: str | exp.Expression, result: Any) -> "Case":
|
|
149
|
+
condition_expr = parse_condition_expression(condition)
|
|
150
|
+
result_expr = to_expression(result)
|
|
151
|
+
self.conditions.append(exp.If(this=condition_expr, true=result_expr))
|
|
152
|
+
return self
|
|
153
|
+
|
|
154
|
+
def else_(self, value: Any) -> "Case":
|
|
155
|
+
self.default = to_expression(value)
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
def end(self) -> "Case":
|
|
159
|
+
return self
|
|
160
|
+
|
|
161
|
+
def as_(self, alias: str) -> exp.Alias:
|
|
162
|
+
return cast("exp.Alias", exp.alias_(self.expression, alias))
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def expression(self) -> exp.Case:
|
|
166
|
+
return exp.Case(ifs=self.conditions, default=self.default)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class CaseBuilder:
|
|
170
|
+
"""Fluent builder for CASE expressions used within SELECT clauses."""
|
|
171
|
+
|
|
172
|
+
__slots__ = ()
|
|
173
|
+
|
|
174
|
+
def __call__(self, *args: Any, default: Any | None = None) -> Case:
|
|
175
|
+
conditions = [to_expression(arg) for arg in args]
|
|
176
|
+
default_expr = to_expression(default) if default is not None else None
|
|
177
|
+
return Case(*conditions, default=default_expr)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class SubqueryBuilder:
|
|
181
|
+
"""Helper to build subquery expressions for EXISTS/IN/ANY/ALL operations."""
|
|
182
|
+
|
|
183
|
+
__slots__ = ("_operation",)
|
|
184
|
+
|
|
185
|
+
def __init__(self, operation: str) -> None:
|
|
186
|
+
self._operation = operation
|
|
187
|
+
|
|
188
|
+
def __call__(self, subquery: Any) -> exp.Expression:
|
|
189
|
+
if isinstance(subquery, exp.Expression):
|
|
190
|
+
subquery_expr = subquery
|
|
191
|
+
elif has_parameter_builder(subquery):
|
|
192
|
+
built_query = subquery.build()
|
|
193
|
+
sql_text = built_query.sql if isinstance(built_query, BuiltQuery) else str(built_query)
|
|
194
|
+
dialect = subquery.dialect if isinstance(subquery, QueryBuilder) else None
|
|
195
|
+
parsed_expr: exp.Expression | None = exp.maybe_parse(sql_text, dialect=dialect)
|
|
196
|
+
if parsed_expr is None:
|
|
197
|
+
msg = f"Could not parse subquery SQL: {sql_text}"
|
|
198
|
+
raise SQLBuilderError(msg)
|
|
199
|
+
subquery_expr = parsed_expr
|
|
200
|
+
else:
|
|
201
|
+
dialect = subquery.dialect if isinstance(subquery, (QueryBuilder, BuiltQuery)) else None
|
|
202
|
+
parsed_expr = exp.maybe_parse(str(subquery), dialect=dialect)
|
|
203
|
+
if parsed_expr is None:
|
|
204
|
+
msg = f"Could not convert subquery to expression: {subquery}"
|
|
205
|
+
raise SQLBuilderError(msg)
|
|
206
|
+
subquery_expr = parsed_expr
|
|
207
|
+
|
|
208
|
+
if self._operation == "exists":
|
|
209
|
+
return exp.Exists(this=subquery_expr)
|
|
210
|
+
if self._operation == "in":
|
|
211
|
+
return exp.In(expressions=[subquery_expr])
|
|
212
|
+
if self._operation == "any":
|
|
213
|
+
return exp.Any(this=subquery_expr)
|
|
214
|
+
if self._operation == "all":
|
|
215
|
+
return exp.All(this=subquery_expr)
|
|
216
|
+
msg = f"Unknown subquery operation: {self._operation}"
|
|
217
|
+
raise SQLBuilderError(msg)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class WindowFunctionBuilder:
|
|
221
|
+
"""Helper to fluently construct window function expressions."""
|
|
222
|
+
|
|
223
|
+
__slots__ = ("_function_args", "_function_name", "_order_by", "_partition_by")
|
|
224
|
+
|
|
225
|
+
def __init__(self, function_name: str, *function_args: Any) -> None:
|
|
226
|
+
self._function_name = function_name
|
|
227
|
+
self._function_args: list[exp.Expression] = [to_expression(arg) for arg in function_args]
|
|
228
|
+
self._partition_by: list[exp.Expression] = []
|
|
229
|
+
self._order_by: list[exp.Ordered] = []
|
|
230
|
+
|
|
231
|
+
def __call__(self, *function_args: Any) -> "WindowFunctionBuilder":
|
|
232
|
+
self._function_args = [to_expression(arg) for arg in function_args]
|
|
233
|
+
return self
|
|
234
|
+
|
|
235
|
+
def partition_by(self, *columns: str | exp.Expression) -> "WindowFunctionBuilder":
|
|
236
|
+
self._partition_by = [exp.column(column) if isinstance(column, str) else column for column in columns]
|
|
237
|
+
return self
|
|
238
|
+
|
|
239
|
+
def order_by(self, *columns: str | exp.Expression) -> "WindowFunctionBuilder":
|
|
240
|
+
ordered_columns: list[exp.Ordered] = []
|
|
241
|
+
for column in columns:
|
|
242
|
+
if isinstance(column, str):
|
|
243
|
+
ordered_columns.append(exp.column(column).asc())
|
|
244
|
+
elif isinstance(column, exp.Ordered):
|
|
245
|
+
ordered_columns.append(column)
|
|
246
|
+
else:
|
|
247
|
+
ordered_columns.append(exp.Ordered(this=column, desc=False))
|
|
248
|
+
self._order_by = ordered_columns
|
|
249
|
+
return self
|
|
250
|
+
|
|
251
|
+
def _build_function_expression(self) -> exp.Expression:
|
|
252
|
+
expressions = self._function_args or []
|
|
253
|
+
return exp.Anonymous(this=self._function_name, expressions=expressions)
|
|
254
|
+
|
|
255
|
+
def build(self) -> exp.Window:
|
|
256
|
+
over_args: dict[str, Any] = {}
|
|
257
|
+
if self._partition_by:
|
|
258
|
+
over_args["partition_by"] = self._partition_by
|
|
259
|
+
if self._order_by:
|
|
260
|
+
over_args["order"] = exp.Order(expressions=self._order_by)
|
|
261
|
+
return exp.Window(this=self._build_function_expression(), **over_args)
|
|
262
|
+
|
|
263
|
+
def as_(self, alias: str) -> exp.Alias:
|
|
264
|
+
return cast("exp.Alias", exp.alias_(self.build(), alias))
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _ensure_select_expression(
|
|
268
|
+
mixin: "SQLBuilderProtocol", *, error_message: str, initialize: bool = True
|
|
269
|
+
) -> exp.Select:
|
|
270
|
+
expression = mixin.get_expression()
|
|
271
|
+
if expression is None and initialize:
|
|
272
|
+
mixin.set_expression(exp.Select())
|
|
273
|
+
expression = mixin.get_expression()
|
|
274
|
+
|
|
275
|
+
if not isinstance(expression, exp.Select):
|
|
276
|
+
raise SQLBuilderError(error_message)
|
|
277
|
+
|
|
278
|
+
return expression
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@trait
|
|
282
|
+
class SelectClauseMixin:
|
|
283
|
+
"""Mixin providing SELECT clause methods."""
|
|
284
|
+
|
|
285
|
+
__slots__ = ()
|
|
286
|
+
|
|
287
|
+
def get_expression(self) -> exp.Expression | None: ...
|
|
288
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
289
|
+
|
|
290
|
+
def select(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn", SQL, Case]) -> Self:
|
|
291
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
292
|
+
select_expr = _ensure_select_expression(builder, error_message="Cannot add columns to non-SELECT expression.")
|
|
293
|
+
for column in columns:
|
|
294
|
+
column_expr = column.expression if isinstance(column, Case) else parse_column_expression(column, builder)
|
|
295
|
+
select_expr = select_expr.select(column_expr, copy=False)
|
|
296
|
+
self.set_expression(select_expr)
|
|
297
|
+
return cast("Self", builder)
|
|
298
|
+
|
|
299
|
+
def distinct(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn", SQL]) -> Self:
|
|
300
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
301
|
+
select_expr = _ensure_select_expression(builder, error_message="Cannot add DISTINCT to non-SELECT expression.")
|
|
302
|
+
if not columns:
|
|
303
|
+
select_expr.set("distinct", exp.Distinct())
|
|
304
|
+
else:
|
|
305
|
+
distinct_columns = [parse_column_expression(column, builder) for column in columns]
|
|
306
|
+
select_expr.set("distinct", exp.Distinct(expressions=distinct_columns))
|
|
307
|
+
builder.set_expression(select_expr)
|
|
308
|
+
return cast("Self", builder)
|
|
309
|
+
|
|
310
|
+
def from_(
|
|
311
|
+
self,
|
|
312
|
+
table: str | exp.Expression | Any,
|
|
313
|
+
alias: str | None = None,
|
|
314
|
+
as_of: Any | None = None,
|
|
315
|
+
as_of_type: str | None = None,
|
|
316
|
+
) -> Self:
|
|
317
|
+
"""Set the FROM clause and optionally attach temporal versioning.
|
|
318
|
+
|
|
319
|
+
``as_of`` copies the resolved table expression, normalizes aliases, and adds an ``exp.Version`` so sqlglot's generator emits dialect-specific time-travel SQL.
|
|
320
|
+
"""
|
|
321
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
322
|
+
select_expr = _ensure_select_expression(builder, error_message="FROM clause only valid for SELECT.")
|
|
323
|
+
from_expr: exp.Expression
|
|
324
|
+
|
|
325
|
+
if isinstance(table, str):
|
|
326
|
+
from_expr = parse_table_expression(table, alias)
|
|
327
|
+
elif is_expression(table):
|
|
328
|
+
from_expr = exp.alias_(table, alias) if alias else table
|
|
329
|
+
elif has_parameter_builder(table):
|
|
330
|
+
subquery_expression = table.get_expression()
|
|
331
|
+
if subquery_expression is None:
|
|
332
|
+
msg = "Subquery builder has no expression to include in FROM clause."
|
|
333
|
+
raise SQLBuilderError(msg)
|
|
334
|
+
|
|
335
|
+
subquery_copy = subquery_expression.copy()
|
|
336
|
+
base_builder = cast("QueryBuilder", builder)
|
|
337
|
+
param_mapping = base_builder._merge_cte_parameters(alias or "subquery", table.parameters)
|
|
338
|
+
if param_mapping:
|
|
339
|
+
subquery_copy = base_builder._update_placeholders_in_expression(subquery_copy, param_mapping)
|
|
340
|
+
|
|
341
|
+
wrapped_subquery = exp.paren(subquery_copy)
|
|
342
|
+
from_expr = exp.alias_(wrapped_subquery, alias) if alias else wrapped_subquery
|
|
343
|
+
else:
|
|
344
|
+
from_expr = table
|
|
345
|
+
|
|
346
|
+
if as_of is not None:
|
|
347
|
+
inner_expr = from_expr.copy()
|
|
348
|
+
target_alias = alias
|
|
349
|
+
|
|
350
|
+
if isinstance(inner_expr, exp.Alias):
|
|
351
|
+
target_alias = inner_expr.alias
|
|
352
|
+
inner_expr = inner_expr.this
|
|
353
|
+
|
|
354
|
+
if target_alias is None and isinstance(inner_expr, exp.Table):
|
|
355
|
+
alias_expr = inner_expr.args.get("alias")
|
|
356
|
+
if alias_expr is not None:
|
|
357
|
+
target_alias = alias_expr.this
|
|
358
|
+
inner_expr.set("alias", None)
|
|
359
|
+
|
|
360
|
+
version = exp.Version(this=as_of_type or "TIMESTAMP", kind="AS OF", expression=exp.convert(as_of))
|
|
361
|
+
inner_expr.set("version", version)
|
|
362
|
+
from_expr = exp.alias_(inner_expr, target_alias) if target_alias else inner_expr
|
|
363
|
+
|
|
364
|
+
builder.set_expression(select_expr.from_(from_expr, copy=False))
|
|
365
|
+
return cast("Self", builder)
|
|
366
|
+
|
|
367
|
+
def group_by(self, *columns: str | exp.Expression) -> Self:
|
|
368
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
369
|
+
select_expr = builder.get_expression()
|
|
370
|
+
if select_expr is None or not isinstance(select_expr, exp.Select):
|
|
371
|
+
return cast("Self", builder)
|
|
372
|
+
|
|
373
|
+
for column in columns:
|
|
374
|
+
column_expr = exp.column(column) if isinstance(column, str) else column
|
|
375
|
+
select_expr = select_expr.group_by(column_expr, copy=False)
|
|
376
|
+
builder.set_expression(select_expr)
|
|
377
|
+
return cast("Self", builder)
|
|
378
|
+
|
|
379
|
+
def group_by_rollup(self, *columns: str | exp.Expression) -> Self:
|
|
380
|
+
column_exprs = [exp.column(column) if isinstance(column, str) else column for column in columns]
|
|
381
|
+
rollup_expr = exp.Rollup(expressions=column_exprs)
|
|
382
|
+
return self.group_by(rollup_expr)
|
|
383
|
+
|
|
384
|
+
def group_by_cube(self, *columns: str | exp.Expression) -> Self:
|
|
385
|
+
column_exprs = [exp.column(column) if isinstance(column, str) else column for column in columns]
|
|
386
|
+
cube_expr = exp.Cube(expressions=column_exprs)
|
|
387
|
+
return self.group_by(cube_expr)
|
|
388
|
+
|
|
389
|
+
def group_by_grouping_sets(self, *column_sets: tuple[str, ...] | list[str]) -> Self:
|
|
390
|
+
grouping_sets = [
|
|
391
|
+
exp.Tuple(expressions=[exp.column(col) if isinstance(col, str) else col for col in column_set])
|
|
392
|
+
for column_set in column_sets
|
|
393
|
+
]
|
|
394
|
+
grouping_expr = exp.GroupingSets(expressions=grouping_sets)
|
|
395
|
+
return self.group_by(grouping_expr)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
@trait
|
|
399
|
+
class OrderByClauseMixin:
|
|
400
|
+
__slots__ = ()
|
|
401
|
+
|
|
402
|
+
_expression: exp.Expression | None
|
|
403
|
+
|
|
404
|
+
def order_by(self, *items: Union[str, exp.Ordered, "Column"], desc: bool = False) -> Self:
|
|
405
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
406
|
+
select_expr = _ensure_select_expression(builder, error_message="ORDER BY only valid for SELECT.")
|
|
407
|
+
|
|
408
|
+
current_expr = select_expr
|
|
409
|
+
for item in items:
|
|
410
|
+
if isinstance(item, str):
|
|
411
|
+
order_item = parse_order_expression(item)
|
|
412
|
+
if desc:
|
|
413
|
+
order_item = order_item.desc()
|
|
414
|
+
else:
|
|
415
|
+
extracted_item = extract_expression(item)
|
|
416
|
+
order_item = extracted_item.desc() if desc and not isinstance(item, exp.Ordered) else extracted_item
|
|
417
|
+
current_expr = current_expr.order_by(order_item, copy=False)
|
|
418
|
+
builder.set_expression(current_expr)
|
|
419
|
+
return cast("Self", builder)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@trait
|
|
423
|
+
class LimitOffsetClauseMixin:
|
|
424
|
+
__slots__ = ()
|
|
425
|
+
|
|
426
|
+
_expression: exp.Expression | None
|
|
427
|
+
|
|
428
|
+
def limit(self, value: int) -> Self:
|
|
429
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
430
|
+
select_expr = _ensure_select_expression(builder, error_message="LIMIT only valid for SELECT.")
|
|
431
|
+
builder.set_expression(select_expr.limit(exp.convert(value), copy=False))
|
|
432
|
+
return cast("Self", builder)
|
|
433
|
+
|
|
434
|
+
def offset(self, value: int) -> Self:
|
|
435
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
436
|
+
select_expr = _ensure_select_expression(builder, error_message="OFFSET only valid for SELECT.")
|
|
437
|
+
builder.set_expression(select_expr.offset(exp.convert(value), copy=False))
|
|
438
|
+
return cast("Self", builder)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
@trait
|
|
442
|
+
class ReturningClauseMixin:
|
|
443
|
+
__slots__ = ()
|
|
444
|
+
|
|
445
|
+
_expression: exp.Expression | None
|
|
446
|
+
|
|
447
|
+
def returning(self, *columns: Union[str, exp.Expression, "Column", "ExpressionWrapper", Case]) -> Self:
|
|
448
|
+
if self._expression is None:
|
|
449
|
+
msg = "Cannot add RETURNING: expression not initialized."
|
|
450
|
+
raise SQLBuilderError(msg)
|
|
451
|
+
if not isinstance(self._expression, (exp.Insert, exp.Update, exp.Delete)):
|
|
452
|
+
msg = "RETURNING only supported for INSERT, UPDATE, DELETE statements."
|
|
453
|
+
raise SQLBuilderError(msg)
|
|
454
|
+
returning_exprs = [extract_expression(col) for col in columns]
|
|
455
|
+
self._expression.set("returning", exp.Returning(expressions=returning_exprs))
|
|
456
|
+
return self
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
@trait
|
|
460
|
+
class WhereClauseMixin:
|
|
461
|
+
__slots__ = ()
|
|
462
|
+
|
|
463
|
+
def _merge_sql_object_parameters(self, sql_obj: Any) -> None:
|
|
464
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
465
|
+
builder._merge_sql_object_parameters(sql_obj)
|
|
466
|
+
|
|
467
|
+
def get_expression(self) -> exp.Expression | None: ...
|
|
468
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
469
|
+
|
|
470
|
+
def _create_parameterized_condition(
|
|
471
|
+
self,
|
|
472
|
+
column: str | exp.Column,
|
|
473
|
+
value: Any,
|
|
474
|
+
condition_factory: "Callable[[exp.Expression, exp.Placeholder], exp.Expression]",
|
|
475
|
+
) -> exp.Expression:
|
|
476
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
477
|
+
column_name = extract_column_name(column)
|
|
478
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
479
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
480
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
481
|
+
placeholder = exp.Placeholder(this=param_name)
|
|
482
|
+
return condition_factory(col_expr, placeholder)
|
|
483
|
+
|
|
484
|
+
def _get_existing_where_clause(self) -> exp.Where | None:
|
|
485
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
486
|
+
expression = builder.get_expression()
|
|
487
|
+
if isinstance(expression, (exp.Select, exp.Update, exp.Delete)):
|
|
488
|
+
where_clause = expression.args.get("where")
|
|
489
|
+
if isinstance(where_clause, exp.Where):
|
|
490
|
+
return where_clause
|
|
491
|
+
return None
|
|
492
|
+
|
|
493
|
+
def _combine_with_or(self, new_condition: exp.Expression) -> Self:
|
|
494
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
495
|
+
expression = builder.get_expression()
|
|
496
|
+
if expression is None or not isinstance(expression, (exp.Select, exp.Update, exp.Delete)):
|
|
497
|
+
msg = "OR WHERE clause not supported for current expression. Use where() first."
|
|
498
|
+
raise SQLBuilderError(msg)
|
|
499
|
+
|
|
500
|
+
where_clause = self._get_existing_where_clause()
|
|
501
|
+
if where_clause is None or where_clause.this is None:
|
|
502
|
+
msg = "Cannot add OR WHERE clause: no existing WHERE clause found. Use where() before or_where()."
|
|
503
|
+
raise SQLBuilderError(msg)
|
|
504
|
+
|
|
505
|
+
combined_condition = exp.Or(this=where_clause.this, expression=new_condition)
|
|
506
|
+
where_clause.set("this", combined_condition)
|
|
507
|
+
builder.set_expression(expression)
|
|
508
|
+
return cast("Self", builder)
|
|
509
|
+
|
|
510
|
+
def _handle_in_operator(
|
|
511
|
+
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
512
|
+
) -> exp.Expression:
|
|
513
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
514
|
+
if has_parameter_builder(value) or isinstance(value, exp.Expression):
|
|
515
|
+
subquery_expr = self._normalize_subquery_expression(value, builder)
|
|
516
|
+
return exp.In(this=column_exp, expressions=[subquery_expr])
|
|
517
|
+
if is_iterable_parameters(value):
|
|
518
|
+
placeholders = []
|
|
519
|
+
for index, element in enumerate(value):
|
|
520
|
+
name_seed = column_name if len(value) == 1 else f"{column_name}_{index + 1}"
|
|
521
|
+
param_name = builder._generate_unique_parameter_name(name_seed)
|
|
522
|
+
_, param_name = builder.add_parameter(element, name=param_name)
|
|
523
|
+
placeholders.append(exp.Placeholder(this=param_name))
|
|
524
|
+
return exp.In(this=column_exp, expressions=placeholders)
|
|
525
|
+
|
|
526
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
527
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
528
|
+
return exp.In(this=column_exp, expressions=[exp.Placeholder(this=param_name)])
|
|
529
|
+
|
|
530
|
+
def _handle_not_in_operator(
|
|
531
|
+
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
532
|
+
) -> exp.Expression:
|
|
533
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
534
|
+
if has_parameter_builder(value) or isinstance(value, exp.Expression):
|
|
535
|
+
subquery_expr = self._normalize_subquery_expression(value, builder)
|
|
536
|
+
return exp.Not(this=exp.In(this=column_exp, expressions=[subquery_expr]))
|
|
537
|
+
if is_iterable_parameters(value):
|
|
538
|
+
placeholders = []
|
|
539
|
+
for index, element in enumerate(value):
|
|
540
|
+
name_seed = column_name if len(value) == 1 else f"{column_name}_{index + 1}"
|
|
541
|
+
param_name = builder._generate_unique_parameter_name(name_seed)
|
|
542
|
+
_, param_name = builder.add_parameter(element, name=param_name)
|
|
543
|
+
placeholders.append(exp.Placeholder(this=param_name))
|
|
544
|
+
return exp.Not(this=exp.In(this=column_exp, expressions=placeholders))
|
|
545
|
+
|
|
546
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
547
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
548
|
+
return exp.Not(this=exp.In(this=column_exp, expressions=[exp.Placeholder(this=param_name)]))
|
|
549
|
+
|
|
550
|
+
def _handle_is_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
|
|
551
|
+
value_expr = exp.Null() if value is None else exp.convert(value)
|
|
552
|
+
return exp.Is(this=column_exp, expression=value_expr)
|
|
553
|
+
|
|
554
|
+
def _handle_is_not_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
|
|
555
|
+
value_expr = exp.Null() if value is None else exp.convert(value)
|
|
556
|
+
return exp.Not(this=exp.Is(this=column_exp, expression=value_expr))
|
|
557
|
+
|
|
558
|
+
def _handle_between_operator(
|
|
559
|
+
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
560
|
+
) -> exp.Expression:
|
|
561
|
+
if is_iterable_parameters(value) and len(value) == BETWEEN_BOUND_COUNT:
|
|
562
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
563
|
+
low, high = value
|
|
564
|
+
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
565
|
+
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
566
|
+
_, low_param = builder.add_parameter(low, name=low_param)
|
|
567
|
+
_, high_param = builder.add_parameter(high, name=high_param)
|
|
568
|
+
return exp.Between(
|
|
569
|
+
this=column_exp, low=exp.Placeholder(this=low_param), high=exp.Placeholder(this=high_param)
|
|
570
|
+
)
|
|
571
|
+
msg = f"BETWEEN operator requires a tuple of two values, got {type(value).__name__}"
|
|
572
|
+
raise SQLBuilderError(msg)
|
|
573
|
+
|
|
574
|
+
def _handle_not_between_operator(
|
|
575
|
+
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
576
|
+
) -> exp.Expression:
|
|
577
|
+
if is_iterable_parameters(value) and len(value) == BETWEEN_BOUND_COUNT:
|
|
578
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
579
|
+
low, high = value
|
|
580
|
+
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
581
|
+
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
582
|
+
_, low_param = builder.add_parameter(low, name=low_param)
|
|
583
|
+
_, high_param = builder.add_parameter(high, name=high_param)
|
|
584
|
+
return exp.Not(
|
|
585
|
+
this=exp.Between(
|
|
586
|
+
this=column_exp, low=exp.Placeholder(this=low_param), high=exp.Placeholder(this=high_param)
|
|
587
|
+
)
|
|
588
|
+
)
|
|
589
|
+
msg = f"NOT BETWEEN operator requires a tuple of two values, got {type(value).__name__}"
|
|
590
|
+
raise SQLBuilderError(msg)
|
|
591
|
+
|
|
592
|
+
def _create_any_condition(self, column_expr: exp.Expression, values: Any, column_name: str) -> exp.Expression:
|
|
593
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
594
|
+
if has_parameter_builder(values):
|
|
595
|
+
subquery_expr = self._normalize_subquery_expression(values, builder)
|
|
596
|
+
return exp.EQ(this=column_expr, expression=exp.Any(this=subquery_expr))
|
|
597
|
+
if isinstance(values, exp.Expression):
|
|
598
|
+
return exp.EQ(this=column_expr, expression=exp.Any(this=values))
|
|
599
|
+
if has_sqlglot_expression(values):
|
|
600
|
+
raw_expr = values.sqlglot_expression
|
|
601
|
+
if isinstance(raw_expr, exp.Expression):
|
|
602
|
+
return exp.EQ(this=column_expr, expression=exp.Any(this=raw_expr))
|
|
603
|
+
parsed_expr: exp.Expression | None = exp.maybe_parse(str(values), dialect=builder.dialect)
|
|
604
|
+
if parsed_expr is not None:
|
|
605
|
+
return exp.EQ(this=column_expr, expression=exp.Any(this=parsed_expr))
|
|
606
|
+
if has_expression_and_sql(values):
|
|
607
|
+
self._merge_sql_object_parameters(values)
|
|
608
|
+
expression_attr = values.expression
|
|
609
|
+
if isinstance(expression_attr, exp.Expression):
|
|
610
|
+
return exp.EQ(this=column_expr, expression=exp.Any(this=expression_attr))
|
|
611
|
+
sql_text = values.sql
|
|
612
|
+
parsed_expr = exp.maybe_parse(sql_text, dialect=builder.dialect)
|
|
613
|
+
if parsed_expr is not None:
|
|
614
|
+
return exp.EQ(this=column_expr, expression=exp.Any(this=parsed_expr))
|
|
615
|
+
if isinstance(values, str):
|
|
616
|
+
parsed_expr = exp.maybe_parse(values, dialect=builder.dialect)
|
|
617
|
+
if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
|
|
618
|
+
return exp.EQ(this=column_expr, expression=exp.Any(this=exp.paren(parsed_expr)))
|
|
619
|
+
msg = "Unsupported type for 'values' in WHERE ANY"
|
|
620
|
+
raise SQLBuilderError(msg)
|
|
621
|
+
if not is_iterable_parameters(values) or isinstance(values, (bytes, bytearray)):
|
|
622
|
+
msg = "Unsupported type for 'values' in WHERE ANY"
|
|
623
|
+
raise SQLBuilderError(msg)
|
|
624
|
+
placeholders: list[exp.Expression] = []
|
|
625
|
+
values_list = list(values)
|
|
626
|
+
for index, element in enumerate(values_list):
|
|
627
|
+
if len(values_list) == 1:
|
|
628
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
629
|
+
else:
|
|
630
|
+
param_name = builder._generate_unique_parameter_name(f"{column_name}_any_{index + 1}")
|
|
631
|
+
_, param_name = builder.add_parameter(element, name=param_name)
|
|
632
|
+
placeholders.append(exp.Placeholder(this=param_name))
|
|
633
|
+
tuple_expr = exp.Tuple(expressions=placeholders)
|
|
634
|
+
return exp.EQ(this=column_expr, expression=exp.Any(this=tuple_expr))
|
|
635
|
+
|
|
636
|
+
def _create_not_any_condition(self, column_expr: exp.Expression, values: Any, column_name: str) -> exp.Expression:
|
|
637
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
638
|
+
if has_parameter_builder(values):
|
|
639
|
+
subquery_expr = self._normalize_subquery_expression(values, builder)
|
|
640
|
+
return exp.NEQ(this=column_expr, expression=exp.Any(this=subquery_expr))
|
|
641
|
+
if isinstance(values, exp.Expression):
|
|
642
|
+
return exp.NEQ(this=column_expr, expression=exp.Any(this=values))
|
|
643
|
+
if has_sqlglot_expression(values):
|
|
644
|
+
raw_expr = values.sqlglot_expression
|
|
645
|
+
if isinstance(raw_expr, exp.Expression):
|
|
646
|
+
return exp.NEQ(this=column_expr, expression=exp.Any(this=raw_expr))
|
|
647
|
+
parsed_expr: exp.Expression | None = exp.maybe_parse(str(values), dialect=builder.dialect)
|
|
648
|
+
if parsed_expr is not None:
|
|
649
|
+
return exp.NEQ(this=column_expr, expression=exp.Any(this=parsed_expr))
|
|
650
|
+
if has_expression_and_sql(values):
|
|
651
|
+
self._merge_sql_object_parameters(values)
|
|
652
|
+
expression_attr = values.expression
|
|
653
|
+
if isinstance(expression_attr, exp.Expression):
|
|
654
|
+
return exp.NEQ(this=column_expr, expression=exp.Any(this=expression_attr))
|
|
655
|
+
sql_text = values.sql
|
|
656
|
+
parsed_expr = exp.maybe_parse(sql_text, dialect=builder.dialect)
|
|
657
|
+
if parsed_expr is not None:
|
|
658
|
+
return exp.NEQ(this=column_expr, expression=exp.Any(this=parsed_expr))
|
|
659
|
+
if isinstance(values, str):
|
|
660
|
+
parsed_expr = exp.maybe_parse(values, dialect=builder.dialect)
|
|
661
|
+
if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
|
|
662
|
+
return exp.NEQ(this=column_expr, expression=exp.Any(this=exp.paren(parsed_expr)))
|
|
663
|
+
msg = "Unsupported type for 'values' in WHERE NOT ANY"
|
|
664
|
+
raise SQLBuilderError(msg)
|
|
665
|
+
if not is_iterable_parameters(values) or isinstance(values, (bytes, bytearray)):
|
|
666
|
+
msg = "Unsupported type for 'values' in WHERE NOT ANY"
|
|
667
|
+
raise SQLBuilderError(msg)
|
|
668
|
+
placeholders: list[exp.Expression] = []
|
|
669
|
+
values_list = list(values)
|
|
670
|
+
for index, element in enumerate(values_list):
|
|
671
|
+
if len(values_list) == 1:
|
|
672
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
673
|
+
else:
|
|
674
|
+
param_name = builder._generate_unique_parameter_name(f"{column_name}_not_any_{index + 1}")
|
|
675
|
+
_, param_name = builder.add_parameter(element, name=param_name)
|
|
676
|
+
placeholders.append(exp.Placeholder(this=param_name))
|
|
677
|
+
tuple_expr = exp.Tuple(expressions=placeholders)
|
|
678
|
+
return exp.NEQ(this=column_expr, expression=exp.Any(this=tuple_expr))
|
|
679
|
+
|
|
680
|
+
def _normalize_subquery_expression(self, subquery: Any, builder: "SQLBuilderProtocol") -> exp.Expression:
|
|
681
|
+
if has_parameter_builder(subquery):
|
|
682
|
+
subquery_builder = cast("QueryBuilder", subquery)
|
|
683
|
+
safe_query: BuiltQuery = subquery_builder.build()
|
|
684
|
+
parsed_subquery: exp.Expression | None = exp.maybe_parse(safe_query.sql, dialect=builder.dialect)
|
|
685
|
+
if parsed_subquery is None:
|
|
686
|
+
msg = f"Could not parse subquery SQL: {safe_query.sql}"
|
|
687
|
+
raise SQLBuilderError(msg)
|
|
688
|
+
subquery_expr = exp.paren(parsed_subquery)
|
|
689
|
+
parameters: Any = safe_query.parameters
|
|
690
|
+
if isinstance(parameters, dict):
|
|
691
|
+
param_mapping: dict[str, str] = {}
|
|
692
|
+
query_builder = cast("QueryBuilder", builder)
|
|
693
|
+
for param_name, param_value in parameters.items():
|
|
694
|
+
unique_name = query_builder._generate_unique_parameter_name(param_name)
|
|
695
|
+
param_mapping[param_name] = unique_name
|
|
696
|
+
query_builder.add_parameter(param_value, name=unique_name)
|
|
697
|
+
if param_mapping:
|
|
698
|
+
updated = query_builder._update_placeholders_in_expression(parsed_subquery, param_mapping)
|
|
699
|
+
subquery_expr = exp.paren(updated)
|
|
700
|
+
elif isinstance(parameters, (list, tuple)):
|
|
701
|
+
for param_value in parameters:
|
|
702
|
+
builder.add_parameter(param_value)
|
|
703
|
+
elif parameters is not None:
|
|
704
|
+
builder.add_parameter(parameters)
|
|
705
|
+
return subquery_expr
|
|
706
|
+
|
|
707
|
+
if has_expression_and_sql(subquery):
|
|
708
|
+
self._merge_sql_object_parameters(subquery)
|
|
709
|
+
expression_attr = subquery.expression
|
|
710
|
+
if isinstance(expression_attr, exp.Expression):
|
|
711
|
+
return expression_attr
|
|
712
|
+
sql_text = subquery.sql
|
|
713
|
+
parsed_from_sql: exp.Expression | None = exp.maybe_parse(sql_text, dialect=builder.dialect)
|
|
714
|
+
if parsed_from_sql is None:
|
|
715
|
+
msg = f"Could not parse subquery SQL: {sql_text}"
|
|
716
|
+
raise SQLBuilderError(msg)
|
|
717
|
+
return parsed_from_sql
|
|
718
|
+
|
|
719
|
+
if isinstance(subquery, exp.Expression):
|
|
720
|
+
return subquery
|
|
721
|
+
|
|
722
|
+
if isinstance(subquery, str):
|
|
723
|
+
parsed_expression_from_str: exp.Expression | None = exp.maybe_parse(subquery, dialect=builder.dialect)
|
|
724
|
+
if parsed_expression_from_str is None:
|
|
725
|
+
msg = f"Could not parse subquery SQL: {subquery}"
|
|
726
|
+
raise SQLBuilderError(msg)
|
|
727
|
+
return parsed_expression_from_str
|
|
728
|
+
|
|
729
|
+
converted_expr: exp.Expression = exp.convert(subquery)
|
|
730
|
+
return converted_expr
|
|
731
|
+
|
|
732
|
+
def _create_or_expression(self, conditions: "list[exp.Expression]") -> exp.Expression:
|
|
733
|
+
if not conditions:
|
|
734
|
+
msg = "OR expression requires at least one condition"
|
|
735
|
+
raise SQLBuilderError(msg)
|
|
736
|
+
|
|
737
|
+
return exp.or_(*conditions)
|
|
738
|
+
|
|
739
|
+
def _process_tuple_condition(self, condition: "tuple[Any, ...]") -> exp.Expression:
|
|
740
|
+
if len(condition) == PAIR_LENGTH:
|
|
741
|
+
column, value = condition
|
|
742
|
+
return self._create_parameterized_condition(column, value, _expr_eq)
|
|
743
|
+
|
|
744
|
+
if len(condition) != TRIPLE_LENGTH:
|
|
745
|
+
msg = f"Condition tuple must have 2 or 3 elements, got {len(condition)}"
|
|
746
|
+
raise SQLBuilderError(msg)
|
|
747
|
+
|
|
748
|
+
column_raw, operator, value = condition
|
|
749
|
+
operator_upper = str(operator).upper()
|
|
750
|
+
column_expr = parse_column_expression(column_raw)
|
|
751
|
+
column_name = extract_column_name(column_raw)
|
|
752
|
+
|
|
753
|
+
if operator_upper in _SIMPLE_OPERATOR_MAP:
|
|
754
|
+
return self._create_parameterized_condition(column_raw, value, _SIMPLE_OPERATOR_MAP[operator_upper])
|
|
755
|
+
|
|
756
|
+
if operator_upper == "IN":
|
|
757
|
+
return self._handle_in_operator(column_expr, value, column_name)
|
|
758
|
+
if operator_upper == "NOT IN":
|
|
759
|
+
return self._handle_not_in_operator(column_expr, value, column_name)
|
|
760
|
+
if operator_upper == "IS":
|
|
761
|
+
return self._handle_is_operator(column_expr, value)
|
|
762
|
+
if operator_upper == "IS NOT":
|
|
763
|
+
return self._handle_is_not_operator(column_expr, value)
|
|
764
|
+
if operator_upper == "BETWEEN":
|
|
765
|
+
return self._handle_between_operator(column_expr, value, column_name)
|
|
766
|
+
if operator_upper == "NOT BETWEEN":
|
|
767
|
+
return self._handle_not_between_operator(column_expr, value, column_name)
|
|
768
|
+
|
|
769
|
+
msg = f"Unsupported operator: {operator}"
|
|
770
|
+
raise SQLBuilderError(msg)
|
|
771
|
+
|
|
772
|
+
def _process_where_condition(
|
|
773
|
+
self,
|
|
774
|
+
condition: Union[
|
|
775
|
+
str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression", SQL
|
|
776
|
+
],
|
|
777
|
+
values: tuple[Any, ...],
|
|
778
|
+
operator: str | None,
|
|
779
|
+
kwargs: dict[str, Any],
|
|
780
|
+
) -> exp.Expression:
|
|
781
|
+
if values or kwargs:
|
|
782
|
+
if not isinstance(condition, str):
|
|
783
|
+
msg = "When values are provided, condition must be a string"
|
|
784
|
+
raise SQLBuilderError(msg)
|
|
785
|
+
|
|
786
|
+
validator = ParameterValidator()
|
|
787
|
+
param_info = validator.extract_parameters(condition)
|
|
788
|
+
|
|
789
|
+
if param_info:
|
|
790
|
+
param_dict = dict(kwargs)
|
|
791
|
+
positional_params = [
|
|
792
|
+
info
|
|
793
|
+
for info in param_info
|
|
794
|
+
if info.style in {ParameterStyle.NUMERIC, ParameterStyle.POSITIONAL_COLON, ParameterStyle.QMARK}
|
|
795
|
+
]
|
|
796
|
+
|
|
797
|
+
if len(values) != len(positional_params):
|
|
798
|
+
msg = (
|
|
799
|
+
"Parameter count mismatch: condition has "
|
|
800
|
+
f"{len(positional_params)} positional placeholders, got {len(values)} values"
|
|
801
|
+
)
|
|
802
|
+
raise SQLBuilderError(msg)
|
|
803
|
+
|
|
804
|
+
for index, value in enumerate(values):
|
|
805
|
+
param_dict[f"param_{index}"] = value
|
|
806
|
+
|
|
807
|
+
condition = SQL(condition, param_dict)
|
|
808
|
+
elif len(values) == 1 and not kwargs:
|
|
809
|
+
if operator is not None:
|
|
810
|
+
return self._process_tuple_condition((condition, operator, values[0]))
|
|
811
|
+
return self._process_tuple_condition((condition, values[0]))
|
|
812
|
+
else:
|
|
813
|
+
msg = f"Cannot bind parameters to condition without placeholders: {condition}"
|
|
814
|
+
raise SQLBuilderError(msg)
|
|
815
|
+
|
|
816
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
817
|
+
|
|
818
|
+
if isinstance(condition, str):
|
|
819
|
+
return parse_condition_expression(condition)
|
|
820
|
+
if isinstance(condition, (exp.Expression, exp.Condition)):
|
|
821
|
+
return condition
|
|
822
|
+
if isinstance(condition, tuple):
|
|
823
|
+
return self._process_tuple_condition(condition)
|
|
824
|
+
if has_parameter_builder(condition):
|
|
825
|
+
column_expr_obj = cast("ColumnExpression", condition)
|
|
826
|
+
expression_attr = cast("exp.Expression | None", column_expr_obj._expression)
|
|
827
|
+
if expression_attr is None:
|
|
828
|
+
msg = "Column expression is missing underlying sqlglot expression."
|
|
829
|
+
raise SQLBuilderError(msg)
|
|
830
|
+
return expression_attr
|
|
831
|
+
if has_sqlglot_expression(condition):
|
|
832
|
+
raw_expr = condition.sqlglot_expression
|
|
833
|
+
if isinstance(raw_expr, exp.Expression):
|
|
834
|
+
return builder._parameterize_expression(raw_expr)
|
|
835
|
+
return parse_condition_expression(str(condition))
|
|
836
|
+
if has_expression_and_sql(condition):
|
|
837
|
+
expression_attr = condition.expression
|
|
838
|
+
if isinstance(expression_attr, exp.Expression):
|
|
839
|
+
self._merge_sql_object_parameters(condition)
|
|
840
|
+
return expression_attr
|
|
841
|
+
sql_text = condition.sql
|
|
842
|
+
self._merge_sql_object_parameters(condition)
|
|
843
|
+
return parse_condition_expression(sql_text)
|
|
844
|
+
|
|
845
|
+
msg = f"Unsupported condition type: {type(condition).__name__}"
|
|
846
|
+
raise SQLBuilderError(msg)
|
|
847
|
+
|
|
848
|
+
def where(
|
|
849
|
+
self,
|
|
850
|
+
condition: Union[
|
|
851
|
+
str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression", SQL
|
|
852
|
+
],
|
|
853
|
+
*values: Any,
|
|
854
|
+
operator: str | None = None,
|
|
855
|
+
**kwargs: Any,
|
|
856
|
+
) -> Self:
|
|
857
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
858
|
+
current_expr = builder.get_expression()
|
|
859
|
+
if current_expr is None:
|
|
860
|
+
msg = "Cannot add WHERE clause: expression is not initialized."
|
|
861
|
+
raise SQLBuilderError(msg)
|
|
862
|
+
|
|
863
|
+
if isinstance(current_expr, exp.Delete) and not current_expr.args.get("this"):
|
|
864
|
+
msg = "WHERE clause requires a table to be set. Use from() to set the table first."
|
|
865
|
+
raise SQLBuilderError(msg)
|
|
866
|
+
|
|
867
|
+
where_expr = self._process_where_condition(condition, values, operator, kwargs)
|
|
868
|
+
|
|
869
|
+
if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)):
|
|
870
|
+
updated_expr = current_expr.where(where_expr, copy=False)
|
|
871
|
+
builder.set_expression(updated_expr)
|
|
872
|
+
return cast("Self", builder)
|
|
873
|
+
msg = f"WHERE clause not supported for {type(current_expr).__name__}"
|
|
874
|
+
raise SQLBuilderError(msg)
|
|
875
|
+
|
|
876
|
+
def where_eq(self, column: str | exp.Column, value: Any) -> Self:
|
|
877
|
+
condition = self._create_parameterized_condition(column, value, _expr_eq)
|
|
878
|
+
return self.where(condition)
|
|
879
|
+
|
|
880
|
+
def where_neq(self, column: str | exp.Column, value: Any) -> Self:
|
|
881
|
+
condition = self._create_parameterized_condition(column, value, _expr_neq)
|
|
882
|
+
return self.where(condition)
|
|
883
|
+
|
|
884
|
+
def where_lt(self, column: str | exp.Column, value: Any) -> Self:
|
|
885
|
+
condition = self._create_parameterized_condition(column, value, _expr_lt)
|
|
886
|
+
return self.where(condition)
|
|
887
|
+
|
|
888
|
+
def where_lte(self, column: str | exp.Column, value: Any) -> Self:
|
|
889
|
+
condition = self._create_parameterized_condition(column, value, _expr_lte)
|
|
890
|
+
return self.where(condition)
|
|
891
|
+
|
|
892
|
+
def where_gt(self, column: str | exp.Column, value: Any) -> Self:
|
|
893
|
+
condition = self._create_parameterized_condition(column, value, _expr_gt)
|
|
894
|
+
return self.where(condition)
|
|
895
|
+
|
|
896
|
+
def where_gte(self, column: str | exp.Column, value: Any) -> Self:
|
|
897
|
+
condition = self._create_parameterized_condition(column, value, _expr_gte)
|
|
898
|
+
return self.where(condition)
|
|
899
|
+
|
|
900
|
+
def where_between(self, column: str | exp.Column, low: Any, high: Any) -> Self:
|
|
901
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
902
|
+
column_name = extract_column_name(column)
|
|
903
|
+
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
904
|
+
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
905
|
+
_, low_param = builder.add_parameter(low, name=low_param)
|
|
906
|
+
_, high_param = builder.add_parameter(high, name=high_param)
|
|
907
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
908
|
+
condition: exp.Expression = col_expr.between(exp.Placeholder(this=low_param), exp.Placeholder(this=high_param))
|
|
909
|
+
return self.where(condition)
|
|
910
|
+
|
|
911
|
+
def where_like(self, column: str | exp.Column, pattern: str, escape: str | None = None) -> Self:
|
|
912
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
913
|
+
column_name = extract_column_name(column)
|
|
914
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
915
|
+
_, param_name = builder.add_parameter(pattern, name=param_name)
|
|
916
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
917
|
+
if escape is not None:
|
|
918
|
+
condition = exp.Like(
|
|
919
|
+
this=col_expr, expression=exp.Placeholder(this=param_name), escape=exp.convert(str(escape))
|
|
920
|
+
)
|
|
921
|
+
else:
|
|
922
|
+
condition = col_expr.like(exp.Placeholder(this=param_name))
|
|
923
|
+
return self.where(condition)
|
|
924
|
+
|
|
925
|
+
def where_not_like(self, column: str | exp.Column, pattern: str) -> Self:
|
|
926
|
+
condition = self._create_parameterized_condition(column, pattern, _expr_not_like)
|
|
927
|
+
return self.where(condition)
|
|
928
|
+
|
|
929
|
+
def where_ilike(self, column: str | exp.Column, pattern: str) -> Self:
|
|
930
|
+
condition = self._create_parameterized_condition(column, pattern, _expr_ilike)
|
|
931
|
+
return self.where(condition)
|
|
932
|
+
|
|
933
|
+
def where_is_null(self, column: str | exp.Column) -> Self:
|
|
934
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
935
|
+
condition: exp.Expression = col_expr.is_(exp.null())
|
|
936
|
+
return self.where(condition)
|
|
937
|
+
|
|
938
|
+
def where_is_not_null(self, column: str | exp.Column) -> Self:
|
|
939
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
940
|
+
condition: exp.Expression = col_expr.is_(exp.null()).not_()
|
|
941
|
+
return self.where(condition)
|
|
942
|
+
|
|
943
|
+
def where_in(self, column: str | exp.Column, values: Any) -> Self:
|
|
944
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
945
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
946
|
+
if has_parameter_builder(values) or isinstance(values, (exp.Expression, str)):
|
|
947
|
+
subquery_exp = self._normalize_subquery_expression(values, builder)
|
|
948
|
+
return self.where(exp.In(this=col_expr, expressions=[subquery_exp]))
|
|
949
|
+
|
|
950
|
+
condition = self._handle_in_operator(col_expr, values, extract_column_name(column))
|
|
951
|
+
return self.where(condition)
|
|
952
|
+
|
|
953
|
+
def where_not_in(self, column: str | exp.Column, values: Any) -> Self:
|
|
954
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
955
|
+
condition = self._handle_not_in_operator(col_expr, values, extract_column_name(column))
|
|
956
|
+
return self.where(condition)
|
|
957
|
+
|
|
958
|
+
def where_any(self, column: str | exp.Column, subquery: Any) -> Self:
|
|
959
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
960
|
+
column_name = extract_column_name(column)
|
|
961
|
+
condition = self._create_any_condition(col_expr, subquery, column_name)
|
|
962
|
+
return self.where(condition)
|
|
963
|
+
|
|
964
|
+
def where_not_any(self, column: str | exp.Column, subquery: Any) -> Self:
|
|
965
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
966
|
+
column_name = extract_column_name(column)
|
|
967
|
+
condition = self._create_not_any_condition(col_expr, subquery, column_name)
|
|
968
|
+
return self.where(condition)
|
|
969
|
+
|
|
970
|
+
def where_exists(self, subquery: Any) -> Self:
|
|
971
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
972
|
+
subquery_expr = self._normalize_subquery_expression(subquery, builder)
|
|
973
|
+
return self.where(exp.Exists(this=subquery_expr))
|
|
974
|
+
|
|
975
|
+
def where_not_exists(self, subquery: Any) -> Self:
|
|
976
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
977
|
+
subquery_expr = self._normalize_subquery_expression(subquery, builder)
|
|
978
|
+
return self.where(exp.Not(this=exp.Exists(this=subquery_expr)))
|
|
979
|
+
|
|
980
|
+
def where_like_any(self, column: str | exp.Column, patterns: list[str]) -> Self:
|
|
981
|
+
conditions = [self._create_parameterized_condition(column, pattern, _expr_like_method) for pattern in patterns]
|
|
982
|
+
or_condition = self._create_or_expression(conditions)
|
|
983
|
+
return self.where(or_condition)
|
|
984
|
+
|
|
985
|
+
def or_where_eq(self, column: str | exp.Column, value: Any) -> Self:
|
|
986
|
+
condition = self._create_parameterized_condition(column, value, _expr_eq)
|
|
987
|
+
return self._combine_with_or(condition)
|
|
988
|
+
|
|
989
|
+
def or_where_neq(self, column: str | exp.Column, value: Any) -> Self:
|
|
990
|
+
condition = self._create_parameterized_condition(column, value, _expr_neq)
|
|
991
|
+
return self._combine_with_or(condition)
|
|
992
|
+
|
|
993
|
+
def or_where_lt(self, column: str | exp.Column, value: Any) -> Self:
|
|
994
|
+
condition = self._create_parameterized_condition(column, value, _expr_lt)
|
|
995
|
+
return self._combine_with_or(condition)
|
|
996
|
+
|
|
997
|
+
def or_where_lte(self, column: str | exp.Column, value: Any) -> Self:
|
|
998
|
+
condition = self._create_parameterized_condition(column, value, _expr_lte)
|
|
999
|
+
return self._combine_with_or(condition)
|
|
1000
|
+
|
|
1001
|
+
def or_where_gt(self, column: str | exp.Column, value: Any) -> Self:
|
|
1002
|
+
condition = self._create_parameterized_condition(column, value, _expr_gt)
|
|
1003
|
+
return self._combine_with_or(condition)
|
|
1004
|
+
|
|
1005
|
+
def or_where_gte(self, column: str | exp.Column, value: Any) -> Self:
|
|
1006
|
+
condition = self._create_parameterized_condition(column, value, _expr_gte)
|
|
1007
|
+
return self._combine_with_or(condition)
|
|
1008
|
+
|
|
1009
|
+
def or_where_between(self, column: str | exp.Column, low: Any, high: Any) -> Self:
|
|
1010
|
+
column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
1011
|
+
condition = self._handle_between_operator(column_expr, (low, high), extract_column_name(column))
|
|
1012
|
+
return self._combine_with_or(condition)
|
|
1013
|
+
|
|
1014
|
+
def or_where_like(self, column: str | exp.Column, pattern: str, escape: str | None = None) -> Self:
|
|
1015
|
+
column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
1016
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
1017
|
+
column_name = extract_column_name(column)
|
|
1018
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
1019
|
+
_, param_name = builder.add_parameter(pattern, name=param_name)
|
|
1020
|
+
placeholder = exp.Placeholder(this=param_name)
|
|
1021
|
+
if escape is not None:
|
|
1022
|
+
condition = exp.Like(this=column_expr, expression=placeholder, escape=exp.convert(str(escape)))
|
|
1023
|
+
else:
|
|
1024
|
+
condition = column_expr.like(placeholder)
|
|
1025
|
+
return self._combine_with_or(cast("exp.Expression", condition))
|
|
1026
|
+
|
|
1027
|
+
def or_where_not_like(self, column: str | exp.Column, pattern: str) -> Self:
|
|
1028
|
+
condition = self._create_parameterized_condition(column, pattern, _expr_like_not)
|
|
1029
|
+
return self._combine_with_or(condition)
|
|
1030
|
+
|
|
1031
|
+
def or_where_ilike(self, column: str | exp.Column, pattern: str) -> Self:
|
|
1032
|
+
condition = self._create_parameterized_condition(column, pattern, _expr_ilike)
|
|
1033
|
+
return self._combine_with_or(condition)
|
|
1034
|
+
|
|
1035
|
+
def or_where_is_null(self, column: str | exp.Column) -> Self:
|
|
1036
|
+
column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
1037
|
+
condition: exp.Expression = column_expr.is_(exp.null())
|
|
1038
|
+
return self._combine_with_or(condition)
|
|
1039
|
+
|
|
1040
|
+
def or_where_is_not_null(self, column: str | exp.Column) -> Self:
|
|
1041
|
+
column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
1042
|
+
condition: exp.Expression = column_expr.is_(exp.null()).not_()
|
|
1043
|
+
return self._combine_with_or(condition)
|
|
1044
|
+
|
|
1045
|
+
def or_where_null(self, column: str | exp.Column) -> Self:
|
|
1046
|
+
return self.or_where_is_null(column)
|
|
1047
|
+
|
|
1048
|
+
def or_where_not_null(self, column: str | exp.Column) -> Self:
|
|
1049
|
+
return self.or_where_is_not_null(column)
|
|
1050
|
+
|
|
1051
|
+
def or_where_in(self, column: str | exp.Column, values: Any) -> Self:
|
|
1052
|
+
column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
1053
|
+
condition = self._handle_in_operator(column_expr, values, extract_column_name(column))
|
|
1054
|
+
return self._combine_with_or(condition)
|
|
1055
|
+
|
|
1056
|
+
def or_where_not_in(self, column: str | exp.Column, values: Any) -> Self:
|
|
1057
|
+
column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
1058
|
+
condition = self._handle_not_in_operator(column_expr, values, extract_column_name(column))
|
|
1059
|
+
return self._combine_with_or(condition)
|
|
1060
|
+
|
|
1061
|
+
def or_where_any(self, column: str | exp.Column, subquery: Any) -> Self:
|
|
1062
|
+
column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
1063
|
+
condition = self._create_any_condition(column_expr, subquery, extract_column_name(column))
|
|
1064
|
+
return self._combine_with_or(condition)
|
|
1065
|
+
|
|
1066
|
+
def or_where_not_any(self, column: str | exp.Column, subquery: Any) -> Self:
|
|
1067
|
+
column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
1068
|
+
condition = self._create_not_any_condition(column_expr, subquery, extract_column_name(column))
|
|
1069
|
+
return self._combine_with_or(condition)
|
|
1070
|
+
|
|
1071
|
+
def or_where_exists(self, subquery: Any) -> Self:
|
|
1072
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
1073
|
+
subquery_expr = self._normalize_subquery_expression(subquery, builder)
|
|
1074
|
+
condition = exp.Exists(this=subquery_expr)
|
|
1075
|
+
return self._combine_with_or(condition)
|
|
1076
|
+
|
|
1077
|
+
def or_where_not_exists(self, subquery: Any) -> Self:
|
|
1078
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
1079
|
+
subquery_expr = self._normalize_subquery_expression(subquery, builder)
|
|
1080
|
+
condition = exp.Not(this=exp.Exists(this=subquery_expr))
|
|
1081
|
+
return self._combine_with_or(condition)
|
|
1082
|
+
|
|
1083
|
+
def where_or(self, *conditions: str | tuple[str, Any] | tuple[str, str, Any] | exp.Expression) -> Self:
|
|
1084
|
+
if not conditions:
|
|
1085
|
+
msg = "where_or() requires at least one condition"
|
|
1086
|
+
raise SQLBuilderError(msg)
|
|
1087
|
+
|
|
1088
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
1089
|
+
if builder.get_expression() is None:
|
|
1090
|
+
msg = "Cannot add WHERE OR clause: expression is not initialized."
|
|
1091
|
+
raise SQLBuilderError(msg)
|
|
1092
|
+
|
|
1093
|
+
processed_conditions = [self._process_where_condition(condition, (), None, {}) for condition in conditions]
|
|
1094
|
+
or_condition = self._create_or_expression(processed_conditions)
|
|
1095
|
+
return self.where(or_condition)
|
|
1096
|
+
|
|
1097
|
+
def or_where(
|
|
1098
|
+
self,
|
|
1099
|
+
condition: Union[
|
|
1100
|
+
str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression", SQL
|
|
1101
|
+
],
|
|
1102
|
+
*values: Any,
|
|
1103
|
+
operator: str | None = None,
|
|
1104
|
+
**kwargs: Any,
|
|
1105
|
+
) -> Self:
|
|
1106
|
+
or_condition = self._process_where_condition(condition, values, operator, kwargs)
|
|
1107
|
+
return self._combine_with_or(or_condition)
|
|
1108
|
+
|
|
1109
|
+
|
|
1110
|
+
@trait
|
|
1111
|
+
class HavingClauseMixin:
|
|
1112
|
+
__slots__ = ()
|
|
1113
|
+
|
|
1114
|
+
def having(self, condition: str | exp.Expression | exp.Condition | tuple[str, Any] | tuple[str, str, Any]) -> Self:
|
|
1115
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
1116
|
+
current_expr = builder.get_expression()
|
|
1117
|
+
if current_expr is None or not isinstance(current_expr, exp.Select):
|
|
1118
|
+
return cast("Self", builder)
|
|
1119
|
+
|
|
1120
|
+
if isinstance(condition, tuple):
|
|
1121
|
+
where_mixin = cast("WhereClauseMixin", self)
|
|
1122
|
+
having_expr = where_mixin._process_tuple_condition(condition)
|
|
1123
|
+
else:
|
|
1124
|
+
having_expr = parse_condition_expression(condition)
|
|
1125
|
+
|
|
1126
|
+
builder.set_expression(current_expr.having(having_expr, copy=False))
|
|
1127
|
+
return cast("Self", builder)
|
|
1128
|
+
|
|
1129
|
+
|
|
1130
|
+
@trait
|
|
1131
|
+
class PivotClauseMixin:
|
|
1132
|
+
__slots__ = ()
|
|
1133
|
+
|
|
1134
|
+
def pivot(
|
|
1135
|
+
self,
|
|
1136
|
+
aggregate_function: str | exp.Expression,
|
|
1137
|
+
aggregate_column: str | exp.Expression,
|
|
1138
|
+
pivot_column: str | exp.Expression,
|
|
1139
|
+
pivot_values: list[str | int | float | exp.Expression],
|
|
1140
|
+
alias: str | None = None,
|
|
1141
|
+
) -> "Select":
|
|
1142
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
1143
|
+
current_expr = builder.get_expression()
|
|
1144
|
+
if not isinstance(current_expr, exp.Select):
|
|
1145
|
+
msg = "Pivot can only be applied to a Select expression managed by SelectBuilder."
|
|
1146
|
+
raise TypeError(msg)
|
|
1147
|
+
|
|
1148
|
+
agg_name = aggregate_function if isinstance(aggregate_function, str) else aggregate_function.name
|
|
1149
|
+
agg_column = exp.column(aggregate_column) if isinstance(aggregate_column, str) else aggregate_column
|
|
1150
|
+
pivot_col_expr = exp.column(pivot_column) if isinstance(pivot_column, str) else pivot_column
|
|
1151
|
+
|
|
1152
|
+
pivot_agg_expr = exp.func(agg_name, agg_column)
|
|
1153
|
+
|
|
1154
|
+
pivot_value_exprs: list[exp.Expression] = []
|
|
1155
|
+
for raw_value in pivot_values:
|
|
1156
|
+
if isinstance(raw_value, exp.Expression):
|
|
1157
|
+
pivot_value_exprs.append(raw_value)
|
|
1158
|
+
elif isinstance(raw_value, (str, int, float)):
|
|
1159
|
+
pivot_value_exprs.append(exp.convert(raw_value))
|
|
1160
|
+
else:
|
|
1161
|
+
pivot_value_exprs.append(exp.convert(str(raw_value)))
|
|
1162
|
+
|
|
1163
|
+
in_expr = exp.In(this=pivot_col_expr, expressions=pivot_value_exprs)
|
|
1164
|
+
pivot_node = exp.Pivot(expressions=[pivot_agg_expr], fields=[in_expr], unpivot=False)
|
|
1165
|
+
|
|
1166
|
+
if alias:
|
|
1167
|
+
pivot_node.set("alias", exp.TableAlias(this=exp.to_identifier(alias)))
|
|
1168
|
+
|
|
1169
|
+
from_clause = current_expr.args.get("from")
|
|
1170
|
+
if from_clause and isinstance(from_clause, exp.From):
|
|
1171
|
+
table = from_clause.this
|
|
1172
|
+
if isinstance(table, exp.Table):
|
|
1173
|
+
existing = table.args.get("pivots", [])
|
|
1174
|
+
existing.append(pivot_node)
|
|
1175
|
+
table.set("pivots", existing)
|
|
1176
|
+
|
|
1177
|
+
return cast("Select", self)
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
@trait
|
|
1181
|
+
class UnpivotClauseMixin:
|
|
1182
|
+
__slots__ = ()
|
|
1183
|
+
|
|
1184
|
+
def unpivot(
|
|
1185
|
+
self,
|
|
1186
|
+
value_column_name: str,
|
|
1187
|
+
name_column_name: str,
|
|
1188
|
+
columns_to_unpivot: list[str | exp.Expression],
|
|
1189
|
+
alias: str | None = None,
|
|
1190
|
+
) -> "Select":
|
|
1191
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
1192
|
+
current_expr = builder.get_expression()
|
|
1193
|
+
if not isinstance(current_expr, exp.Select):
|
|
1194
|
+
msg = "Unpivot can only be applied to a Select expression managed by Select."
|
|
1195
|
+
raise TypeError(msg)
|
|
1196
|
+
|
|
1197
|
+
value_identifier = exp.to_identifier(value_column_name)
|
|
1198
|
+
name_identifier = exp.to_identifier(name_column_name)
|
|
1199
|
+
|
|
1200
|
+
unpivot_columns: list[exp.Expression] = []
|
|
1201
|
+
for column in columns_to_unpivot:
|
|
1202
|
+
if isinstance(column, exp.Expression):
|
|
1203
|
+
unpivot_columns.append(column)
|
|
1204
|
+
elif isinstance(column, str):
|
|
1205
|
+
unpivot_columns.append(exp.column(column))
|
|
1206
|
+
else:
|
|
1207
|
+
unpivot_columns.append(exp.column(str(column)))
|
|
1208
|
+
|
|
1209
|
+
in_expr = exp.In(this=name_identifier, expressions=unpivot_columns)
|
|
1210
|
+
unpivot_node = exp.Pivot(expressions=[value_identifier], fields=[in_expr], unpivot=True)
|
|
1211
|
+
|
|
1212
|
+
if alias:
|
|
1213
|
+
unpivot_node.set("alias", exp.TableAlias(this=exp.to_identifier(alias)))
|
|
1214
|
+
|
|
1215
|
+
from_clause = current_expr.args.get("from")
|
|
1216
|
+
if from_clause and isinstance(from_clause, exp.From):
|
|
1217
|
+
table = from_clause.this
|
|
1218
|
+
if isinstance(table, exp.Table):
|
|
1219
|
+
existing = table.args.get("pivots", [])
|
|
1220
|
+
existing.append(unpivot_node)
|
|
1221
|
+
table.set("pivots", existing)
|
|
1222
|
+
|
|
1223
|
+
return cast("Select", self)
|
|
1224
|
+
|
|
1225
|
+
|
|
1226
|
+
@trait
|
|
1227
|
+
class CommonTableExpressionMixin:
|
|
1228
|
+
__slots__ = ()
|
|
1229
|
+
|
|
1230
|
+
def get_expression(self) -> exp.Expression | None: ...
|
|
1231
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
1232
|
+
|
|
1233
|
+
_with_ctes: Any
|
|
1234
|
+
dialect: Any
|
|
1235
|
+
|
|
1236
|
+
def with_(self, name: str, query: Any | str, recursive: bool = False, columns: list[str] | None = None) -> Self:
|
|
1237
|
+
"""Add a CTE via the WITH clause.
|
|
1238
|
+
|
|
1239
|
+
When ``query`` is another builder we reuse its expression, merge parameters with unique names, and let sqlglot handle the actual CTE wrapping to avoid duplicating ``_with_ctes`` state.
|
|
1240
|
+
"""
|
|
1241
|
+
builder = cast("QueryBuilder", self)
|
|
1242
|
+
expression = builder.get_expression()
|
|
1243
|
+
if expression is None:
|
|
1244
|
+
msg = "Cannot add WITH clause: expression not initialized."
|
|
1245
|
+
raise SQLBuilderError(msg)
|
|
1246
|
+
|
|
1247
|
+
if not isinstance(expression, (exp.Select, exp.Insert, exp.Update, exp.Delete)):
|
|
1248
|
+
msg = f"Cannot add WITH clause to {type(expression).__name__} expression."
|
|
1249
|
+
raise SQLBuilderError(msg)
|
|
1250
|
+
|
|
1251
|
+
cte_select: exp.Expression | None
|
|
1252
|
+
if isinstance(query, str):
|
|
1253
|
+
cte_select = exp.maybe_parse(query, dialect=self.dialect)
|
|
1254
|
+
elif isinstance(query, exp.Expression):
|
|
1255
|
+
cte_select = query
|
|
1256
|
+
else:
|
|
1257
|
+
cte_select = query.get_expression()
|
|
1258
|
+
if cte_select is None:
|
|
1259
|
+
msg = f"Could not get expression from builder: {query}"
|
|
1260
|
+
raise SQLBuilderError(msg)
|
|
1261
|
+
|
|
1262
|
+
built_query = query.to_statement()
|
|
1263
|
+
parameters = built_query.parameters
|
|
1264
|
+
if isinstance(parameters, dict):
|
|
1265
|
+
param_mapping: dict[str, str] = {}
|
|
1266
|
+
for param_name, param_value in parameters.items():
|
|
1267
|
+
unique_name = builder._generate_unique_parameter_name(f"{name}_{param_name}")
|
|
1268
|
+
param_mapping[param_name] = unique_name
|
|
1269
|
+
builder.add_parameter(param_value, name=unique_name)
|
|
1270
|
+
cte_select = builder._update_placeholders_in_expression(cte_select, param_mapping)
|
|
1271
|
+
elif isinstance(parameters, (list, tuple)):
|
|
1272
|
+
for param_value in parameters:
|
|
1273
|
+
builder.add_parameter(param_value)
|
|
1274
|
+
elif parameters is not None:
|
|
1275
|
+
builder.add_parameter(parameters)
|
|
1276
|
+
|
|
1277
|
+
if cte_select is None:
|
|
1278
|
+
msg = f"Could not parse CTE query: {query}"
|
|
1279
|
+
raise SQLBuilderError(msg)
|
|
1280
|
+
|
|
1281
|
+
if isinstance(expression, (exp.Select, exp.Insert, exp.Update)):
|
|
1282
|
+
updated = expression.with_(name, as_=cte_select.copy(), recursive=recursive, copy=True)
|
|
1283
|
+
builder.set_expression(updated)
|
|
1284
|
+
|
|
1285
|
+
return cast("Self", builder)
|
|
1286
|
+
|
|
1287
|
+
|
|
1288
|
+
@trait
|
|
1289
|
+
class SetOperationMixin:
|
|
1290
|
+
__slots__ = ()
|
|
1291
|
+
|
|
1292
|
+
def get_expression(self) -> exp.Expression | None: ...
|
|
1293
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
1294
|
+
def set_parameters(self, parameters: dict[str, Any]) -> None: ...
|
|
1295
|
+
|
|
1296
|
+
dialect: Any = None
|
|
1297
|
+
|
|
1298
|
+
def union(self, other: Any, all_: bool = False) -> Self:
|
|
1299
|
+
return self._combine_with_other(other, operator="union", distinct=not all_)
|
|
1300
|
+
|
|
1301
|
+
def intersect(self, other: Any) -> Self:
|
|
1302
|
+
return self._combine_with_other(other, operator="intersect", distinct=True)
|
|
1303
|
+
|
|
1304
|
+
def except_(self, other: Any) -> Self:
|
|
1305
|
+
return self._combine_with_other(other, operator="except", distinct=True)
|
|
1306
|
+
|
|
1307
|
+
def _combine_with_other(self, other: Any, *, operator: str, distinct: bool) -> Self:
|
|
1308
|
+
builder = cast("QueryBuilder", self)
|
|
1309
|
+
|
|
1310
|
+
if not isinstance(other, QueryBuilder):
|
|
1311
|
+
msg = "Set operations require another SQLSpec query builder."
|
|
1312
|
+
raise SQLBuilderError(msg)
|
|
1313
|
+
|
|
1314
|
+
other_builder = other
|
|
1315
|
+
left_expr = builder._build_final_expression(copy=True)
|
|
1316
|
+
right_expr = other_builder._build_final_expression(copy=True)
|
|
1317
|
+
|
|
1318
|
+
merged_parameters: dict[str, Any] = dict(builder.parameters)
|
|
1319
|
+
rename_map: dict[str, str] = {}
|
|
1320
|
+
for param_name, param_value in other_builder.parameters.items():
|
|
1321
|
+
target_name = param_name
|
|
1322
|
+
if target_name in merged_parameters:
|
|
1323
|
+
counter = 1
|
|
1324
|
+
while True:
|
|
1325
|
+
candidate = f"{param_name}_right_{counter}"
|
|
1326
|
+
if candidate not in merged_parameters:
|
|
1327
|
+
target_name = candidate
|
|
1328
|
+
break
|
|
1329
|
+
counter += 1
|
|
1330
|
+
rename_map[param_name] = target_name
|
|
1331
|
+
merged_parameters[target_name] = param_value
|
|
1332
|
+
|
|
1333
|
+
if rename_map:
|
|
1334
|
+
right_expr = builder._update_placeholders_in_expression(right_expr, rename_map)
|
|
1335
|
+
|
|
1336
|
+
combined: exp.Expression
|
|
1337
|
+
if operator == "union":
|
|
1338
|
+
combined = exp.union(left_expr, right_expr, distinct=distinct)
|
|
1339
|
+
elif operator == "intersect":
|
|
1340
|
+
combined = exp.intersect(left_expr, right_expr, distinct=distinct)
|
|
1341
|
+
elif operator == "except":
|
|
1342
|
+
combined = exp.except_(left_expr, right_expr)
|
|
1343
|
+
else: # pragma: no cover - defensive
|
|
1344
|
+
msg = f"Unsupported set operation: {operator}"
|
|
1345
|
+
raise SQLBuilderError(msg)
|
|
1346
|
+
|
|
1347
|
+
new_builder = builder._spawn_like_self()
|
|
1348
|
+
new_builder.set_expression(combined)
|
|
1349
|
+
new_builder.set_parameters(merged_parameters)
|
|
1350
|
+
return cast("Self", new_builder)
|
|
1351
|
+
|
|
1352
|
+
|
|
1353
|
+
TABLE_HINT_PATTERN: Final[str] = r"\b{}\b(\s+AS\s+\w+)?"
|
|
1354
|
+
|
|
1355
|
+
|
|
1356
|
+
def _parse_hint_expression(hint: Any, dialect: "DialectType | str | None") -> exp.Expression:
|
|
1357
|
+
try:
|
|
1358
|
+
hint_str = str(hint)
|
|
1359
|
+
hint_expr: exp.Expression | None = exp.maybe_parse(hint_str, dialect=dialect)
|
|
1360
|
+
return hint_expr or exp.Anonymous(this=hint_str)
|
|
1361
|
+
except Exception:
|
|
1362
|
+
return exp.Anonymous(this=str(hint))
|
|
1363
|
+
|
|
1364
|
+
|
|
1365
|
+
class _TableHintReplacer:
|
|
1366
|
+
__slots__ = ("_hint", "_table")
|
|
1367
|
+
|
|
1368
|
+
def __init__(self, hint: str, table: str) -> None:
|
|
1369
|
+
self._hint = hint
|
|
1370
|
+
self._table = table
|
|
1371
|
+
|
|
1372
|
+
def __call__(self, match: "re.Match[str]") -> str:
|
|
1373
|
+
alias_part = match.group(1) or ""
|
|
1374
|
+
return f"/*+ {self._hint} */ {self._table}{alias_part}"
|
|
1375
|
+
|
|
1376
|
+
|
|
1377
|
+
class Select(
|
|
1378
|
+
QueryBuilder,
|
|
1379
|
+
WhereClauseMixin,
|
|
1380
|
+
OrderByClauseMixin,
|
|
1381
|
+
LimitOffsetClauseMixin,
|
|
1382
|
+
SelectClauseMixin,
|
|
1383
|
+
JoinClauseMixin,
|
|
1384
|
+
HavingClauseMixin,
|
|
1385
|
+
SetOperationMixin,
|
|
1386
|
+
CommonTableExpressionMixin,
|
|
1387
|
+
PivotClauseMixin,
|
|
1388
|
+
UnpivotClauseMixin,
|
|
1389
|
+
ExplainMixin,
|
|
1390
|
+
):
|
|
1391
|
+
"""Builder for SELECT queries.
|
|
1392
|
+
|
|
1393
|
+
Provides a fluent interface for constructing SQL SELECT statements
|
|
1394
|
+
with parameter binding and validation.
|
|
1395
|
+
|
|
1396
|
+
Example:
|
|
1397
|
+
>>> class User(BaseModel):
|
|
1398
|
+
... id: int
|
|
1399
|
+
... name: str
|
|
1400
|
+
>>> builder = Select("id", "name").from_("users")
|
|
1401
|
+
>>> result = driver.execute(builder)
|
|
1402
|
+
"""
|
|
1403
|
+
|
|
1404
|
+
__slots__ = ("_hints",)
|
|
1405
|
+
_expression: exp.Expression | None
|
|
1406
|
+
|
|
1407
|
+
def __init__(self, *columns: str, **kwargs: Any) -> None:
|
|
1408
|
+
"""Initialize SELECT with optional columns.
|
|
1409
|
+
|
|
1410
|
+
Args:
|
|
1411
|
+
*columns: Column names to select (e.g., "id", "name", "u.email")
|
|
1412
|
+
**kwargs: Additional QueryBuilder arguments (dialect, schema, etc.)
|
|
1413
|
+
|
|
1414
|
+
Examples:
|
|
1415
|
+
Select("id", "name") # Shorthand for Select().select("id", "name")
|
|
1416
|
+
Select() # Same as Select() - start empty
|
|
1417
|
+
"""
|
|
1418
|
+
(dialect, schema, enable_optimization, optimize_joins, optimize_predicates, simplify_expressions) = (
|
|
1419
|
+
self._parse_query_builder_kwargs(kwargs)
|
|
1420
|
+
)
|
|
1421
|
+
super().__init__(
|
|
1422
|
+
dialect=dialect,
|
|
1423
|
+
schema=schema,
|
|
1424
|
+
enable_optimization=enable_optimization,
|
|
1425
|
+
optimize_joins=optimize_joins,
|
|
1426
|
+
optimize_predicates=optimize_predicates,
|
|
1427
|
+
simplify_expressions=simplify_expressions,
|
|
1428
|
+
)
|
|
1429
|
+
|
|
1430
|
+
self._hints: list[dict[str, object]] = []
|
|
1431
|
+
|
|
1432
|
+
self._initialize_expression()
|
|
1433
|
+
|
|
1434
|
+
if columns:
|
|
1435
|
+
self.select(*columns)
|
|
1436
|
+
|
|
1437
|
+
@property
|
|
1438
|
+
def _expected_result_type(self) -> "type[SQLResult]":
|
|
1439
|
+
"""Get the expected result type for SELECT operations.
|
|
1440
|
+
|
|
1441
|
+
Returns:
|
|
1442
|
+
type: The SelectResult type.
|
|
1443
|
+
"""
|
|
1444
|
+
return SQLResult
|
|
1445
|
+
|
|
1446
|
+
def _create_base_expression(self) -> exp.Select:
|
|
1447
|
+
"""Create base SELECT expression."""
|
|
1448
|
+
if self._expression is None or not isinstance(self._expression, exp.Select):
|
|
1449
|
+
self._expression = exp.Select()
|
|
1450
|
+
return self._expression
|
|
1451
|
+
|
|
1452
|
+
def with_hint(
|
|
1453
|
+
self, hint: "str", *, location: "str" = "statement", table: "str | None" = None, dialect: "str | None" = None
|
|
1454
|
+
) -> "Self":
|
|
1455
|
+
"""Attach an optimizer or dialect-specific hint to the query.
|
|
1456
|
+
|
|
1457
|
+
Args:
|
|
1458
|
+
hint: The raw hint string (e.g., 'INDEX(users idx_users_name)').
|
|
1459
|
+
location: Where to apply the hint ('statement', 'table').
|
|
1460
|
+
table: Table name if the hint is for a specific table.
|
|
1461
|
+
dialect: Restrict the hint to a specific dialect (optional).
|
|
1462
|
+
|
|
1463
|
+
Returns:
|
|
1464
|
+
The current builder instance for method chaining.
|
|
1465
|
+
"""
|
|
1466
|
+
self._hints.append({"hint": hint, "location": location, "table": table, "dialect": dialect})
|
|
1467
|
+
return self
|
|
1468
|
+
|
|
1469
|
+
def build(self, dialect: "DialectType" = None) -> "BuiltQuery":
|
|
1470
|
+
"""Builds the SQL query string and parameters with hint injection.
|
|
1471
|
+
|
|
1472
|
+
Args:
|
|
1473
|
+
dialect: Optional dialect override for SQL generation.
|
|
1474
|
+
|
|
1475
|
+
Returns:
|
|
1476
|
+
BuiltQuery: A dataclass containing the SQL string and parameters.
|
|
1477
|
+
"""
|
|
1478
|
+
safe_query = super().build(dialect=dialect)
|
|
1479
|
+
|
|
1480
|
+
if not self._hints:
|
|
1481
|
+
return safe_query
|
|
1482
|
+
|
|
1483
|
+
target_dialect = str(dialect) if dialect else self.dialect_name
|
|
1484
|
+
|
|
1485
|
+
modified_expr = self._expression or self._create_base_expression()
|
|
1486
|
+
|
|
1487
|
+
if isinstance(modified_expr, exp.Select):
|
|
1488
|
+
statement_hints = [h["hint"] for h in self._hints if h.get("location") == "statement"]
|
|
1489
|
+
if statement_hints:
|
|
1490
|
+
hint_expressions: list[exp.Expression] = [
|
|
1491
|
+
_parse_hint_expression(hint, target_dialect) for hint in statement_hints
|
|
1492
|
+
]
|
|
1493
|
+
|
|
1494
|
+
if hint_expressions:
|
|
1495
|
+
modified_expr.set("hint", exp.Hint(expressions=hint_expressions))
|
|
1496
|
+
|
|
1497
|
+
modified_sql = modified_expr.sql(dialect=target_dialect, pretty=True)
|
|
1498
|
+
|
|
1499
|
+
for hint_dict in self._hints:
|
|
1500
|
+
if hint_dict.get("location") == "table" and hint_dict.get("table"):
|
|
1501
|
+
table = str(hint_dict["table"])
|
|
1502
|
+
hint = str(hint_dict["hint"])
|
|
1503
|
+
pattern = TABLE_HINT_PATTERN.format(re.escape(table))
|
|
1504
|
+
|
|
1505
|
+
modified_sql = re.sub(
|
|
1506
|
+
pattern, _TableHintReplacer(hint, table), modified_sql, count=1, flags=re.IGNORECASE
|
|
1507
|
+
)
|
|
1508
|
+
|
|
1509
|
+
return BuiltQuery(sql=modified_sql, parameters=safe_query.parameters, dialect=safe_query.dialect)
|
|
1510
|
+
|
|
1511
|
+
def _validate_select_expression(self) -> None:
|
|
1512
|
+
"""Validate that current expression is a valid SELECT statement.
|
|
1513
|
+
|
|
1514
|
+
Raises:
|
|
1515
|
+
SQLBuilderError: If expression is None or not a SELECT statement
|
|
1516
|
+
"""
|
|
1517
|
+
if self._expression is None or not isinstance(self._expression, exp.Select):
|
|
1518
|
+
msg = "Locking clauses can only be applied to SELECT statements"
|
|
1519
|
+
raise SQLBuilderError(msg)
|
|
1520
|
+
|
|
1521
|
+
def _validate_lock_parameters(self, skip_locked: bool, nowait: bool) -> None:
|
|
1522
|
+
"""Validate locking parameters for conflicting options.
|
|
1523
|
+
|
|
1524
|
+
Args:
|
|
1525
|
+
skip_locked: Whether SKIP LOCKED option is enabled
|
|
1526
|
+
nowait: Whether NOWAIT option is enabled
|
|
1527
|
+
|
|
1528
|
+
Raises:
|
|
1529
|
+
SQLBuilderError: If both skip_locked and nowait are True
|
|
1530
|
+
"""
|
|
1531
|
+
if skip_locked and nowait:
|
|
1532
|
+
msg = "Cannot use both skip_locked and nowait"
|
|
1533
|
+
raise SQLBuilderError(msg)
|
|
1534
|
+
|
|
1535
|
+
def for_update(
|
|
1536
|
+
self, *, skip_locked: bool = False, nowait: bool = False, of: "str | list[str] | None" = None
|
|
1537
|
+
) -> "Self":
|
|
1538
|
+
"""Add FOR UPDATE clause to SELECT statement for row-level locking.
|
|
1539
|
+
|
|
1540
|
+
Args:
|
|
1541
|
+
skip_locked: Skip rows that are already locked (SKIP LOCKED)
|
|
1542
|
+
nowait: Return immediately if row is locked (NOWAIT)
|
|
1543
|
+
of: Table names/aliases to lock (FOR UPDATE OF table)
|
|
1544
|
+
|
|
1545
|
+
Returns:
|
|
1546
|
+
Self for method chaining
|
|
1547
|
+
"""
|
|
1548
|
+
self._validate_select_expression()
|
|
1549
|
+
self._validate_lock_parameters(skip_locked, nowait)
|
|
1550
|
+
|
|
1551
|
+
assert self._expression is not None
|
|
1552
|
+
select_expr = cast("exp.Select", self._expression)
|
|
1553
|
+
|
|
1554
|
+
lock_args: dict[str, Any] = {"update": True}
|
|
1555
|
+
|
|
1556
|
+
if skip_locked:
|
|
1557
|
+
lock_args["wait"] = False
|
|
1558
|
+
elif nowait:
|
|
1559
|
+
lock_args["wait"] = True
|
|
1560
|
+
|
|
1561
|
+
if of:
|
|
1562
|
+
tables = [of] if isinstance(of, str) else of
|
|
1563
|
+
lock_args["expressions"] = [exp.to_identifier(str(t), quoted=is_explicitly_quoted(t)) for t in tables]
|
|
1564
|
+
self._lock_targets_quoted = any(is_explicitly_quoted(t) for t in tables)
|
|
1565
|
+
else:
|
|
1566
|
+
self._lock_targets_quoted = False
|
|
1567
|
+
|
|
1568
|
+
lock = exp.Lock(**lock_args)
|
|
1569
|
+
|
|
1570
|
+
current_locks = select_expr.args.get("locks", [])
|
|
1571
|
+
current_locks.append(lock)
|
|
1572
|
+
select_expr.set("locks", current_locks)
|
|
1573
|
+
|
|
1574
|
+
return self
|
|
1575
|
+
|
|
1576
|
+
def for_share(
|
|
1577
|
+
self, *, skip_locked: bool = False, nowait: bool = False, of: "str | list[str] | None" = None
|
|
1578
|
+
) -> "Self":
|
|
1579
|
+
"""Add FOR SHARE clause for shared row-level locking.
|
|
1580
|
+
|
|
1581
|
+
Args:
|
|
1582
|
+
skip_locked: Skip rows that are already locked (SKIP LOCKED)
|
|
1583
|
+
nowait: Return immediately if row is locked (NOWAIT)
|
|
1584
|
+
of: Table names/aliases to lock (FOR SHARE OF table)
|
|
1585
|
+
|
|
1586
|
+
Returns:
|
|
1587
|
+
Self for method chaining
|
|
1588
|
+
"""
|
|
1589
|
+
self._validate_select_expression()
|
|
1590
|
+
self._validate_lock_parameters(skip_locked, nowait)
|
|
1591
|
+
|
|
1592
|
+
assert self._expression is not None
|
|
1593
|
+
select_expr = cast("exp.Select", self._expression)
|
|
1594
|
+
|
|
1595
|
+
lock_args: dict[str, Any] = {"update": False}
|
|
1596
|
+
|
|
1597
|
+
if skip_locked:
|
|
1598
|
+
lock_args["wait"] = False
|
|
1599
|
+
elif nowait:
|
|
1600
|
+
lock_args["wait"] = True
|
|
1601
|
+
|
|
1602
|
+
if of:
|
|
1603
|
+
tables = [of] if isinstance(of, str) else of
|
|
1604
|
+
lock_args["expressions"] = [exp.to_identifier(str(t), quoted=is_explicitly_quoted(t)) for t in tables]
|
|
1605
|
+
self._lock_targets_quoted = any(is_explicitly_quoted(t) for t in tables)
|
|
1606
|
+
else:
|
|
1607
|
+
self._lock_targets_quoted = False
|
|
1608
|
+
|
|
1609
|
+
lock = exp.Lock(**lock_args)
|
|
1610
|
+
|
|
1611
|
+
current_locks = select_expr.args.get("locks", [])
|
|
1612
|
+
current_locks.append(lock)
|
|
1613
|
+
select_expr.set("locks", current_locks)
|
|
1614
|
+
|
|
1615
|
+
return self
|
|
1616
|
+
|
|
1617
|
+
def for_key_share(self) -> "Self":
|
|
1618
|
+
"""Add FOR KEY SHARE clause (PostgreSQL-specific).
|
|
1619
|
+
|
|
1620
|
+
FOR KEY SHARE is like FOR SHARE, but the lock is weaker:
|
|
1621
|
+
SELECT FOR UPDATE is blocked, but not SELECT FOR NO KEY UPDATE.
|
|
1622
|
+
|
|
1623
|
+
Returns:
|
|
1624
|
+
Self for method chaining
|
|
1625
|
+
"""
|
|
1626
|
+
self._validate_select_expression()
|
|
1627
|
+
|
|
1628
|
+
assert self._expression is not None
|
|
1629
|
+
select_expr = cast("exp.Select", self._expression)
|
|
1630
|
+
|
|
1631
|
+
lock = exp.Lock(update=False, key=True)
|
|
1632
|
+
|
|
1633
|
+
current_locks = select_expr.args.get("locks", [])
|
|
1634
|
+
current_locks.append(lock)
|
|
1635
|
+
select_expr.set("locks", current_locks)
|
|
1636
|
+
|
|
1637
|
+
return self
|
|
1638
|
+
|
|
1639
|
+
def for_no_key_update(self) -> "Self":
|
|
1640
|
+
"""Add FOR NO KEY UPDATE clause (PostgreSQL-specific).
|
|
1641
|
+
|
|
1642
|
+
FOR NO KEY UPDATE is like FOR UPDATE, but the lock is weaker:
|
|
1643
|
+
it does not block SELECT FOR KEY SHARE commands that attempt to
|
|
1644
|
+
acquire a share lock on the same rows.
|
|
1645
|
+
|
|
1646
|
+
Returns:
|
|
1647
|
+
Self for method chaining
|
|
1648
|
+
"""
|
|
1649
|
+
self._validate_select_expression()
|
|
1650
|
+
|
|
1651
|
+
assert self._expression is not None
|
|
1652
|
+
select_expr = cast("exp.Select", self._expression)
|
|
1653
|
+
|
|
1654
|
+
lock = exp.Lock(update=True, key=False)
|
|
1655
|
+
|
|
1656
|
+
current_locks = select_expr.args.get("locks", [])
|
|
1657
|
+
current_locks.append(lock)
|
|
1658
|
+
select_expr.set("locks", current_locks)
|
|
1659
|
+
|
|
1660
|
+
return self
|