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,1697 @@
|
|
|
1
|
+
"""SQL factory for creating SQL builders and column expressions.
|
|
2
|
+
|
|
3
|
+
Provides statement builders (select, insert, update, etc.) and column expressions.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import hashlib
|
|
7
|
+
import logging
|
|
8
|
+
from collections.abc import Mapping, Sequence
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Union, cast
|
|
10
|
+
|
|
11
|
+
import sqlglot
|
|
12
|
+
from sqlglot import exp
|
|
13
|
+
from sqlglot.dialects.dialect import DialectType
|
|
14
|
+
from sqlglot.errors import ParseError as SQLGlotParseError
|
|
15
|
+
|
|
16
|
+
from sqlspec.builder._column import Column
|
|
17
|
+
from sqlspec.builder._ddl import (
|
|
18
|
+
AlterTable,
|
|
19
|
+
CommentOn,
|
|
20
|
+
CreateIndex,
|
|
21
|
+
CreateMaterializedView,
|
|
22
|
+
CreateSchema,
|
|
23
|
+
CreateTable,
|
|
24
|
+
CreateTableAsSelect,
|
|
25
|
+
CreateView,
|
|
26
|
+
DropIndex,
|
|
27
|
+
DropSchema,
|
|
28
|
+
DropTable,
|
|
29
|
+
DropView,
|
|
30
|
+
RenameTable,
|
|
31
|
+
Truncate,
|
|
32
|
+
)
|
|
33
|
+
from sqlspec.builder._delete import Delete
|
|
34
|
+
from sqlspec.builder._explain import Explain
|
|
35
|
+
from sqlspec.builder._expression_wrappers import (
|
|
36
|
+
AggregateExpression,
|
|
37
|
+
ConversionExpression,
|
|
38
|
+
FunctionExpression,
|
|
39
|
+
MathExpression,
|
|
40
|
+
StringExpression,
|
|
41
|
+
)
|
|
42
|
+
from sqlspec.builder._insert import Insert
|
|
43
|
+
from sqlspec.builder._join import JoinBuilder, create_join_builder
|
|
44
|
+
from sqlspec.builder._merge import Merge
|
|
45
|
+
from sqlspec.builder._parsing_utils import extract_expression, to_expression
|
|
46
|
+
from sqlspec.builder._select import Case, Select, SubqueryBuilder, WindowFunctionBuilder
|
|
47
|
+
from sqlspec.builder._update import Update
|
|
48
|
+
from sqlspec.core import SQL
|
|
49
|
+
from sqlspec.core.explain import ExplainFormat, ExplainOptions
|
|
50
|
+
from sqlspec.exceptions import SQLBuilderError
|
|
51
|
+
from sqlspec.utils.logging import get_logger
|
|
52
|
+
|
|
53
|
+
if TYPE_CHECKING:
|
|
54
|
+
from collections.abc import Mapping, Sequence
|
|
55
|
+
|
|
56
|
+
from sqlspec.builder._expression_wrappers import ExpressionWrapper
|
|
57
|
+
from sqlspec.protocols import SQLBuilderProtocol
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
__all__ = (
|
|
61
|
+
"AlterTable",
|
|
62
|
+
"Case",
|
|
63
|
+
"Column",
|
|
64
|
+
"CommentOn",
|
|
65
|
+
"CreateIndex",
|
|
66
|
+
"CreateMaterializedView",
|
|
67
|
+
"CreateSchema",
|
|
68
|
+
"CreateTable",
|
|
69
|
+
"CreateTableAsSelect",
|
|
70
|
+
"CreateView",
|
|
71
|
+
"Delete",
|
|
72
|
+
"DropIndex",
|
|
73
|
+
"DropSchema",
|
|
74
|
+
"DropTable",
|
|
75
|
+
"DropView",
|
|
76
|
+
"Explain",
|
|
77
|
+
"Insert",
|
|
78
|
+
"Merge",
|
|
79
|
+
"RenameTable",
|
|
80
|
+
"SQLFactory",
|
|
81
|
+
"Select",
|
|
82
|
+
"Truncate",
|
|
83
|
+
"Update",
|
|
84
|
+
"WindowFunctionBuilder",
|
|
85
|
+
"build_copy_from_statement",
|
|
86
|
+
"build_copy_statement",
|
|
87
|
+
"build_copy_to_statement",
|
|
88
|
+
"sql",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
logger = get_logger("sqlspec.builder.factory")
|
|
92
|
+
|
|
93
|
+
MIN_SQL_LIKE_STRING_LENGTH = 6
|
|
94
|
+
MIN_DECODE_ARGS = 2
|
|
95
|
+
SQL_STARTERS = {
|
|
96
|
+
"SELECT",
|
|
97
|
+
"INSERT",
|
|
98
|
+
"UPDATE",
|
|
99
|
+
"DELETE",
|
|
100
|
+
"MERGE",
|
|
101
|
+
"WITH",
|
|
102
|
+
"CALL",
|
|
103
|
+
"DECLARE",
|
|
104
|
+
"BEGIN",
|
|
105
|
+
"END",
|
|
106
|
+
"CREATE",
|
|
107
|
+
"DROP",
|
|
108
|
+
"ALTER",
|
|
109
|
+
"TRUNCATE",
|
|
110
|
+
"RENAME",
|
|
111
|
+
"GRANT",
|
|
112
|
+
"REVOKE",
|
|
113
|
+
"SET",
|
|
114
|
+
"SHOW",
|
|
115
|
+
"USE",
|
|
116
|
+
"EXPLAIN",
|
|
117
|
+
"OPTIMIZE",
|
|
118
|
+
"VACUUM",
|
|
119
|
+
"COPY",
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _fingerprint_sql(sql: str) -> str:
|
|
124
|
+
digest = hashlib.sha256(sql.encode("utf-8", errors="replace")).hexdigest()
|
|
125
|
+
return digest[:12]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _normalize_copy_dialect(dialect: DialectType | None) -> str:
|
|
129
|
+
if dialect is None:
|
|
130
|
+
return "postgres"
|
|
131
|
+
if isinstance(dialect, str):
|
|
132
|
+
return dialect
|
|
133
|
+
return str(dialect)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _to_copy_schema(table: str, columns: "Sequence[str] | None") -> exp.Expression:
|
|
137
|
+
base = exp.table_(table)
|
|
138
|
+
if not columns:
|
|
139
|
+
return base
|
|
140
|
+
column_nodes = [exp.column(column_name) for column_name in columns]
|
|
141
|
+
return exp.Schema(this=base, expressions=column_nodes)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _build_copy_expression(
|
|
145
|
+
*, direction: str, table: str, location: str, columns: "Sequence[str] | None", options: "Mapping[str, Any] | None"
|
|
146
|
+
) -> exp.Copy:
|
|
147
|
+
copy_args: dict[str, Any] = {"this": _to_copy_schema(table, columns), "files": [exp.Literal.string(location)]}
|
|
148
|
+
|
|
149
|
+
if direction == "from":
|
|
150
|
+
copy_args["kind"] = True
|
|
151
|
+
elif direction == "to":
|
|
152
|
+
copy_args["kind"] = False
|
|
153
|
+
|
|
154
|
+
if options:
|
|
155
|
+
params: list[exp.CopyParameter] = []
|
|
156
|
+
for key, value in options.items():
|
|
157
|
+
identifier = exp.Var(this=str(key).upper())
|
|
158
|
+
value_expression: exp.Expression
|
|
159
|
+
if isinstance(value, bool):
|
|
160
|
+
value_expression = exp.Boolean(this=value)
|
|
161
|
+
elif value is None:
|
|
162
|
+
value_expression = exp.null()
|
|
163
|
+
elif isinstance(value, (int, float)):
|
|
164
|
+
value_expression = exp.Literal.number(value)
|
|
165
|
+
elif isinstance(value, (list, tuple)):
|
|
166
|
+
elements = [exp.Literal.string(str(item)) for item in value]
|
|
167
|
+
value_expression = exp.Array(expressions=elements)
|
|
168
|
+
else:
|
|
169
|
+
value_expression = exp.Literal.string(str(value))
|
|
170
|
+
params.append(exp.CopyParameter(this=identifier, expression=value_expression))
|
|
171
|
+
copy_args["params"] = params
|
|
172
|
+
|
|
173
|
+
return exp.Copy(**copy_args)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def build_copy_statement(
|
|
177
|
+
*,
|
|
178
|
+
direction: str,
|
|
179
|
+
table: str,
|
|
180
|
+
location: str,
|
|
181
|
+
columns: "Sequence[str] | None" = None,
|
|
182
|
+
options: "Mapping[str, Any] | None" = None,
|
|
183
|
+
dialect: DialectType | None = None,
|
|
184
|
+
) -> SQL:
|
|
185
|
+
expression = _build_copy_expression(
|
|
186
|
+
direction=direction, table=table, location=location, columns=columns, options=options
|
|
187
|
+
)
|
|
188
|
+
rendered = expression.sql(dialect=_normalize_copy_dialect(dialect))
|
|
189
|
+
return SQL(rendered)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def build_copy_from_statement(
|
|
193
|
+
table: str,
|
|
194
|
+
source: str,
|
|
195
|
+
*,
|
|
196
|
+
columns: "Sequence[str] | None" = None,
|
|
197
|
+
options: "Mapping[str, Any] | None" = None,
|
|
198
|
+
dialect: DialectType | None = None,
|
|
199
|
+
) -> SQL:
|
|
200
|
+
return build_copy_statement(
|
|
201
|
+
direction="from", table=table, location=source, columns=columns, options=options, dialect=dialect
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def build_copy_to_statement(
|
|
206
|
+
table: str,
|
|
207
|
+
target: str,
|
|
208
|
+
*,
|
|
209
|
+
columns: "Sequence[str] | None" = None,
|
|
210
|
+
options: "Mapping[str, Any] | None" = None,
|
|
211
|
+
dialect: DialectType | None = None,
|
|
212
|
+
) -> SQL:
|
|
213
|
+
return build_copy_statement(
|
|
214
|
+
direction="to", table=table, location=target, columns=columns, options=options, dialect=dialect
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class SQLFactory:
|
|
219
|
+
"""Factory for creating SQL builders and column expressions."""
|
|
220
|
+
|
|
221
|
+
@staticmethod
|
|
222
|
+
def _detect_type_from_expression(parsed_expr: exp.Expression) -> str:
|
|
223
|
+
if parsed_expr.key:
|
|
224
|
+
return parsed_expr.key.upper()
|
|
225
|
+
command_type = type(parsed_expr).__name__.upper()
|
|
226
|
+
if command_type == "COMMAND" and parsed_expr.this:
|
|
227
|
+
return str(parsed_expr.this).upper()
|
|
228
|
+
return command_type
|
|
229
|
+
|
|
230
|
+
@staticmethod
|
|
231
|
+
def _parse_sql_expression(sql: str, dialect: DialectType | None) -> "exp.Expression | None":
|
|
232
|
+
try:
|
|
233
|
+
return sqlglot.parse_one(sql, read=dialect)
|
|
234
|
+
except SQLGlotParseError:
|
|
235
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
236
|
+
logger.debug(
|
|
237
|
+
"Failed to parse SQL for type detection",
|
|
238
|
+
extra={"sql_length": len(sql), "sql_hash": _fingerprint_sql(sql)},
|
|
239
|
+
)
|
|
240
|
+
except (ValueError, TypeError, AttributeError):
|
|
241
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
242
|
+
logger.debug(
|
|
243
|
+
"Unexpected error during SQL type detection",
|
|
244
|
+
exc_info=True,
|
|
245
|
+
extra={"sql_length": len(sql), "sql_hash": _fingerprint_sql(sql)},
|
|
246
|
+
)
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
@classmethod
|
|
250
|
+
def detect_sql_type(cls, sql: str, dialect: DialectType = None) -> str:
|
|
251
|
+
parsed_expr = cls._parse_sql_expression(sql, dialect)
|
|
252
|
+
if parsed_expr is None:
|
|
253
|
+
return "UNKNOWN"
|
|
254
|
+
return cls._detect_type_from_expression(parsed_expr)
|
|
255
|
+
|
|
256
|
+
def __init__(self, dialect: DialectType = None) -> None:
|
|
257
|
+
"""Initialize the SQL factory.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
dialect: Default SQL dialect to use for all builders.
|
|
261
|
+
"""
|
|
262
|
+
self.dialect = dialect
|
|
263
|
+
|
|
264
|
+
def __call__(self, statement: str, dialect: DialectType = None) -> "Any":
|
|
265
|
+
"""Create a SelectBuilder from a SQL string, or SQL object for DML with RETURNING.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
statement: The SQL statement string.
|
|
269
|
+
dialect: Optional SQL dialect.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
SelectBuilder instance for SELECT/WITH statements,
|
|
273
|
+
SQL object for DML statements with RETURNING clause.
|
|
274
|
+
|
|
275
|
+
Raises:
|
|
276
|
+
SQLBuilderError: If the SQL is not a SELECT/CTE/DML+RETURNING statement.
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
try:
|
|
280
|
+
parsed_expr = sqlglot.parse_one(statement, read=dialect or self.dialect)
|
|
281
|
+
except Exception as e:
|
|
282
|
+
msg = f"Failed to parse SQL: {e}"
|
|
283
|
+
raise SQLBuilderError(msg) from e
|
|
284
|
+
actual_type = type(parsed_expr).__name__.upper()
|
|
285
|
+
expr_type_map = {
|
|
286
|
+
"SELECT": "SELECT",
|
|
287
|
+
"INSERT": "INSERT",
|
|
288
|
+
"UPDATE": "UPDATE",
|
|
289
|
+
"DELETE": "DELETE",
|
|
290
|
+
"MERGE": "MERGE",
|
|
291
|
+
"WITH": "WITH",
|
|
292
|
+
}
|
|
293
|
+
actual_type_str = expr_type_map.get(actual_type, actual_type)
|
|
294
|
+
if actual_type_str == "SELECT" or (
|
|
295
|
+
actual_type_str == "WITH" and parsed_expr.this and isinstance(parsed_expr.this, exp.Select)
|
|
296
|
+
):
|
|
297
|
+
builder = Select(dialect=dialect or self.dialect)
|
|
298
|
+
builder.set_expression(parsed_expr)
|
|
299
|
+
return builder
|
|
300
|
+
|
|
301
|
+
if actual_type_str in {"INSERT", "UPDATE", "DELETE"} and parsed_expr.args.get("returning") is not None:
|
|
302
|
+
return SQL(statement)
|
|
303
|
+
|
|
304
|
+
msg = (
|
|
305
|
+
f"sql(...) only supports SELECT statements or DML statements with RETURNING clause. "
|
|
306
|
+
f"Detected type: {actual_type_str}. "
|
|
307
|
+
f"Use sql.{actual_type_str.lower()}() instead."
|
|
308
|
+
)
|
|
309
|
+
raise SQLBuilderError(msg)
|
|
310
|
+
|
|
311
|
+
def select(
|
|
312
|
+
self, *columns_or_sql: Union[str, exp.Expression, Column, "SQL", "Case"], dialect: DialectType = None
|
|
313
|
+
) -> "Select":
|
|
314
|
+
builder_dialect = dialect or self.dialect
|
|
315
|
+
if len(columns_or_sql) == 1 and isinstance(columns_or_sql[0], str):
|
|
316
|
+
sql_candidate = columns_or_sql[0].strip()
|
|
317
|
+
if self._looks_like_sql(sql_candidate):
|
|
318
|
+
parsed_expr = self._parse_sql_expression(sql_candidate, builder_dialect)
|
|
319
|
+
detected = "UNKNOWN" if parsed_expr is None else self._detect_type_from_expression(parsed_expr)
|
|
320
|
+
if detected not in {"SELECT", "WITH"}:
|
|
321
|
+
msg = (
|
|
322
|
+
f"sql.select() expects a SELECT or WITH statement, got {detected}. "
|
|
323
|
+
f"Use sql.{detected.lower()}() if a dedicated builder exists, or ensure the SQL is SELECT/WITH."
|
|
324
|
+
)
|
|
325
|
+
raise SQLBuilderError(msg)
|
|
326
|
+
select_builder = Select(dialect=builder_dialect)
|
|
327
|
+
return self._populate_select_from_sql(select_builder, sql_candidate, parsed_expr)
|
|
328
|
+
select_builder = Select(dialect=builder_dialect)
|
|
329
|
+
if columns_or_sql:
|
|
330
|
+
select_builder.select(*columns_or_sql)
|
|
331
|
+
return select_builder
|
|
332
|
+
|
|
333
|
+
def insert(self, table_or_sql: str | None = None, dialect: DialectType = None) -> "Insert":
|
|
334
|
+
builder_dialect = dialect or self.dialect
|
|
335
|
+
builder = Insert(dialect=builder_dialect)
|
|
336
|
+
if table_or_sql:
|
|
337
|
+
if self._looks_like_sql(table_or_sql):
|
|
338
|
+
parsed_expr = self._parse_sql_expression(table_or_sql, builder_dialect)
|
|
339
|
+
detected = "UNKNOWN" if parsed_expr is None else self._detect_type_from_expression(parsed_expr)
|
|
340
|
+
if detected not in {"INSERT", "SELECT"}:
|
|
341
|
+
msg = (
|
|
342
|
+
f"sql.insert() expects INSERT or SELECT (for insert-from-select), got {detected}. "
|
|
343
|
+
f"Use sql.{detected.lower()}() if a dedicated builder exists, "
|
|
344
|
+
f"or ensure the SQL is INSERT/SELECT."
|
|
345
|
+
)
|
|
346
|
+
raise SQLBuilderError(msg)
|
|
347
|
+
return self._populate_insert_from_sql(builder, table_or_sql, parsed_expr)
|
|
348
|
+
return builder.into(table_or_sql)
|
|
349
|
+
return builder
|
|
350
|
+
|
|
351
|
+
def update(self, table_or_sql: str | None = None, dialect: DialectType = None) -> "Update":
|
|
352
|
+
builder_dialect = dialect or self.dialect
|
|
353
|
+
builder = Update(dialect=builder_dialect)
|
|
354
|
+
if table_or_sql:
|
|
355
|
+
if self._looks_like_sql(table_or_sql):
|
|
356
|
+
parsed_expr = self._parse_sql_expression(table_or_sql, builder_dialect)
|
|
357
|
+
detected = "UNKNOWN" if parsed_expr is None else self._detect_type_from_expression(parsed_expr)
|
|
358
|
+
if detected != "UPDATE":
|
|
359
|
+
msg = (
|
|
360
|
+
f"sql.update() expects UPDATE statement, got {detected}. "
|
|
361
|
+
f"Use sql.{detected.lower()}() if a dedicated builder exists."
|
|
362
|
+
)
|
|
363
|
+
raise SQLBuilderError(msg)
|
|
364
|
+
return self._populate_update_from_sql(builder, table_or_sql, parsed_expr)
|
|
365
|
+
return builder.table(table_or_sql)
|
|
366
|
+
return builder
|
|
367
|
+
|
|
368
|
+
def delete(self, table_or_sql: str | None = None, dialect: DialectType = None) -> "Delete":
|
|
369
|
+
builder_dialect = dialect or self.dialect
|
|
370
|
+
if table_or_sql and self._looks_like_sql(table_or_sql):
|
|
371
|
+
builder = Delete(dialect=builder_dialect)
|
|
372
|
+
parsed_expr = self._parse_sql_expression(table_or_sql, builder_dialect)
|
|
373
|
+
detected = "UNKNOWN" if parsed_expr is None else self._detect_type_from_expression(parsed_expr)
|
|
374
|
+
if detected != "DELETE":
|
|
375
|
+
msg = (
|
|
376
|
+
f"sql.delete() expects DELETE statement, got {detected}. "
|
|
377
|
+
f"Use sql.{detected.lower()}() if a dedicated builder exists."
|
|
378
|
+
)
|
|
379
|
+
raise SQLBuilderError(msg)
|
|
380
|
+
return self._populate_delete_from_sql(builder, table_or_sql, parsed_expr)
|
|
381
|
+
|
|
382
|
+
return Delete(table_or_sql, dialect=builder_dialect) if table_or_sql else Delete(dialect=builder_dialect)
|
|
383
|
+
|
|
384
|
+
def merge(self, table_or_sql: str | None = None, dialect: DialectType = None) -> "Merge":
|
|
385
|
+
builder_dialect = dialect or self.dialect
|
|
386
|
+
if table_or_sql and self._looks_like_sql(table_or_sql):
|
|
387
|
+
builder = Merge(dialect=builder_dialect)
|
|
388
|
+
parsed_expr = self._parse_sql_expression(table_or_sql, builder_dialect)
|
|
389
|
+
detected = "UNKNOWN" if parsed_expr is None else self._detect_type_from_expression(parsed_expr)
|
|
390
|
+
if detected != "MERGE":
|
|
391
|
+
msg = (
|
|
392
|
+
f"sql.merge() expects MERGE statement, got {detected}. "
|
|
393
|
+
f"Use sql.{detected.lower()}() if a dedicated builder exists."
|
|
394
|
+
)
|
|
395
|
+
raise SQLBuilderError(msg)
|
|
396
|
+
return self._populate_merge_from_sql(builder, table_or_sql, parsed_expr)
|
|
397
|
+
|
|
398
|
+
return Merge(table_or_sql, dialect=builder_dialect) if table_or_sql else Merge(dialect=builder_dialect)
|
|
399
|
+
|
|
400
|
+
def explain(
|
|
401
|
+
self,
|
|
402
|
+
statement: "str | exp.Expression | SQL | SQLBuilderProtocol",
|
|
403
|
+
*,
|
|
404
|
+
analyze: bool = False,
|
|
405
|
+
verbose: bool = False,
|
|
406
|
+
format: "ExplainFormat | str | None" = None,
|
|
407
|
+
dialect: DialectType = None,
|
|
408
|
+
) -> "Explain":
|
|
409
|
+
"""Create an EXPLAIN builder for a SQL statement.
|
|
410
|
+
|
|
411
|
+
Wraps any SQL statement in an EXPLAIN clause with dialect-aware
|
|
412
|
+
syntax generation.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
statement: SQL statement to explain (string, expression, SQL object, or builder)
|
|
416
|
+
analyze: Execute the statement and show actual runtime statistics
|
|
417
|
+
verbose: Show additional information
|
|
418
|
+
format: Output format (TEXT, JSON, XML, YAML, TREE, TRADITIONAL)
|
|
419
|
+
dialect: Optional SQL dialect override
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
Explain builder for further configuration
|
|
423
|
+
|
|
424
|
+
Examples:
|
|
425
|
+
Basic EXPLAIN:
|
|
426
|
+
plan = sql.explain("SELECT * FROM users").build()
|
|
427
|
+
|
|
428
|
+
With options:
|
|
429
|
+
plan = (
|
|
430
|
+
sql.explain("SELECT * FROM users", analyze=True, format="json")
|
|
431
|
+
.buffers()
|
|
432
|
+
.timing()
|
|
433
|
+
.build()
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
From QueryBuilder:
|
|
437
|
+
query = sql.select("*").from_("users").where("id = :id", id=1)
|
|
438
|
+
plan = sql.explain(query, analyze=True).build()
|
|
439
|
+
|
|
440
|
+
Chained configuration:
|
|
441
|
+
plan = (
|
|
442
|
+
sql.explain(sql.select("*").from_("large_table"))
|
|
443
|
+
.analyze()
|
|
444
|
+
.format("json")
|
|
445
|
+
.buffers()
|
|
446
|
+
.timing()
|
|
447
|
+
.build()
|
|
448
|
+
)
|
|
449
|
+
"""
|
|
450
|
+
builder_dialect = dialect or self.dialect
|
|
451
|
+
|
|
452
|
+
fmt = None
|
|
453
|
+
if format is not None:
|
|
454
|
+
fmt = ExplainFormat(format.lower()) if isinstance(format, str) else format
|
|
455
|
+
|
|
456
|
+
options = ExplainOptions(analyze=analyze, verbose=verbose, format=fmt)
|
|
457
|
+
|
|
458
|
+
return Explain(statement, dialect=builder_dialect, options=options)
|
|
459
|
+
|
|
460
|
+
@property
|
|
461
|
+
def merge_(self) -> "Merge":
|
|
462
|
+
"""Create a new MERGE builder (property shorthand).
|
|
463
|
+
|
|
464
|
+
Property that returns a new Merge builder instance using the factory's
|
|
465
|
+
default dialect. Cleaner syntax alternative to merge() method.
|
|
466
|
+
|
|
467
|
+
Examples:
|
|
468
|
+
query = sql.merge_.into("products").using(data, alias="src")
|
|
469
|
+
query = sql.merge_.into("products", alias="t").on("t.id = src.id")
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
New Merge builder instance
|
|
473
|
+
"""
|
|
474
|
+
return Merge(dialect=self.dialect)
|
|
475
|
+
|
|
476
|
+
def upsert(self, table: str, dialect: DialectType = None) -> "Merge | Insert":
|
|
477
|
+
"""Create an upsert builder (MERGE or INSERT ON CONFLICT).
|
|
478
|
+
|
|
479
|
+
Automatically selects the appropriate builder based on database dialect:
|
|
480
|
+
- PostgreSQL 15+, Oracle, BigQuery: Returns MERGE builder
|
|
481
|
+
- SQLite, DuckDB, MySQL: Returns INSERT builder with ON CONFLICT support
|
|
482
|
+
|
|
483
|
+
Args:
|
|
484
|
+
table: Target table name
|
|
485
|
+
dialect: Optional SQL dialect (uses factory default if not provided)
|
|
486
|
+
|
|
487
|
+
Returns:
|
|
488
|
+
MERGE builder for supported databases, INSERT builder otherwise
|
|
489
|
+
|
|
490
|
+
Examples:
|
|
491
|
+
PostgreSQL/Oracle/BigQuery (uses MERGE):
|
|
492
|
+
upsert_query = (
|
|
493
|
+
sql.upsert("products", dialect="postgres")
|
|
494
|
+
.using([{"id": 1, "name": "Product 1"}], alias="src")
|
|
495
|
+
.on("t.id = src.id")
|
|
496
|
+
.when_matched_then_update(name="src.name")
|
|
497
|
+
.when_not_matched_then_insert(id="src.id", name="src.name")
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
SQLite/DuckDB/MySQL (uses INSERT ON CONFLICT):
|
|
501
|
+
upsert_query = (
|
|
502
|
+
sql.upsert("products", dialect="sqlite")
|
|
503
|
+
.values(id=1, name="Product 1")
|
|
504
|
+
.on_conflict("id")
|
|
505
|
+
.do_update(name="EXCLUDED.name")
|
|
506
|
+
)
|
|
507
|
+
"""
|
|
508
|
+
builder_dialect = dialect or self.dialect
|
|
509
|
+
dialect_str = str(builder_dialect).lower() if builder_dialect else None
|
|
510
|
+
|
|
511
|
+
merge_supported = {"postgres", "postgresql", "oracle", "bigquery"}
|
|
512
|
+
|
|
513
|
+
if dialect_str in merge_supported:
|
|
514
|
+
return self.merge(table, dialect=builder_dialect)
|
|
515
|
+
|
|
516
|
+
return self.insert(table, dialect=builder_dialect)
|
|
517
|
+
|
|
518
|
+
def create_table(self, table_name: str, dialect: DialectType = None) -> "CreateTable":
|
|
519
|
+
"""Create a CREATE TABLE builder.
|
|
520
|
+
|
|
521
|
+
Args:
|
|
522
|
+
table_name: Name of the table to create
|
|
523
|
+
dialect: Optional SQL dialect
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
CreateTable builder instance
|
|
527
|
+
"""
|
|
528
|
+
return CreateTable(table_name, dialect=dialect or self.dialect)
|
|
529
|
+
|
|
530
|
+
def create_table_as_select(self, dialect: DialectType = None) -> "CreateTableAsSelect":
|
|
531
|
+
"""Create a CREATE TABLE AS SELECT builder.
|
|
532
|
+
|
|
533
|
+
Args:
|
|
534
|
+
dialect: Optional SQL dialect
|
|
535
|
+
|
|
536
|
+
Returns:
|
|
537
|
+
CreateTableAsSelect builder instance
|
|
538
|
+
"""
|
|
539
|
+
return CreateTableAsSelect(dialect=dialect or self.dialect)
|
|
540
|
+
|
|
541
|
+
def create_view(self, view_name: str, dialect: DialectType = None) -> "CreateView":
|
|
542
|
+
"""Create a CREATE VIEW builder.
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
view_name: Name of the view to create
|
|
546
|
+
dialect: Optional SQL dialect
|
|
547
|
+
|
|
548
|
+
Returns:
|
|
549
|
+
CreateView builder instance
|
|
550
|
+
"""
|
|
551
|
+
return CreateView(view_name, dialect=dialect or self.dialect)
|
|
552
|
+
|
|
553
|
+
def create_materialized_view(self, view_name: str, dialect: DialectType = None) -> "CreateMaterializedView":
|
|
554
|
+
"""Create a CREATE MATERIALIZED VIEW builder.
|
|
555
|
+
|
|
556
|
+
Args:
|
|
557
|
+
view_name: Name of the materialized view to create
|
|
558
|
+
dialect: Optional SQL dialect
|
|
559
|
+
|
|
560
|
+
Returns:
|
|
561
|
+
CreateMaterializedView builder instance
|
|
562
|
+
"""
|
|
563
|
+
return CreateMaterializedView(view_name, dialect=dialect or self.dialect)
|
|
564
|
+
|
|
565
|
+
def create_index(self, index_name: str, dialect: DialectType = None) -> "CreateIndex":
|
|
566
|
+
"""Create a CREATE INDEX builder.
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
index_name: Name of the index to create
|
|
570
|
+
dialect: Optional SQL dialect
|
|
571
|
+
|
|
572
|
+
Returns:
|
|
573
|
+
CreateIndex builder instance
|
|
574
|
+
"""
|
|
575
|
+
return CreateIndex(index_name, dialect=dialect or self.dialect)
|
|
576
|
+
|
|
577
|
+
def create_schema(self, schema_name: str, dialect: DialectType = None) -> "CreateSchema":
|
|
578
|
+
"""Create a CREATE SCHEMA builder.
|
|
579
|
+
|
|
580
|
+
Args:
|
|
581
|
+
schema_name: Name of the schema to create
|
|
582
|
+
dialect: Optional SQL dialect
|
|
583
|
+
|
|
584
|
+
Returns:
|
|
585
|
+
CreateSchema builder instance
|
|
586
|
+
"""
|
|
587
|
+
return CreateSchema(schema_name, dialect=dialect or self.dialect)
|
|
588
|
+
|
|
589
|
+
def drop_table(self, table_name: str, dialect: DialectType = None) -> "DropTable":
|
|
590
|
+
"""Create a DROP TABLE builder.
|
|
591
|
+
|
|
592
|
+
Args:
|
|
593
|
+
table_name: Name of the table to drop
|
|
594
|
+
dialect: Optional SQL dialect
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
DropTable builder instance
|
|
598
|
+
"""
|
|
599
|
+
return DropTable(table_name, dialect=dialect or self.dialect)
|
|
600
|
+
|
|
601
|
+
def drop_view(self, view_name: str, dialect: DialectType = None) -> "DropView":
|
|
602
|
+
"""Create a DROP VIEW builder.
|
|
603
|
+
|
|
604
|
+
Args:
|
|
605
|
+
view_name: Name of the view to drop
|
|
606
|
+
dialect: Optional SQL dialect
|
|
607
|
+
|
|
608
|
+
Returns:
|
|
609
|
+
DropView builder instance
|
|
610
|
+
"""
|
|
611
|
+
return DropView(view_name, dialect=dialect or self.dialect)
|
|
612
|
+
|
|
613
|
+
def drop_index(self, index_name: str, dialect: DialectType = None) -> "DropIndex":
|
|
614
|
+
"""Create a DROP INDEX builder.
|
|
615
|
+
|
|
616
|
+
Args:
|
|
617
|
+
index_name: Name of the index to drop
|
|
618
|
+
dialect: Optional SQL dialect
|
|
619
|
+
|
|
620
|
+
Returns:
|
|
621
|
+
DropIndex builder instance
|
|
622
|
+
"""
|
|
623
|
+
return DropIndex(index_name, dialect=dialect or self.dialect)
|
|
624
|
+
|
|
625
|
+
def drop_schema(self, schema_name: str, dialect: DialectType = None) -> "DropSchema":
|
|
626
|
+
"""Create a DROP SCHEMA builder.
|
|
627
|
+
|
|
628
|
+
Args:
|
|
629
|
+
schema_name: Name of the schema to drop
|
|
630
|
+
dialect: Optional SQL dialect
|
|
631
|
+
|
|
632
|
+
Returns:
|
|
633
|
+
DropSchema builder instance
|
|
634
|
+
"""
|
|
635
|
+
return DropSchema(schema_name, dialect=dialect or self.dialect)
|
|
636
|
+
|
|
637
|
+
def alter_table(self, table_name: str, dialect: DialectType = None) -> "AlterTable":
|
|
638
|
+
"""Create an ALTER TABLE builder.
|
|
639
|
+
|
|
640
|
+
Args:
|
|
641
|
+
table_name: Name of the table to alter
|
|
642
|
+
dialect: Optional SQL dialect
|
|
643
|
+
|
|
644
|
+
Returns:
|
|
645
|
+
AlterTable builder instance
|
|
646
|
+
"""
|
|
647
|
+
return AlterTable(table_name, dialect=dialect or self.dialect)
|
|
648
|
+
|
|
649
|
+
def rename_table(self, old_name: str, dialect: DialectType = None) -> "RenameTable":
|
|
650
|
+
"""Create a RENAME TABLE builder.
|
|
651
|
+
|
|
652
|
+
Args:
|
|
653
|
+
old_name: Current name of the table
|
|
654
|
+
dialect: Optional SQL dialect
|
|
655
|
+
|
|
656
|
+
Returns:
|
|
657
|
+
RenameTable builder instance
|
|
658
|
+
"""
|
|
659
|
+
return RenameTable(old_name, dialect=dialect or self.dialect)
|
|
660
|
+
|
|
661
|
+
def comment_on(self, dialect: DialectType = None) -> "CommentOn":
|
|
662
|
+
"""Create a COMMENT ON builder.
|
|
663
|
+
|
|
664
|
+
Args:
|
|
665
|
+
dialect: Optional SQL dialect
|
|
666
|
+
|
|
667
|
+
Returns:
|
|
668
|
+
CommentOn builder instance
|
|
669
|
+
"""
|
|
670
|
+
return CommentOn(dialect=dialect or self.dialect)
|
|
671
|
+
|
|
672
|
+
def copy_from(
|
|
673
|
+
self,
|
|
674
|
+
table: str,
|
|
675
|
+
source: str,
|
|
676
|
+
*,
|
|
677
|
+
columns: "Sequence[str] | None" = None,
|
|
678
|
+
options: "Mapping[str, Any] | None" = None,
|
|
679
|
+
dialect: DialectType | None = None,
|
|
680
|
+
) -> SQL:
|
|
681
|
+
"""Build a COPY ... FROM statement."""
|
|
682
|
+
|
|
683
|
+
effective_dialect = dialect or self.dialect
|
|
684
|
+
return build_copy_from_statement(table, source, columns=columns, options=options, dialect=effective_dialect)
|
|
685
|
+
|
|
686
|
+
def copy_to(
|
|
687
|
+
self,
|
|
688
|
+
table: str,
|
|
689
|
+
target: str,
|
|
690
|
+
*,
|
|
691
|
+
columns: "Sequence[str] | None" = None,
|
|
692
|
+
options: "Mapping[str, Any] | None" = None,
|
|
693
|
+
dialect: DialectType | None = None,
|
|
694
|
+
) -> SQL:
|
|
695
|
+
"""Build a COPY ... TO statement."""
|
|
696
|
+
|
|
697
|
+
effective_dialect = dialect or self.dialect
|
|
698
|
+
return build_copy_to_statement(table, target, columns=columns, options=options, dialect=effective_dialect)
|
|
699
|
+
|
|
700
|
+
def copy(
|
|
701
|
+
self,
|
|
702
|
+
table: str,
|
|
703
|
+
*,
|
|
704
|
+
source: str | None = None,
|
|
705
|
+
target: str | None = None,
|
|
706
|
+
columns: "Sequence[str] | None" = None,
|
|
707
|
+
options: "Mapping[str, Any] | None" = None,
|
|
708
|
+
dialect: DialectType | None = None,
|
|
709
|
+
) -> SQL:
|
|
710
|
+
"""Build a COPY statement, inferring direction from provided arguments."""
|
|
711
|
+
|
|
712
|
+
if (source is None and target is None) or (source is not None and target is not None):
|
|
713
|
+
msg = "Provide either 'source' or 'target' (but not both) to sql.copy()."
|
|
714
|
+
raise SQLBuilderError(msg)
|
|
715
|
+
|
|
716
|
+
if source is not None:
|
|
717
|
+
return self.copy_from(table, source, columns=columns, options=options, dialect=dialect)
|
|
718
|
+
|
|
719
|
+
target_value = cast("str", target)
|
|
720
|
+
return self.copy_to(table, target_value, columns=columns, options=options, dialect=dialect)
|
|
721
|
+
|
|
722
|
+
@staticmethod
|
|
723
|
+
def _looks_like_sql(candidate: str, expected_type: str | None = None) -> bool:
|
|
724
|
+
"""Determine if a string looks like SQL.
|
|
725
|
+
|
|
726
|
+
Args:
|
|
727
|
+
candidate: String to check
|
|
728
|
+
expected_type: Expected SQL statement type (SELECT, INSERT, etc.)
|
|
729
|
+
|
|
730
|
+
Returns:
|
|
731
|
+
True if the string appears to be SQL
|
|
732
|
+
"""
|
|
733
|
+
if not candidate or len(candidate.strip()) < MIN_SQL_LIKE_STRING_LENGTH:
|
|
734
|
+
return False
|
|
735
|
+
|
|
736
|
+
candidate_upper = candidate.strip().upper()
|
|
737
|
+
|
|
738
|
+
if expected_type:
|
|
739
|
+
return candidate_upper.startswith(expected_type.upper())
|
|
740
|
+
|
|
741
|
+
if any(candidate_upper.startswith(starter) for starter in SQL_STARTERS):
|
|
742
|
+
return " " in candidate
|
|
743
|
+
|
|
744
|
+
return False
|
|
745
|
+
|
|
746
|
+
def _populate_insert_from_sql(
|
|
747
|
+
self, builder: "Insert", sql_string: str, parsed_expr: "exp.Expression | None" = None
|
|
748
|
+
) -> "Insert":
|
|
749
|
+
"""Parse SQL string and populate INSERT builder using SQLGlot directly."""
|
|
750
|
+
try:
|
|
751
|
+
if parsed_expr is None:
|
|
752
|
+
parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect)
|
|
753
|
+
|
|
754
|
+
if isinstance(parsed_expr, exp.Insert):
|
|
755
|
+
builder.set_expression(parsed_expr)
|
|
756
|
+
return builder
|
|
757
|
+
|
|
758
|
+
if isinstance(parsed_expr, exp.Select):
|
|
759
|
+
logger.debug(
|
|
760
|
+
"Detected SELECT statement for INSERT; builder requires explicit target table",
|
|
761
|
+
extra={"builder": "insert"},
|
|
762
|
+
)
|
|
763
|
+
return builder
|
|
764
|
+
|
|
765
|
+
logger.debug(
|
|
766
|
+
"Cannot create INSERT from parsed statement type",
|
|
767
|
+
extra={"builder": "insert", "parsed_type": type(parsed_expr).__name__},
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
except Exception:
|
|
771
|
+
logger.debug(
|
|
772
|
+
"Failed to parse INSERT SQL; falling back to traditional mode",
|
|
773
|
+
exc_info=True,
|
|
774
|
+
extra={"builder": "insert"},
|
|
775
|
+
)
|
|
776
|
+
return builder
|
|
777
|
+
|
|
778
|
+
def _populate_select_from_sql(
|
|
779
|
+
self, builder: "Select", sql_string: str, parsed_expr: "exp.Expression | None" = None
|
|
780
|
+
) -> "Select":
|
|
781
|
+
"""Parse SQL string and populate SELECT builder using SQLGlot directly."""
|
|
782
|
+
try:
|
|
783
|
+
if parsed_expr is None:
|
|
784
|
+
parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect)
|
|
785
|
+
|
|
786
|
+
if isinstance(parsed_expr, exp.With):
|
|
787
|
+
base_expression = parsed_expr.this
|
|
788
|
+
if isinstance(base_expression, exp.Select):
|
|
789
|
+
builder.set_expression(base_expression)
|
|
790
|
+
builder.load_ctes(list(parsed_expr.expressions))
|
|
791
|
+
return builder
|
|
792
|
+
if isinstance(parsed_expr, exp.Select):
|
|
793
|
+
builder.set_expression(parsed_expr)
|
|
794
|
+
return builder
|
|
795
|
+
|
|
796
|
+
logger.debug(
|
|
797
|
+
"Cannot create SELECT from parsed statement type",
|
|
798
|
+
extra={"builder": "select", "parsed_type": type(parsed_expr).__name__},
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
except Exception:
|
|
802
|
+
logger.debug(
|
|
803
|
+
"Failed to parse SELECT SQL; falling back to traditional mode",
|
|
804
|
+
exc_info=True,
|
|
805
|
+
extra={"builder": "select"},
|
|
806
|
+
)
|
|
807
|
+
return builder
|
|
808
|
+
|
|
809
|
+
def _populate_update_from_sql(
|
|
810
|
+
self, builder: "Update", sql_string: str, parsed_expr: "exp.Expression | None" = None
|
|
811
|
+
) -> "Update":
|
|
812
|
+
"""Parse SQL string and populate UPDATE builder using SQLGlot directly."""
|
|
813
|
+
try:
|
|
814
|
+
if parsed_expr is None:
|
|
815
|
+
parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect)
|
|
816
|
+
|
|
817
|
+
if isinstance(parsed_expr, exp.Update):
|
|
818
|
+
builder.set_expression(parsed_expr)
|
|
819
|
+
return builder
|
|
820
|
+
|
|
821
|
+
logger.debug(
|
|
822
|
+
"Cannot create UPDATE from parsed statement type",
|
|
823
|
+
extra={"builder": "update", "parsed_type": type(parsed_expr).__name__},
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
except Exception:
|
|
827
|
+
logger.debug(
|
|
828
|
+
"Failed to parse UPDATE SQL; falling back to traditional mode",
|
|
829
|
+
exc_info=True,
|
|
830
|
+
extra={"builder": "update"},
|
|
831
|
+
)
|
|
832
|
+
return builder
|
|
833
|
+
|
|
834
|
+
def _populate_delete_from_sql(
|
|
835
|
+
self, builder: "Delete", sql_string: str, parsed_expr: "exp.Expression | None" = None
|
|
836
|
+
) -> "Delete":
|
|
837
|
+
"""Parse SQL string and populate DELETE builder using SQLGlot directly."""
|
|
838
|
+
try:
|
|
839
|
+
if parsed_expr is None:
|
|
840
|
+
parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect)
|
|
841
|
+
|
|
842
|
+
if isinstance(parsed_expr, exp.Delete):
|
|
843
|
+
builder.set_expression(parsed_expr)
|
|
844
|
+
return builder
|
|
845
|
+
|
|
846
|
+
logger.debug(
|
|
847
|
+
"Cannot create DELETE from parsed statement type",
|
|
848
|
+
extra={"builder": "delete", "parsed_type": type(parsed_expr).__name__},
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
except Exception:
|
|
852
|
+
logger.debug(
|
|
853
|
+
"Failed to parse DELETE SQL; falling back to traditional mode",
|
|
854
|
+
exc_info=True,
|
|
855
|
+
extra={"builder": "delete"},
|
|
856
|
+
)
|
|
857
|
+
return builder
|
|
858
|
+
|
|
859
|
+
def _populate_merge_from_sql(
|
|
860
|
+
self, builder: "Merge", sql_string: str, parsed_expr: "exp.Expression | None" = None
|
|
861
|
+
) -> "Merge":
|
|
862
|
+
"""Parse SQL string and populate MERGE builder using SQLGlot directly."""
|
|
863
|
+
try:
|
|
864
|
+
if parsed_expr is None:
|
|
865
|
+
parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect)
|
|
866
|
+
|
|
867
|
+
if isinstance(parsed_expr, exp.Merge):
|
|
868
|
+
builder.set_expression(parsed_expr)
|
|
869
|
+
return builder
|
|
870
|
+
|
|
871
|
+
logger.debug(
|
|
872
|
+
"Cannot create MERGE from parsed statement type",
|
|
873
|
+
extra={"builder": "merge", "parsed_type": type(parsed_expr).__name__},
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
except Exception:
|
|
877
|
+
logger.debug(
|
|
878
|
+
"Failed to parse MERGE SQL; falling back to traditional mode", exc_info=True, extra={"builder": "merge"}
|
|
879
|
+
)
|
|
880
|
+
return builder
|
|
881
|
+
|
|
882
|
+
def column(self, name: str, table: str | None = None) -> Column:
|
|
883
|
+
"""Create a column reference.
|
|
884
|
+
|
|
885
|
+
Args:
|
|
886
|
+
name: Column name.
|
|
887
|
+
table: Optional table name.
|
|
888
|
+
|
|
889
|
+
Returns:
|
|
890
|
+
Column object that supports method chaining and operator overloading.
|
|
891
|
+
"""
|
|
892
|
+
return Column(name, table)
|
|
893
|
+
|
|
894
|
+
@property
|
|
895
|
+
def case_(self) -> "Case":
|
|
896
|
+
"""Create a CASE expression builder.
|
|
897
|
+
|
|
898
|
+
Returns:
|
|
899
|
+
Case builder instance for CASE expression building.
|
|
900
|
+
|
|
901
|
+
Example:
|
|
902
|
+
```python
|
|
903
|
+
case_expr = (
|
|
904
|
+
sql.case_
|
|
905
|
+
.when("x = 1", "one")
|
|
906
|
+
.when("x = 2", "two")
|
|
907
|
+
.else_("other")
|
|
908
|
+
.end()
|
|
909
|
+
)
|
|
910
|
+
aliased_case = (
|
|
911
|
+
sql.case_
|
|
912
|
+
.when("status = 'active'", 1)
|
|
913
|
+
.else_(0)
|
|
914
|
+
.as_("is_active")
|
|
915
|
+
)
|
|
916
|
+
```
|
|
917
|
+
"""
|
|
918
|
+
return Case()
|
|
919
|
+
|
|
920
|
+
@property
|
|
921
|
+
def row_number_(self) -> "WindowFunctionBuilder":
|
|
922
|
+
"""Create a ROW_NUMBER() window function builder."""
|
|
923
|
+
return WindowFunctionBuilder("row_number")
|
|
924
|
+
|
|
925
|
+
@property
|
|
926
|
+
def rank_(self) -> "WindowFunctionBuilder":
|
|
927
|
+
"""Create a RANK() window function builder."""
|
|
928
|
+
return WindowFunctionBuilder("rank")
|
|
929
|
+
|
|
930
|
+
@property
|
|
931
|
+
def dense_rank_(self) -> "WindowFunctionBuilder":
|
|
932
|
+
"""Create a DENSE_RANK() window function builder."""
|
|
933
|
+
return WindowFunctionBuilder("dense_rank")
|
|
934
|
+
|
|
935
|
+
@property
|
|
936
|
+
def lag_(self) -> "WindowFunctionBuilder":
|
|
937
|
+
"""Create a LAG() window function builder."""
|
|
938
|
+
return WindowFunctionBuilder("lag")
|
|
939
|
+
|
|
940
|
+
@property
|
|
941
|
+
def lead_(self) -> "WindowFunctionBuilder":
|
|
942
|
+
"""Create a LEAD() window function builder."""
|
|
943
|
+
return WindowFunctionBuilder("lead")
|
|
944
|
+
|
|
945
|
+
@property
|
|
946
|
+
def exists_(self) -> "SubqueryBuilder":
|
|
947
|
+
"""Create an EXISTS subquery builder."""
|
|
948
|
+
return SubqueryBuilder("exists")
|
|
949
|
+
|
|
950
|
+
@property
|
|
951
|
+
def in_(self) -> "SubqueryBuilder":
|
|
952
|
+
"""Create an IN subquery builder."""
|
|
953
|
+
return SubqueryBuilder("in")
|
|
954
|
+
|
|
955
|
+
@property
|
|
956
|
+
def any_(self) -> "SubqueryBuilder":
|
|
957
|
+
"""Create an ANY subquery builder."""
|
|
958
|
+
return SubqueryBuilder("any")
|
|
959
|
+
|
|
960
|
+
@property
|
|
961
|
+
def all_(self) -> "SubqueryBuilder":
|
|
962
|
+
"""Create an ALL subquery builder."""
|
|
963
|
+
return SubqueryBuilder("all")
|
|
964
|
+
|
|
965
|
+
@property
|
|
966
|
+
def inner_join_(self) -> "JoinBuilder":
|
|
967
|
+
"""Create an INNER JOIN builder."""
|
|
968
|
+
return create_join_builder("inner join")
|
|
969
|
+
|
|
970
|
+
@property
|
|
971
|
+
def left_join_(self) -> "JoinBuilder":
|
|
972
|
+
"""Create a LEFT JOIN builder."""
|
|
973
|
+
return create_join_builder("left join")
|
|
974
|
+
|
|
975
|
+
@property
|
|
976
|
+
def right_join_(self) -> "JoinBuilder":
|
|
977
|
+
"""Create a RIGHT JOIN builder."""
|
|
978
|
+
return create_join_builder("right join")
|
|
979
|
+
|
|
980
|
+
@property
|
|
981
|
+
def full_join_(self) -> "JoinBuilder":
|
|
982
|
+
"""Create a FULL OUTER JOIN builder."""
|
|
983
|
+
return create_join_builder("full join")
|
|
984
|
+
|
|
985
|
+
@property
|
|
986
|
+
def cross_join_(self) -> "JoinBuilder":
|
|
987
|
+
"""Create a CROSS JOIN builder."""
|
|
988
|
+
return create_join_builder("cross join")
|
|
989
|
+
|
|
990
|
+
@property
|
|
991
|
+
def lateral_join_(self) -> "JoinBuilder":
|
|
992
|
+
"""Create a LATERAL JOIN builder.
|
|
993
|
+
|
|
994
|
+
Returns:
|
|
995
|
+
JoinBuilder configured for LATERAL JOIN
|
|
996
|
+
|
|
997
|
+
Example:
|
|
998
|
+
```python
|
|
999
|
+
query = (
|
|
1000
|
+
sql
|
|
1001
|
+
.select("u.name", "arr.value")
|
|
1002
|
+
.from_("users u")
|
|
1003
|
+
.join(sql.lateral_join_("UNNEST(u.tags)").on("true"))
|
|
1004
|
+
)
|
|
1005
|
+
```
|
|
1006
|
+
"""
|
|
1007
|
+
return create_join_builder("lateral join", lateral=True)
|
|
1008
|
+
|
|
1009
|
+
@property
|
|
1010
|
+
def left_lateral_join_(self) -> "JoinBuilder":
|
|
1011
|
+
"""Create a LEFT LATERAL JOIN builder.
|
|
1012
|
+
|
|
1013
|
+
Returns:
|
|
1014
|
+
JoinBuilder configured for LEFT LATERAL JOIN
|
|
1015
|
+
"""
|
|
1016
|
+
return create_join_builder("left join", lateral=True)
|
|
1017
|
+
|
|
1018
|
+
@property
|
|
1019
|
+
def cross_lateral_join_(self) -> "JoinBuilder":
|
|
1020
|
+
"""Create a CROSS LATERAL JOIN builder.
|
|
1021
|
+
|
|
1022
|
+
Returns:
|
|
1023
|
+
JoinBuilder configured for CROSS LATERAL JOIN
|
|
1024
|
+
"""
|
|
1025
|
+
return create_join_builder("cross join", lateral=True)
|
|
1026
|
+
|
|
1027
|
+
def __getattr__(self, name: str) -> "Column":
|
|
1028
|
+
"""Dynamically create column references.
|
|
1029
|
+
|
|
1030
|
+
Args:
|
|
1031
|
+
name: Column name.
|
|
1032
|
+
|
|
1033
|
+
Returns:
|
|
1034
|
+
Column object for the given name.
|
|
1035
|
+
|
|
1036
|
+
Note:
|
|
1037
|
+
Special SQL constructs like case_, row_number_, etc. are
|
|
1038
|
+
handled as properties for type safety.
|
|
1039
|
+
"""
|
|
1040
|
+
return Column(name)
|
|
1041
|
+
|
|
1042
|
+
@staticmethod
|
|
1043
|
+
def raw(sql_fragment: str, **parameters: Any) -> "exp.Expression | SQL":
|
|
1044
|
+
"""Create a raw SQL expression from a string fragment with optional parameters.
|
|
1045
|
+
|
|
1046
|
+
Args:
|
|
1047
|
+
sql_fragment: Raw SQL string to parse into an expression.
|
|
1048
|
+
**parameters: Named parameters for parameter binding.
|
|
1049
|
+
|
|
1050
|
+
Returns:
|
|
1051
|
+
SQLGlot expression from the parsed SQL fragment (if no parameters).
|
|
1052
|
+
SQL statement object (if parameters provided).
|
|
1053
|
+
|
|
1054
|
+
Raises:
|
|
1055
|
+
SQLBuilderError: If the SQL fragment cannot be parsed.
|
|
1056
|
+
|
|
1057
|
+
Example:
|
|
1058
|
+
```python
|
|
1059
|
+
expr = sql.raw("COALESCE(name, 'Unknown')")
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
stmt = sql.raw(
|
|
1063
|
+
"LOWER(name) LIKE LOWER(:pattern)", pattern=f"%{query}%"
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
expr = sql.raw(
|
|
1068
|
+
"price BETWEEN :min_price AND :max_price",
|
|
1069
|
+
min_price=100,
|
|
1070
|
+
max_price=500,
|
|
1071
|
+
)
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
query = sql.select(
|
|
1075
|
+
"name",
|
|
1076
|
+
sql.raw(
|
|
1077
|
+
"ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC)"
|
|
1078
|
+
),
|
|
1079
|
+
).from_("employees")
|
|
1080
|
+
```
|
|
1081
|
+
"""
|
|
1082
|
+
if not parameters:
|
|
1083
|
+
try:
|
|
1084
|
+
parsed: exp.Expression = exp.maybe_parse(sql_fragment)
|
|
1085
|
+
except Exception as e:
|
|
1086
|
+
msg = f"Failed to parse raw SQL fragment '{sql_fragment}': {e}"
|
|
1087
|
+
raise SQLBuilderError(msg) from e
|
|
1088
|
+
return parsed
|
|
1089
|
+
|
|
1090
|
+
return SQL(sql_fragment, parameters)
|
|
1091
|
+
|
|
1092
|
+
def count(
|
|
1093
|
+
self, column: Union[str, exp.Expression, "ExpressionWrapper", "Case", "Column"] = "*", distinct: bool = False
|
|
1094
|
+
) -> AggregateExpression:
|
|
1095
|
+
"""Create a COUNT expression.
|
|
1096
|
+
|
|
1097
|
+
Args:
|
|
1098
|
+
column: Column to count (default "*").
|
|
1099
|
+
distinct: Whether to use COUNT DISTINCT.
|
|
1100
|
+
|
|
1101
|
+
Returns:
|
|
1102
|
+
COUNT expression.
|
|
1103
|
+
"""
|
|
1104
|
+
if isinstance(column, str) and column == "*":
|
|
1105
|
+
expr = exp.Count(this=exp.Star(), distinct=distinct)
|
|
1106
|
+
else:
|
|
1107
|
+
col_expr = extract_expression(column)
|
|
1108
|
+
expr = exp.Count(this=col_expr, distinct=distinct)
|
|
1109
|
+
return AggregateExpression(expr)
|
|
1110
|
+
|
|
1111
|
+
def count_distinct(self, column: Union[str, exp.Expression, "ExpressionWrapper", "Case"]) -> AggregateExpression:
|
|
1112
|
+
"""Create a COUNT(DISTINCT column) expression.
|
|
1113
|
+
|
|
1114
|
+
Args:
|
|
1115
|
+
column: Column to count distinct values.
|
|
1116
|
+
|
|
1117
|
+
Returns:
|
|
1118
|
+
COUNT DISTINCT expression.
|
|
1119
|
+
"""
|
|
1120
|
+
return self.count(column, distinct=True)
|
|
1121
|
+
|
|
1122
|
+
@staticmethod
|
|
1123
|
+
def sum(
|
|
1124
|
+
column: Union[str, exp.Expression, "ExpressionWrapper", "Case"], distinct: bool = False
|
|
1125
|
+
) -> AggregateExpression:
|
|
1126
|
+
"""Create a SUM expression.
|
|
1127
|
+
|
|
1128
|
+
Args:
|
|
1129
|
+
column: Column to sum.
|
|
1130
|
+
distinct: Whether to use SUM DISTINCT.
|
|
1131
|
+
|
|
1132
|
+
Returns:
|
|
1133
|
+
SUM expression.
|
|
1134
|
+
"""
|
|
1135
|
+
col_expr = extract_expression(column)
|
|
1136
|
+
return AggregateExpression(exp.Sum(this=col_expr, distinct=distinct))
|
|
1137
|
+
|
|
1138
|
+
@staticmethod
|
|
1139
|
+
def avg(column: Union[str, exp.Expression, "ExpressionWrapper", "Case"]) -> AggregateExpression:
|
|
1140
|
+
"""Create an AVG expression.
|
|
1141
|
+
|
|
1142
|
+
Args:
|
|
1143
|
+
column: Column to average.
|
|
1144
|
+
|
|
1145
|
+
Returns:
|
|
1146
|
+
AVG expression.
|
|
1147
|
+
"""
|
|
1148
|
+
col_expr = extract_expression(column)
|
|
1149
|
+
return AggregateExpression(exp.Avg(this=col_expr))
|
|
1150
|
+
|
|
1151
|
+
@staticmethod
|
|
1152
|
+
def max(column: Union[str, exp.Expression, "ExpressionWrapper", "Case"]) -> AggregateExpression:
|
|
1153
|
+
"""Create a MAX expression.
|
|
1154
|
+
|
|
1155
|
+
Args:
|
|
1156
|
+
column: Column to find maximum.
|
|
1157
|
+
|
|
1158
|
+
Returns:
|
|
1159
|
+
MAX expression.
|
|
1160
|
+
"""
|
|
1161
|
+
col_expr = extract_expression(column)
|
|
1162
|
+
return AggregateExpression(exp.Max(this=col_expr))
|
|
1163
|
+
|
|
1164
|
+
@staticmethod
|
|
1165
|
+
def min(column: Union[str, exp.Expression, "ExpressionWrapper", "Case"]) -> AggregateExpression:
|
|
1166
|
+
"""Create a MIN expression.
|
|
1167
|
+
|
|
1168
|
+
Args:
|
|
1169
|
+
column: Column to find minimum.
|
|
1170
|
+
|
|
1171
|
+
Returns:
|
|
1172
|
+
MIN expression.
|
|
1173
|
+
"""
|
|
1174
|
+
col_expr = extract_expression(column)
|
|
1175
|
+
return AggregateExpression(exp.Min(this=col_expr))
|
|
1176
|
+
|
|
1177
|
+
@staticmethod
|
|
1178
|
+
def rollup(*columns: str | exp.Expression) -> FunctionExpression:
|
|
1179
|
+
"""Create a ROLLUP expression for GROUP BY clauses.
|
|
1180
|
+
|
|
1181
|
+
Args:
|
|
1182
|
+
*columns: Columns to include in the rollup.
|
|
1183
|
+
|
|
1184
|
+
Returns:
|
|
1185
|
+
ROLLUP expression.
|
|
1186
|
+
|
|
1187
|
+
Example:
|
|
1188
|
+
```python
|
|
1189
|
+
query = (
|
|
1190
|
+
sql
|
|
1191
|
+
.select("product", "region", sql.sum("sales"))
|
|
1192
|
+
.from_("sales_data")
|
|
1193
|
+
.group_by(sql.rollup("product", "region"))
|
|
1194
|
+
)
|
|
1195
|
+
```
|
|
1196
|
+
"""
|
|
1197
|
+
column_exprs = [exp.column(col) if isinstance(col, str) else col for col in columns]
|
|
1198
|
+
return FunctionExpression(exp.Rollup(expressions=column_exprs))
|
|
1199
|
+
|
|
1200
|
+
@staticmethod
|
|
1201
|
+
def cube(*columns: str | exp.Expression) -> FunctionExpression:
|
|
1202
|
+
"""Create a CUBE expression for GROUP BY clauses.
|
|
1203
|
+
|
|
1204
|
+
Args:
|
|
1205
|
+
*columns: Columns to include in the cube.
|
|
1206
|
+
|
|
1207
|
+
Returns:
|
|
1208
|
+
CUBE expression.
|
|
1209
|
+
|
|
1210
|
+
Example:
|
|
1211
|
+
```python
|
|
1212
|
+
query = (
|
|
1213
|
+
sql
|
|
1214
|
+
.select("product", "region", sql.sum("sales"))
|
|
1215
|
+
.from_("sales_data")
|
|
1216
|
+
.group_by(sql.cube("product", "region"))
|
|
1217
|
+
)
|
|
1218
|
+
```
|
|
1219
|
+
"""
|
|
1220
|
+
column_exprs = [exp.column(col) if isinstance(col, str) else col for col in columns]
|
|
1221
|
+
return FunctionExpression(exp.Cube(expressions=column_exprs))
|
|
1222
|
+
|
|
1223
|
+
@staticmethod
|
|
1224
|
+
def grouping_sets(*column_sets: tuple[str, ...] | list[str]) -> FunctionExpression:
|
|
1225
|
+
"""Create a GROUPING SETS expression for GROUP BY clauses.
|
|
1226
|
+
|
|
1227
|
+
Args:
|
|
1228
|
+
*column_sets: Sets of columns to group by.
|
|
1229
|
+
|
|
1230
|
+
Returns:
|
|
1231
|
+
GROUPING SETS expression.
|
|
1232
|
+
|
|
1233
|
+
Example:
|
|
1234
|
+
```python
|
|
1235
|
+
query = (
|
|
1236
|
+
sql
|
|
1237
|
+
.select("product", "region", sql.sum("sales"))
|
|
1238
|
+
.from_("sales_data")
|
|
1239
|
+
.group_by(
|
|
1240
|
+
sql.grouping_sets(("product",), ("region",), ())
|
|
1241
|
+
)
|
|
1242
|
+
)
|
|
1243
|
+
```
|
|
1244
|
+
"""
|
|
1245
|
+
set_expressions = []
|
|
1246
|
+
for column_set in column_sets:
|
|
1247
|
+
if isinstance(column_set, (tuple, list)):
|
|
1248
|
+
if len(column_set) == 0:
|
|
1249
|
+
set_expressions.append(exp.Tuple(expressions=[]))
|
|
1250
|
+
else:
|
|
1251
|
+
columns = [exp.column(col) for col in column_set]
|
|
1252
|
+
set_expressions.append(exp.Tuple(expressions=columns))
|
|
1253
|
+
else:
|
|
1254
|
+
set_expressions.append(exp.column(column_set))
|
|
1255
|
+
|
|
1256
|
+
return FunctionExpression(exp.GroupingSets(expressions=set_expressions))
|
|
1257
|
+
|
|
1258
|
+
@staticmethod
|
|
1259
|
+
def any(values: list[Any] | exp.Expression | str) -> FunctionExpression:
|
|
1260
|
+
"""Create an ANY expression for use with comparison operators.
|
|
1261
|
+
|
|
1262
|
+
Args:
|
|
1263
|
+
values: Values, expression, or subquery for the ANY clause.
|
|
1264
|
+
|
|
1265
|
+
Returns:
|
|
1266
|
+
ANY expression.
|
|
1267
|
+
|
|
1268
|
+
Example:
|
|
1269
|
+
```python
|
|
1270
|
+
subquery = sql.select("user_id").from_("active_users")
|
|
1271
|
+
query = (
|
|
1272
|
+
sql
|
|
1273
|
+
.select("*")
|
|
1274
|
+
.from_("users")
|
|
1275
|
+
.where(sql.id.eq(sql.any(subquery)))
|
|
1276
|
+
)
|
|
1277
|
+
```
|
|
1278
|
+
"""
|
|
1279
|
+
if isinstance(values, list):
|
|
1280
|
+
literals = [SQLFactory.to_literal(v) for v in values]
|
|
1281
|
+
return FunctionExpression(exp.Any(this=exp.Array(expressions=literals)))
|
|
1282
|
+
if isinstance(values, str):
|
|
1283
|
+
parsed: exp.Expression = exp.maybe_parse(values)
|
|
1284
|
+
return FunctionExpression(exp.Any(this=parsed))
|
|
1285
|
+
return FunctionExpression(exp.Any(this=values))
|
|
1286
|
+
|
|
1287
|
+
@staticmethod
|
|
1288
|
+
def not_any_(values: list[Any] | exp.Expression | str) -> FunctionExpression:
|
|
1289
|
+
"""Create a NOT ANY expression for use with comparison operators.
|
|
1290
|
+
|
|
1291
|
+
Args:
|
|
1292
|
+
values: Values, expression, or subquery for the NOT ANY clause.
|
|
1293
|
+
|
|
1294
|
+
Returns:
|
|
1295
|
+
NOT ANY expression.
|
|
1296
|
+
|
|
1297
|
+
Example:
|
|
1298
|
+
```python
|
|
1299
|
+
subquery = sql.select("user_id").from_("blocked_users")
|
|
1300
|
+
query = (
|
|
1301
|
+
sql
|
|
1302
|
+
.select("*")
|
|
1303
|
+
.from_("users")
|
|
1304
|
+
.where(sql.id.neq(sql.not_any(subquery)))
|
|
1305
|
+
)
|
|
1306
|
+
```
|
|
1307
|
+
"""
|
|
1308
|
+
return SQLFactory.any(values)
|
|
1309
|
+
|
|
1310
|
+
@staticmethod
|
|
1311
|
+
def concat(*expressions: str | exp.Expression) -> StringExpression:
|
|
1312
|
+
"""Create a CONCAT expression.
|
|
1313
|
+
|
|
1314
|
+
Args:
|
|
1315
|
+
*expressions: Expressions to concatenate.
|
|
1316
|
+
|
|
1317
|
+
Returns:
|
|
1318
|
+
CONCAT expression.
|
|
1319
|
+
"""
|
|
1320
|
+
exprs = [exp.column(expr) if isinstance(expr, str) else expr for expr in expressions]
|
|
1321
|
+
return StringExpression(exp.Concat(expressions=exprs))
|
|
1322
|
+
|
|
1323
|
+
@staticmethod
|
|
1324
|
+
def upper(column: str | exp.Expression) -> StringExpression:
|
|
1325
|
+
"""Create an UPPER expression.
|
|
1326
|
+
|
|
1327
|
+
Args:
|
|
1328
|
+
column: Column to convert to uppercase.
|
|
1329
|
+
|
|
1330
|
+
Returns:
|
|
1331
|
+
UPPER expression.
|
|
1332
|
+
"""
|
|
1333
|
+
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1334
|
+
return StringExpression(exp.Upper(this=col_expr))
|
|
1335
|
+
|
|
1336
|
+
@staticmethod
|
|
1337
|
+
def lower(column: str | exp.Expression) -> StringExpression:
|
|
1338
|
+
"""Create a LOWER expression.
|
|
1339
|
+
|
|
1340
|
+
Args:
|
|
1341
|
+
column: Column to convert to lowercase.
|
|
1342
|
+
|
|
1343
|
+
Returns:
|
|
1344
|
+
LOWER expression.
|
|
1345
|
+
"""
|
|
1346
|
+
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1347
|
+
return StringExpression(exp.Lower(this=col_expr))
|
|
1348
|
+
|
|
1349
|
+
@staticmethod
|
|
1350
|
+
def length(column: str | exp.Expression) -> StringExpression:
|
|
1351
|
+
"""Create a LENGTH expression.
|
|
1352
|
+
|
|
1353
|
+
Args:
|
|
1354
|
+
column: Column to get length of.
|
|
1355
|
+
|
|
1356
|
+
Returns:
|
|
1357
|
+
LENGTH expression.
|
|
1358
|
+
"""
|
|
1359
|
+
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1360
|
+
return StringExpression(exp.Length(this=col_expr))
|
|
1361
|
+
|
|
1362
|
+
@staticmethod
|
|
1363
|
+
def round(column: str | exp.Expression, decimals: int = 0) -> MathExpression:
|
|
1364
|
+
"""Create a ROUND expression.
|
|
1365
|
+
|
|
1366
|
+
Args:
|
|
1367
|
+
column: Column to round.
|
|
1368
|
+
decimals: Number of decimal places.
|
|
1369
|
+
|
|
1370
|
+
Returns:
|
|
1371
|
+
ROUND expression.
|
|
1372
|
+
"""
|
|
1373
|
+
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1374
|
+
if decimals == 0:
|
|
1375
|
+
return MathExpression(exp.Round(this=col_expr))
|
|
1376
|
+
return MathExpression(exp.Round(this=col_expr, expression=exp.Literal.number(decimals)))
|
|
1377
|
+
|
|
1378
|
+
@staticmethod
|
|
1379
|
+
def to_literal(value: Any) -> FunctionExpression:
|
|
1380
|
+
"""Convert a Python value to a SQLGlot literal expression.
|
|
1381
|
+
|
|
1382
|
+
Uses SQLGlot's built-in exp.convert() function for literal creation.
|
|
1383
|
+
Handles all Python primitive types:
|
|
1384
|
+
- None -> exp.Null (renders as NULL)
|
|
1385
|
+
- bool -> exp.Boolean (renders as TRUE/FALSE or 1/0 based on dialect)
|
|
1386
|
+
- int/float -> exp.Literal with is_number=True
|
|
1387
|
+
- str -> exp.Literal with is_string=True
|
|
1388
|
+
- exp.Expression -> returned as-is (passthrough)
|
|
1389
|
+
|
|
1390
|
+
Args:
|
|
1391
|
+
value: Python value or SQLGlot expression to convert.
|
|
1392
|
+
|
|
1393
|
+
Returns:
|
|
1394
|
+
SQLGlot expression representing the literal value.
|
|
1395
|
+
"""
|
|
1396
|
+
if isinstance(value, exp.Expression):
|
|
1397
|
+
return FunctionExpression(value)
|
|
1398
|
+
return FunctionExpression(exp.convert(value))
|
|
1399
|
+
|
|
1400
|
+
@staticmethod
|
|
1401
|
+
def decode(column: str | exp.Expression, *args: str | exp.Expression | Any) -> FunctionExpression:
|
|
1402
|
+
"""Create a DECODE expression (Oracle-style conditional logic).
|
|
1403
|
+
|
|
1404
|
+
DECODE compares column to each search value and returns the corresponding result.
|
|
1405
|
+
If no match is found, returns the default value (if provided) or NULL.
|
|
1406
|
+
|
|
1407
|
+
Args:
|
|
1408
|
+
column: Column to compare.
|
|
1409
|
+
*args: Alternating search values and results, with optional default at the end.
|
|
1410
|
+
Format: search1, result1, search2, result2, ..., [default]
|
|
1411
|
+
|
|
1412
|
+
Raises:
|
|
1413
|
+
ValueError: If fewer than two search/result pairs are provided.
|
|
1414
|
+
|
|
1415
|
+
Returns:
|
|
1416
|
+
CASE expression equivalent to DECODE.
|
|
1417
|
+
|
|
1418
|
+
Example:
|
|
1419
|
+
```python
|
|
1420
|
+
sql.decode(
|
|
1421
|
+
"status", "A", "Active", "I", "Inactive", "Unknown"
|
|
1422
|
+
)
|
|
1423
|
+
```
|
|
1424
|
+
"""
|
|
1425
|
+
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1426
|
+
|
|
1427
|
+
if len(args) < MIN_DECODE_ARGS:
|
|
1428
|
+
msg = "DECODE requires at least one search/result pair"
|
|
1429
|
+
raise ValueError(msg)
|
|
1430
|
+
|
|
1431
|
+
conditions = []
|
|
1432
|
+
default = None
|
|
1433
|
+
|
|
1434
|
+
for i in range(0, len(args) - 1, 2):
|
|
1435
|
+
if i + 1 >= len(args):
|
|
1436
|
+
default = to_expression(args[i])
|
|
1437
|
+
break
|
|
1438
|
+
|
|
1439
|
+
search_val = args[i]
|
|
1440
|
+
result_val = args[i + 1]
|
|
1441
|
+
|
|
1442
|
+
search_expr = to_expression(search_val)
|
|
1443
|
+
result_expr = to_expression(result_val)
|
|
1444
|
+
|
|
1445
|
+
condition = exp.EQ(this=col_expr, expression=search_expr)
|
|
1446
|
+
conditions.append(exp.If(this=condition, true=result_expr))
|
|
1447
|
+
|
|
1448
|
+
return FunctionExpression(exp.Case(ifs=conditions, default=default))
|
|
1449
|
+
|
|
1450
|
+
@staticmethod
|
|
1451
|
+
def cast(column: str | exp.Expression, data_type: str) -> ConversionExpression:
|
|
1452
|
+
"""Create a CAST expression for type conversion.
|
|
1453
|
+
|
|
1454
|
+
Args:
|
|
1455
|
+
column: Column or expression to cast.
|
|
1456
|
+
data_type: Target data type (e.g., 'INT', 'VARCHAR(100)', 'DECIMAL(10,2)').
|
|
1457
|
+
|
|
1458
|
+
Returns:
|
|
1459
|
+
CAST expression.
|
|
1460
|
+
"""
|
|
1461
|
+
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1462
|
+
return ConversionExpression(exp.Cast(this=col_expr, to=exp.DataType.build(data_type)))
|
|
1463
|
+
|
|
1464
|
+
@staticmethod
|
|
1465
|
+
def coalesce(*expressions: str | exp.Expression) -> ConversionExpression:
|
|
1466
|
+
"""Create a COALESCE expression.
|
|
1467
|
+
|
|
1468
|
+
Args:
|
|
1469
|
+
*expressions: Expressions to coalesce.
|
|
1470
|
+
|
|
1471
|
+
Returns:
|
|
1472
|
+
COALESCE expression.
|
|
1473
|
+
"""
|
|
1474
|
+
exprs = [exp.column(expr) if isinstance(expr, str) else expr for expr in expressions]
|
|
1475
|
+
return ConversionExpression(exp.Coalesce(expressions=exprs))
|
|
1476
|
+
|
|
1477
|
+
@staticmethod
|
|
1478
|
+
def nvl(column: str | exp.Expression, substitute_value: str | exp.Expression | Any) -> ConversionExpression:
|
|
1479
|
+
"""Create an NVL (Oracle-style) expression using COALESCE.
|
|
1480
|
+
|
|
1481
|
+
Args:
|
|
1482
|
+
column: Column to check for NULL.
|
|
1483
|
+
substitute_value: Value to use if column is NULL.
|
|
1484
|
+
|
|
1485
|
+
Returns:
|
|
1486
|
+
COALESCE expression equivalent to NVL.
|
|
1487
|
+
"""
|
|
1488
|
+
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1489
|
+
sub_expr = to_expression(substitute_value)
|
|
1490
|
+
return ConversionExpression(exp.Coalesce(expressions=[col_expr, sub_expr]))
|
|
1491
|
+
|
|
1492
|
+
@staticmethod
|
|
1493
|
+
def nvl2(
|
|
1494
|
+
column: str | exp.Expression,
|
|
1495
|
+
value_if_not_null: str | exp.Expression | Any,
|
|
1496
|
+
value_if_null: str | exp.Expression | Any,
|
|
1497
|
+
) -> ConversionExpression:
|
|
1498
|
+
"""Create an NVL2 (Oracle-style) expression using CASE.
|
|
1499
|
+
|
|
1500
|
+
NVL2 returns value_if_not_null if column is not NULL,
|
|
1501
|
+
otherwise returns value_if_null.
|
|
1502
|
+
|
|
1503
|
+
Args:
|
|
1504
|
+
column: Column to check for NULL.
|
|
1505
|
+
value_if_not_null: Value to use if column is NOT NULL.
|
|
1506
|
+
value_if_null: Value to use if column is NULL.
|
|
1507
|
+
|
|
1508
|
+
Returns:
|
|
1509
|
+
CASE expression equivalent to NVL2.
|
|
1510
|
+
|
|
1511
|
+
Example:
|
|
1512
|
+
```python
|
|
1513
|
+
sql.nvl2("salary", "Has Salary", "No Salary")
|
|
1514
|
+
```
|
|
1515
|
+
"""
|
|
1516
|
+
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1517
|
+
not_null_expr = to_expression(value_if_not_null)
|
|
1518
|
+
null_expr = to_expression(value_if_null)
|
|
1519
|
+
|
|
1520
|
+
is_null = exp.Is(this=col_expr, expression=exp.Null())
|
|
1521
|
+
condition = exp.Not(this=is_null)
|
|
1522
|
+
when_clause = exp.If(this=condition, true=not_null_expr)
|
|
1523
|
+
|
|
1524
|
+
return ConversionExpression(exp.Case(ifs=[when_clause], default=null_expr))
|
|
1525
|
+
|
|
1526
|
+
@staticmethod
|
|
1527
|
+
def bulk_insert(table_name: str, column_count: int, placeholder_style: str = "?") -> FunctionExpression:
|
|
1528
|
+
"""Create bulk INSERT expression for executemany operations.
|
|
1529
|
+
|
|
1530
|
+
For bulk loading operations like CSV ingestion where
|
|
1531
|
+
an INSERT expression with placeholders for executemany() is needed.
|
|
1532
|
+
|
|
1533
|
+
Args:
|
|
1534
|
+
table_name: Name of the table to insert into
|
|
1535
|
+
column_count: Number of columns (for placeholder generation)
|
|
1536
|
+
placeholder_style: Placeholder style ("?" for SQLite/PostgreSQL, "%s" for MySQL, ":1" for Oracle)
|
|
1537
|
+
|
|
1538
|
+
Returns:
|
|
1539
|
+
INSERT expression with placeholders for bulk operations
|
|
1540
|
+
|
|
1541
|
+
Example:
|
|
1542
|
+
```python
|
|
1543
|
+
from sqlspec import sql
|
|
1544
|
+
|
|
1545
|
+
|
|
1546
|
+
insert_expr = sql.bulk_insert("my_table", 3)
|
|
1547
|
+
|
|
1548
|
+
|
|
1549
|
+
insert_expr = sql.bulk_insert(
|
|
1550
|
+
"my_table", 3, placeholder_style="%s"
|
|
1551
|
+
)
|
|
1552
|
+
|
|
1553
|
+
|
|
1554
|
+
insert_expr = sql.bulk_insert(
|
|
1555
|
+
"my_table", 3, placeholder_style=":1"
|
|
1556
|
+
)
|
|
1557
|
+
```
|
|
1558
|
+
"""
|
|
1559
|
+
return FunctionExpression(
|
|
1560
|
+
exp.Insert(
|
|
1561
|
+
this=exp.Table(this=exp.to_identifier(table_name)),
|
|
1562
|
+
expression=exp.Values(
|
|
1563
|
+
expressions=[
|
|
1564
|
+
exp.Tuple(expressions=[exp.Placeholder(this=placeholder_style) for _ in range(column_count)])
|
|
1565
|
+
]
|
|
1566
|
+
),
|
|
1567
|
+
)
|
|
1568
|
+
)
|
|
1569
|
+
|
|
1570
|
+
def truncate(self, table_name: str) -> "Truncate":
|
|
1571
|
+
"""Create a TRUNCATE TABLE builder.
|
|
1572
|
+
|
|
1573
|
+
Args:
|
|
1574
|
+
table_name: Name of the table to truncate
|
|
1575
|
+
|
|
1576
|
+
Returns:
|
|
1577
|
+
TruncateTable builder instance
|
|
1578
|
+
|
|
1579
|
+
Example:
|
|
1580
|
+
```python
|
|
1581
|
+
from sqlspec import sql
|
|
1582
|
+
|
|
1583
|
+
|
|
1584
|
+
truncate_sql = sql.truncate_table("my_table").build().sql
|
|
1585
|
+
|
|
1586
|
+
|
|
1587
|
+
truncate_sql = (
|
|
1588
|
+
sql
|
|
1589
|
+
.truncate_table("my_table")
|
|
1590
|
+
.cascade()
|
|
1591
|
+
.restart_identity()
|
|
1592
|
+
.build()
|
|
1593
|
+
.sql
|
|
1594
|
+
)
|
|
1595
|
+
```
|
|
1596
|
+
"""
|
|
1597
|
+
return Truncate(table_name, dialect=self.dialect)
|
|
1598
|
+
|
|
1599
|
+
@staticmethod
|
|
1600
|
+
def case() -> "Case":
|
|
1601
|
+
"""Create a CASE expression builder.
|
|
1602
|
+
|
|
1603
|
+
Returns:
|
|
1604
|
+
CaseExpressionBuilder for building CASE expressions.
|
|
1605
|
+
"""
|
|
1606
|
+
return Case()
|
|
1607
|
+
|
|
1608
|
+
def row_number(
|
|
1609
|
+
self,
|
|
1610
|
+
partition_by: str | list[str] | exp.Expression | None = None,
|
|
1611
|
+
order_by: str | list[str] | exp.Expression | None = None,
|
|
1612
|
+
) -> FunctionExpression:
|
|
1613
|
+
"""Create a ROW_NUMBER() window function.
|
|
1614
|
+
|
|
1615
|
+
Args:
|
|
1616
|
+
partition_by: Columns to partition by.
|
|
1617
|
+
order_by: Columns to order by.
|
|
1618
|
+
|
|
1619
|
+
Returns:
|
|
1620
|
+
ROW_NUMBER window function expression.
|
|
1621
|
+
"""
|
|
1622
|
+
return self._create_window_function("ROW_NUMBER", [], partition_by, order_by)
|
|
1623
|
+
|
|
1624
|
+
def rank(
|
|
1625
|
+
self,
|
|
1626
|
+
partition_by: str | list[str] | exp.Expression | None = None,
|
|
1627
|
+
order_by: str | list[str] | exp.Expression | None = None,
|
|
1628
|
+
) -> FunctionExpression:
|
|
1629
|
+
"""Create a RANK() window function.
|
|
1630
|
+
|
|
1631
|
+
Args:
|
|
1632
|
+
partition_by: Columns to partition by.
|
|
1633
|
+
order_by: Columns to order by.
|
|
1634
|
+
|
|
1635
|
+
Returns:
|
|
1636
|
+
RANK window function expression.
|
|
1637
|
+
"""
|
|
1638
|
+
return self._create_window_function("RANK", [], partition_by, order_by)
|
|
1639
|
+
|
|
1640
|
+
def dense_rank(
|
|
1641
|
+
self,
|
|
1642
|
+
partition_by: str | list[str] | exp.Expression | None = None,
|
|
1643
|
+
order_by: str | list[str] | exp.Expression | None = None,
|
|
1644
|
+
) -> FunctionExpression:
|
|
1645
|
+
"""Create a DENSE_RANK() window function.
|
|
1646
|
+
|
|
1647
|
+
Args:
|
|
1648
|
+
partition_by: Columns to partition by.
|
|
1649
|
+
order_by: Columns to order by.
|
|
1650
|
+
|
|
1651
|
+
Returns:
|
|
1652
|
+
DENSE_RANK window function expression.
|
|
1653
|
+
"""
|
|
1654
|
+
return self._create_window_function("DENSE_RANK", [], partition_by, order_by)
|
|
1655
|
+
|
|
1656
|
+
@staticmethod
|
|
1657
|
+
def _create_window_function(
|
|
1658
|
+
func_name: str,
|
|
1659
|
+
func_args: list[exp.Expression],
|
|
1660
|
+
partition_by: str | list[str] | exp.Expression | None = None,
|
|
1661
|
+
order_by: str | list[str] | exp.Expression | None = None,
|
|
1662
|
+
) -> FunctionExpression:
|
|
1663
|
+
"""Helper to create window function expressions.
|
|
1664
|
+
|
|
1665
|
+
Args:
|
|
1666
|
+
func_name: Name of the window function.
|
|
1667
|
+
func_args: Arguments to the function.
|
|
1668
|
+
partition_by: Columns to partition by.
|
|
1669
|
+
order_by: Columns to order by.
|
|
1670
|
+
|
|
1671
|
+
Returns:
|
|
1672
|
+
Window function expression.
|
|
1673
|
+
"""
|
|
1674
|
+
func_expr = exp.Anonymous(this=func_name, expressions=func_args)
|
|
1675
|
+
|
|
1676
|
+
over_args: dict[str, Any] = {}
|
|
1677
|
+
|
|
1678
|
+
if partition_by:
|
|
1679
|
+
if isinstance(partition_by, str):
|
|
1680
|
+
over_args["partition_by"] = [exp.column(partition_by)]
|
|
1681
|
+
elif isinstance(partition_by, list):
|
|
1682
|
+
over_args["partition_by"] = [exp.column(col) for col in partition_by]
|
|
1683
|
+
elif isinstance(partition_by, exp.Expression):
|
|
1684
|
+
over_args["partition_by"] = [partition_by]
|
|
1685
|
+
|
|
1686
|
+
if order_by:
|
|
1687
|
+
if isinstance(order_by, str):
|
|
1688
|
+
over_args["order"] = exp.Order(expressions=[exp.column(order_by).asc()])
|
|
1689
|
+
elif isinstance(order_by, list):
|
|
1690
|
+
over_args["order"] = exp.Order(expressions=[exp.column(col).asc() for col in order_by])
|
|
1691
|
+
elif isinstance(order_by, exp.Expression):
|
|
1692
|
+
over_args["order"] = exp.Order(expressions=[order_by])
|
|
1693
|
+
|
|
1694
|
+
return FunctionExpression(exp.Window(this=func_expr, **over_args))
|
|
1695
|
+
|
|
1696
|
+
|
|
1697
|
+
sql = SQLFactory()
|