sqlspec 0.21.1__tar.gz → 0.23.0__tar.gz
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.
Potentially problematic release.
This version of sqlspec might be problematic. Click here for more details.
- {sqlspec-0.21.1 → sqlspec-0.23.0}/.pre-commit-config.yaml +1 -1
- {sqlspec-0.21.1 → sqlspec-0.23.0}/PKG-INFO +1 -1
- {sqlspec-0.21.1 → sqlspec-0.23.0}/pyproject.toml +3 -1
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/_sql.py +36 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/base.py +4 -4
- sqlspec-0.23.0/sqlspec/builder/mixins/_join_operations.py +388 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/loader.py +65 -68
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/protocols.py +3 -5
- sqlspec-0.23.0/sqlspec/storage/__init__.py +13 -0
- sqlspec-0.23.0/sqlspec/storage/backends/__init__.py +1 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/storage/backends/fsspec.py +87 -147
- sqlspec-0.23.0/sqlspec/storage/backends/local.py +310 -0
- sqlspec-0.23.0/sqlspec/storage/backends/obstore.py +474 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/storage/registry.py +101 -70
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/sync_tools.py +8 -5
- sqlspec-0.23.0/tests/integration/test_loader/test_file_system_loading.py +773 -0
- sqlspec-0.23.0/tests/integration/test_storage/__init__.py +1 -0
- sqlspec-0.23.0/tests/integration/test_storage/test_storage_integration.py +866 -0
- sqlspec-0.23.0/tests/unit/test_builder/test_lateral_joins.py +271 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_loader/test_loading_patterns.py +148 -133
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_loader/test_sql_file_loader.py +207 -176
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_sql_factory.py +1 -1
- sqlspec-0.23.0/tests/unit/test_storage/__init__.py +1 -0
- sqlspec-0.23.0/tests/unit/test_storage/test_fsspec_backend.py +499 -0
- sqlspec-0.23.0/tests/unit/test_storage/test_local_store.py +484 -0
- sqlspec-0.23.0/tests/unit/test_storage/test_obstore_backend.py +488 -0
- sqlspec-0.23.0/tests/unit/test_storage/test_storage_registry.py +238 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_sync_tools.py +9 -6
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_type_guards.py +1 -1
- {sqlspec-0.21.1 → sqlspec-0.23.0}/uv.lock +333 -234
- sqlspec-0.21.1/sqlspec/builder/mixins/_join_operations.py +0 -268
- sqlspec-0.21.1/sqlspec/storage/__init__.py +0 -23
- sqlspec-0.21.1/sqlspec/storage/backends/obstore.py +0 -456
- sqlspec-0.21.1/sqlspec/storage/capabilities.py +0 -102
- sqlspec-0.21.1/tests/integration/test_loader/test_file_system_loading.py +0 -693
- sqlspec-0.21.1/tools/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/.gitignore +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/CONTRIBUTING.rst +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/LICENSE +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/Makefile +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/NOTICE +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/README.md +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/__main__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/__metadata__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/_serialization.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/_typing.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/adbc/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/adbc/_types.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/adbc/config.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/adbc/driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/aiosqlite/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/aiosqlite/_types.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/aiosqlite/config.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/aiosqlite/driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/aiosqlite/pool.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/asyncmy/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/asyncmy/_types.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/asyncmy/config.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/asyncmy/driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/asyncpg/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/asyncpg/_types.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/asyncpg/config.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/asyncpg/driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/bigquery/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/bigquery/_types.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/bigquery/config.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/bigquery/driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/duckdb/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/duckdb/_types.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/duckdb/config.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/duckdb/driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/duckdb/pool.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/oracledb/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/oracledb/_types.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/oracledb/config.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/oracledb/driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/oracledb/migrations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/psqlpy/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/psqlpy/_types.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/psqlpy/config.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/psqlpy/driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/psycopg/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/psycopg/_types.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/psycopg/config.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/psycopg/driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/sqlite/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/sqlite/_types.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/sqlite/config.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/sqlite/driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/sqlite/pool.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_base.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_column.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_ddl.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_delete.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_expression_wrappers.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_insert.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_merge.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_parsing_utils.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_select.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_update.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_cte_and_set_ops.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_delete_operations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_insert_operations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_merge_operations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_order_limit_operations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_pivot_operations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_select_operations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_update_operations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_where_clause.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/cli.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/config.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/cache.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/compiler.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/filters.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/hashing.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/parameters.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/result.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/splitter.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/statement.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/driver/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/driver/_async.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/driver/_common.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/driver/_sync.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/driver/mixins/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/driver/mixins/_result_tools.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/driver/mixins/_sql_translator.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/exceptions.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/aiosql/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/aiosql/adapter.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/litestar/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/litestar/_utils.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/litestar/cli.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/litestar/config.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/litestar/handlers.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/litestar/plugin.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/litestar/providers.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/migrations/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/migrations/base.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/migrations/commands.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/migrations/loaders.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/migrations/runner.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/migrations/tracker.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/migrations/utils.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/py.typed +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/storage/backends/base.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/typing.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/correlation.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/data_transformation.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/deprecation.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/fixtures.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/logging.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/module_loader.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/serializers.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/singleton.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/text.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/type_guards.py +0 -0
- {sqlspec-0.21.1/sqlspec/storage/backends → sqlspec-0.23.0/tests}/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/conftest.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/asset_maintenance.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/ddls-mysql-collection.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/ddls-postgres-collection.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/example_usage.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/init.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-config.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-data_types.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-database_details.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-engines.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-hostname.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-plugins.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-process_list.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-resource-groups.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-schema_objects.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-table_details.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-users.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/init.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/oracle.ddl.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-applications.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-aws_extension_dependency.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-aws_oracle_exists.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-bg_writer_stats.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-calculated_metrics.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-data_types.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-database_details.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-extensions.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-index_details.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-pglogical-details.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-privileges.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-replication_slots.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-replication_stats.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-schema_details.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-schema_objects.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-settings.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-source_details.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-table_details.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/extended-collection-all-databases.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/init.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/readiness-check.sql +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/sql_utils.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/conftest.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/conftest.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/test_adbc_arrow_features.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/test_adbc_backends.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/test_adbc_connection.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/test_adbc_driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/test_adbc_edge_cases.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/test_adbc_results.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/test_migrations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/test_parameter_styles.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_aiosqlite/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_aiosqlite/conftest.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_aiosqlite/test_connection.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_aiosqlite/test_driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_aiosqlite/test_migrations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_aiosqlite/test_parameter_styles.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_aiosqlite/test_pooling.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncmy/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncmy/conftest.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncmy/test_asyncmy_features.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncmy/test_config.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncmy/test_driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncmy/test_migrations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncmy/test_parameter_styles.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncpg/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncpg/conftest.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncpg/test_connection.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncpg/test_driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncpg/test_execute_many.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncpg/test_migrations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncpg/test_parameter_styles.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_bigquery/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_bigquery/conftest.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_bigquery/test_bigquery_features.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_bigquery/test_config.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_bigquery/test_connection.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_bigquery/test_driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/test_connection.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/test_driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/test_execute_many.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/test_migrations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/test_mixed_parameter_styles.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/test_parameter_styles.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/test_pooling.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/utils.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/conftest.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/test_connection.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/test_driver_async.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/test_driver_sync.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/test_execute_many.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/test_migrations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/test_oracle_features.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/test_parameter_styles.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psqlpy/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psqlpy/conftest.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psqlpy/test_connection.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psqlpy/test_driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psqlpy/test_migrations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psqlpy/test_parameter_styles.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psqlpy/test_psqlpy_features.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psycopg/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psycopg/conftest.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psycopg/test_async_copy.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psycopg/test_connection.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psycopg/test_driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psycopg/test_execute_many.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psycopg/test_migrations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psycopg/test_parameter_styles.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_sqlite/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_sqlite/conftest.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_sqlite/test_driver.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_sqlite/test_migrations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_sqlite/test_parameter_styles.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_sqlite/test_pooling.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_sqlite/test_query_mixin.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_loader/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_migrations/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/conftest.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_adapters/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_adapters/conftest.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_adapters/test_adapter_implementations.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_adapters/test_async_adapters.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_adapters/test_sync_adapters.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_base/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_base/test_sql_integration.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_base/test_sqlspec_class.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_builder/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_builder/test_insert_builder.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_builder/test_parameter_naming.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_builder_parameter_naming.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_core/test_cache.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_core/test_compiler.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_core/test_filters.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_core/test_hashing.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_core/test_parameters.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_core/test_result.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_core/test_statement.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_extensions/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_extensions/test_litestar/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_extensions/test_litestar/test_config.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_loader/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_loader/test_cache_integration.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_loader/test_fixtures_directory_loading.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_migrations/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_migrations/test_migration.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_migrations/test_migration_commands.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_migrations/test_migration_execution.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_migrations/test_migration_runner.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_parsing_utils.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_correlation.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_data_transformation.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_deprecation.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_fixtures.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_logging.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_module_loader.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_serializers.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_singleton.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_text.py +0 -0
- {sqlspec-0.21.1/tests → sqlspec-0.23.0/tools}/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tools/build_docs.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tools/local-infra.sh +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tools/pypi_readme.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tools/sphinx_ext/__init__.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tools/sphinx_ext/changelog.py +0 -0
- {sqlspec-0.21.1 → sqlspec-0.23.0}/tools/sphinx_ext/missing_references.py +0 -0
|
@@ -13,7 +13,7 @@ maintainers = [{ name = "Litestar Developers", email = "hello@litestar.dev" }]
|
|
|
13
13
|
name = "sqlspec"
|
|
14
14
|
readme = "README.md"
|
|
15
15
|
requires-python = ">=3.9, <4.0"
|
|
16
|
-
version = "0.
|
|
16
|
+
version = "0.23.0"
|
|
17
17
|
|
|
18
18
|
[project.urls]
|
|
19
19
|
Discord = "https://discord.gg/litestar"
|
|
@@ -83,6 +83,7 @@ doc = [
|
|
|
83
83
|
]
|
|
84
84
|
extras = [
|
|
85
85
|
"adbc_driver_manager",
|
|
86
|
+
"fsspec[s3]",
|
|
86
87
|
"pgvector",
|
|
87
88
|
"pyarrow",
|
|
88
89
|
"polars",
|
|
@@ -341,6 +342,7 @@ module = [
|
|
|
341
342
|
"sqlglot.*",
|
|
342
343
|
"pgvector",
|
|
343
344
|
"pgvector.*",
|
|
345
|
+
"minio",
|
|
344
346
|
]
|
|
345
347
|
|
|
346
348
|
[[tool.mypy.overrides]]
|
|
@@ -628,6 +628,42 @@ class SQLFactory:
|
|
|
628
628
|
"""Create a CROSS JOIN builder."""
|
|
629
629
|
return JoinBuilder("cross join")
|
|
630
630
|
|
|
631
|
+
@property
|
|
632
|
+
def lateral_join_(self) -> "JoinBuilder":
|
|
633
|
+
"""Create a LATERAL JOIN builder.
|
|
634
|
+
|
|
635
|
+
Returns:
|
|
636
|
+
JoinBuilder configured for LATERAL JOIN
|
|
637
|
+
|
|
638
|
+
Example:
|
|
639
|
+
```python
|
|
640
|
+
query = (
|
|
641
|
+
sql.select("u.name", "arr.value")
|
|
642
|
+
.from_("users u")
|
|
643
|
+
.join(sql.lateral_join_("UNNEST(u.tags)").on("true"))
|
|
644
|
+
)
|
|
645
|
+
```
|
|
646
|
+
"""
|
|
647
|
+
return JoinBuilder("lateral join", lateral=True)
|
|
648
|
+
|
|
649
|
+
@property
|
|
650
|
+
def left_lateral_join_(self) -> "JoinBuilder":
|
|
651
|
+
"""Create a LEFT LATERAL JOIN builder.
|
|
652
|
+
|
|
653
|
+
Returns:
|
|
654
|
+
JoinBuilder configured for LEFT LATERAL JOIN
|
|
655
|
+
"""
|
|
656
|
+
return JoinBuilder("left join", lateral=True)
|
|
657
|
+
|
|
658
|
+
@property
|
|
659
|
+
def cross_lateral_join_(self) -> "JoinBuilder":
|
|
660
|
+
"""Create a CROSS LATERAL JOIN builder.
|
|
661
|
+
|
|
662
|
+
Returns:
|
|
663
|
+
JoinBuilder configured for CROSS LATERAL JOIN
|
|
664
|
+
"""
|
|
665
|
+
return JoinBuilder("cross join", lateral=True)
|
|
666
|
+
|
|
631
667
|
def __getattr__(self, name: str) -> "Column":
|
|
632
668
|
"""Dynamically create column references.
|
|
633
669
|
|
|
@@ -64,7 +64,7 @@ class SQLSpec:
|
|
|
64
64
|
config.close_pool()
|
|
65
65
|
cleaned_count += 1
|
|
66
66
|
except Exception as e:
|
|
67
|
-
logger.
|
|
67
|
+
logger.debug("Failed to clean up sync pool for config %s: %s", config_type.__name__, e)
|
|
68
68
|
|
|
69
69
|
if cleaned_count > 0:
|
|
70
70
|
logger.debug("Sync pool cleanup completed. Cleaned %d pools.", cleaned_count)
|
|
@@ -87,14 +87,14 @@ class SQLSpec:
|
|
|
87
87
|
else:
|
|
88
88
|
sync_configs.append((config_type, config))
|
|
89
89
|
except Exception as e:
|
|
90
|
-
logger.
|
|
90
|
+
logger.debug("Failed to prepare cleanup for config %s: %s", config_type.__name__, e)
|
|
91
91
|
|
|
92
92
|
if cleanup_tasks:
|
|
93
93
|
try:
|
|
94
94
|
await asyncio.gather(*cleanup_tasks, return_exceptions=True)
|
|
95
95
|
logger.debug("Async pool cleanup completed. Cleaned %d pools.", len(cleanup_tasks))
|
|
96
96
|
except Exception as e:
|
|
97
|
-
logger.
|
|
97
|
+
logger.debug("Failed to complete async pool cleanup: %s", e)
|
|
98
98
|
|
|
99
99
|
for _config_type, config in sync_configs:
|
|
100
100
|
config.close_pool()
|
|
@@ -129,7 +129,7 @@ class SQLSpec:
|
|
|
129
129
|
"""
|
|
130
130
|
config_type = type(config)
|
|
131
131
|
if config_type in self._configs:
|
|
132
|
-
logger.
|
|
132
|
+
logger.debug("Configuration for %s already exists. Overwriting.", config_type.__name__)
|
|
133
133
|
self._configs[config_type] = config
|
|
134
134
|
return config_type
|
|
135
135
|
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"""JOIN operation mixins.
|
|
2
|
+
|
|
3
|
+
Provides mixins for JOIN operations in SELECT statements.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
7
|
+
|
|
8
|
+
from mypy_extensions import trait
|
|
9
|
+
from sqlglot import exp
|
|
10
|
+
from typing_extensions import Self
|
|
11
|
+
|
|
12
|
+
from sqlspec.builder._parsing_utils import parse_table_expression
|
|
13
|
+
from sqlspec.exceptions import SQLBuilderError
|
|
14
|
+
from sqlspec.utils.type_guards import has_query_builder_parameters
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from sqlspec.core.statement import SQL
|
|
18
|
+
from sqlspec.protocols import SQLBuilderProtocol
|
|
19
|
+
|
|
20
|
+
__all__ = ("JoinBuilder", "JoinClauseMixin")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@trait
|
|
24
|
+
class JoinClauseMixin:
|
|
25
|
+
"""Mixin providing JOIN clause methods for SELECT builders."""
|
|
26
|
+
|
|
27
|
+
__slots__ = ()
|
|
28
|
+
|
|
29
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
30
|
+
_expression: Optional[exp.Expression]
|
|
31
|
+
|
|
32
|
+
def join(
|
|
33
|
+
self,
|
|
34
|
+
table: Union[str, exp.Expression, Any],
|
|
35
|
+
on: Optional[Union[str, exp.Expression, "SQL"]] = None,
|
|
36
|
+
alias: Optional[str] = None,
|
|
37
|
+
join_type: str = "INNER",
|
|
38
|
+
lateral: bool = False,
|
|
39
|
+
) -> Self:
|
|
40
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
41
|
+
self._validate_join_context(builder)
|
|
42
|
+
|
|
43
|
+
# Handle Join expressions directly (from JoinBuilder.on() calls)
|
|
44
|
+
if isinstance(table, exp.Join):
|
|
45
|
+
if builder._expression is not None and isinstance(builder._expression, exp.Select):
|
|
46
|
+
builder._expression = builder._expression.join(table, copy=False)
|
|
47
|
+
return cast("Self", builder)
|
|
48
|
+
|
|
49
|
+
table_expr = self._parse_table_expression(table, alias, builder)
|
|
50
|
+
on_expr = self._parse_on_condition(on, builder)
|
|
51
|
+
join_expr = self._create_join_expression(table_expr, on_expr, join_type)
|
|
52
|
+
|
|
53
|
+
if lateral:
|
|
54
|
+
self._apply_lateral_modifier(join_expr)
|
|
55
|
+
|
|
56
|
+
if builder._expression is not None and isinstance(builder._expression, exp.Select):
|
|
57
|
+
builder._expression = builder._expression.join(join_expr, copy=False)
|
|
58
|
+
return cast("Self", builder)
|
|
59
|
+
|
|
60
|
+
def _validate_join_context(self, builder: "SQLBuilderProtocol") -> None:
|
|
61
|
+
"""Validate that the join can be applied to the current expression."""
|
|
62
|
+
if builder._expression is None:
|
|
63
|
+
builder._expression = exp.Select()
|
|
64
|
+
if not isinstance(builder._expression, exp.Select):
|
|
65
|
+
msg = "JOIN clause is only supported for SELECT statements."
|
|
66
|
+
raise SQLBuilderError(msg)
|
|
67
|
+
|
|
68
|
+
def _parse_table_expression(
|
|
69
|
+
self, table: Union[str, exp.Expression, Any], alias: Optional[str], builder: "SQLBuilderProtocol"
|
|
70
|
+
) -> exp.Expression:
|
|
71
|
+
"""Parse table parameter into a SQLGlot expression."""
|
|
72
|
+
if isinstance(table, str):
|
|
73
|
+
return parse_table_expression(table, alias)
|
|
74
|
+
if has_query_builder_parameters(table):
|
|
75
|
+
return self._handle_query_builder_table(table, alias, builder)
|
|
76
|
+
if isinstance(table, exp.Expression):
|
|
77
|
+
return table
|
|
78
|
+
return cast("exp.Expression", table)
|
|
79
|
+
|
|
80
|
+
def _handle_query_builder_table(
|
|
81
|
+
self, table: Any, alias: Optional[str], builder: "SQLBuilderProtocol"
|
|
82
|
+
) -> exp.Expression:
|
|
83
|
+
"""Handle table parameters that are query builders."""
|
|
84
|
+
if hasattr(table, "_expression") and getattr(table, "_expression", None) is not None:
|
|
85
|
+
table_expr_value = getattr(table, "_expression", None)
|
|
86
|
+
if table_expr_value is not None:
|
|
87
|
+
subquery_exp = exp.paren(table_expr_value)
|
|
88
|
+
else:
|
|
89
|
+
subquery_exp = exp.paren(exp.Anonymous(this=""))
|
|
90
|
+
return exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
91
|
+
subquery = table.build()
|
|
92
|
+
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
93
|
+
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(builder, "dialect", None)))
|
|
94
|
+
return exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
95
|
+
|
|
96
|
+
def _parse_on_condition(
|
|
97
|
+
self, on: Optional[Union[str, exp.Expression, "SQL"]], builder: "SQLBuilderProtocol"
|
|
98
|
+
) -> Optional[exp.Expression]:
|
|
99
|
+
"""Parse ON condition into a SQLGlot expression."""
|
|
100
|
+
if on is None:
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
if isinstance(on, str):
|
|
104
|
+
return exp.condition(on)
|
|
105
|
+
if hasattr(on, "expression") and hasattr(on, "sql"):
|
|
106
|
+
return self._handle_sql_object_condition(on, builder)
|
|
107
|
+
if isinstance(on, exp.Expression):
|
|
108
|
+
return on
|
|
109
|
+
# Last resort - convert to string and parse
|
|
110
|
+
return exp.condition(str(on))
|
|
111
|
+
|
|
112
|
+
def _handle_sql_object_condition(self, on: Any, builder: "SQLBuilderProtocol") -> exp.Expression:
|
|
113
|
+
"""Handle SQL object conditions with parameter binding."""
|
|
114
|
+
expression = getattr(on, "expression", None)
|
|
115
|
+
if expression is not None and isinstance(expression, exp.Expression):
|
|
116
|
+
# Merge parameters from SQL object into builder
|
|
117
|
+
if hasattr(on, "parameters") and hasattr(builder, "add_parameter"):
|
|
118
|
+
sql_parameters = getattr(on, "parameters", {})
|
|
119
|
+
for param_name, param_value in sql_parameters.items():
|
|
120
|
+
builder.add_parameter(param_value, name=param_name)
|
|
121
|
+
return cast("exp.Expression", expression)
|
|
122
|
+
# If expression is None, fall back to parsing the raw SQL
|
|
123
|
+
sql_text = getattr(on, "sql", "")
|
|
124
|
+
# Merge parameters even when parsing raw SQL
|
|
125
|
+
if hasattr(on, "parameters") and hasattr(builder, "add_parameter"):
|
|
126
|
+
sql_parameters = getattr(on, "parameters", {})
|
|
127
|
+
for param_name, param_value in sql_parameters.items():
|
|
128
|
+
builder.add_parameter(param_value, name=param_name)
|
|
129
|
+
parsed_expr = exp.maybe_parse(sql_text)
|
|
130
|
+
return parsed_expr if parsed_expr is not None else exp.condition(str(sql_text))
|
|
131
|
+
|
|
132
|
+
def _create_join_expression(
|
|
133
|
+
self, table_expr: exp.Expression, on_expr: Optional[exp.Expression], join_type: str
|
|
134
|
+
) -> exp.Join:
|
|
135
|
+
"""Create the appropriate JOIN expression based on join type."""
|
|
136
|
+
join_type_upper = join_type.upper()
|
|
137
|
+
if join_type_upper == "INNER":
|
|
138
|
+
return exp.Join(this=table_expr, on=on_expr)
|
|
139
|
+
if join_type_upper == "LEFT":
|
|
140
|
+
return exp.Join(this=table_expr, on=on_expr, side="LEFT")
|
|
141
|
+
if join_type_upper == "RIGHT":
|
|
142
|
+
return exp.Join(this=table_expr, on=on_expr, side="RIGHT")
|
|
143
|
+
if join_type_upper == "FULL":
|
|
144
|
+
return exp.Join(this=table_expr, on=on_expr, side="FULL", kind="OUTER")
|
|
145
|
+
if join_type_upper == "CROSS":
|
|
146
|
+
return exp.Join(this=table_expr, kind="CROSS")
|
|
147
|
+
msg = f"Unsupported join type: {join_type}"
|
|
148
|
+
raise SQLBuilderError(msg)
|
|
149
|
+
|
|
150
|
+
def _apply_lateral_modifier(self, join_expr: exp.Join) -> None:
|
|
151
|
+
"""Apply LATERAL modifier to the join expression."""
|
|
152
|
+
current_kind = join_expr.args.get("kind")
|
|
153
|
+
current_side = join_expr.args.get("side")
|
|
154
|
+
|
|
155
|
+
if current_kind == "CROSS":
|
|
156
|
+
join_expr.set("kind", "CROSS LATERAL")
|
|
157
|
+
elif current_kind == "OUTER" and current_side == "FULL":
|
|
158
|
+
join_expr.set("side", "FULL") # Keep side
|
|
159
|
+
join_expr.set("kind", "OUTER LATERAL")
|
|
160
|
+
elif current_side:
|
|
161
|
+
join_expr.set("kind", f"{current_side} LATERAL")
|
|
162
|
+
join_expr.set("side", None) # Clear side to avoid duplication
|
|
163
|
+
else:
|
|
164
|
+
join_expr.set("kind", "LATERAL")
|
|
165
|
+
|
|
166
|
+
def inner_join(
|
|
167
|
+
self, table: Union[str, exp.Expression, Any], on: Union[str, exp.Expression, "SQL"], alias: Optional[str] = None
|
|
168
|
+
) -> Self:
|
|
169
|
+
return self.join(table, on, alias, "INNER")
|
|
170
|
+
|
|
171
|
+
def left_join(
|
|
172
|
+
self, table: Union[str, exp.Expression, Any], on: Union[str, exp.Expression, "SQL"], alias: Optional[str] = None
|
|
173
|
+
) -> Self:
|
|
174
|
+
return self.join(table, on, alias, "LEFT")
|
|
175
|
+
|
|
176
|
+
def right_join(
|
|
177
|
+
self, table: Union[str, exp.Expression, Any], on: Union[str, exp.Expression, "SQL"], alias: Optional[str] = None
|
|
178
|
+
) -> Self:
|
|
179
|
+
return self.join(table, on, alias, "RIGHT")
|
|
180
|
+
|
|
181
|
+
def full_join(
|
|
182
|
+
self, table: Union[str, exp.Expression, Any], on: Union[str, exp.Expression, "SQL"], alias: Optional[str] = None
|
|
183
|
+
) -> Self:
|
|
184
|
+
return self.join(table, on, alias, "FULL")
|
|
185
|
+
|
|
186
|
+
def cross_join(self, table: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
|
|
187
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
188
|
+
if builder._expression is None:
|
|
189
|
+
builder._expression = exp.Select()
|
|
190
|
+
if not isinstance(builder._expression, exp.Select):
|
|
191
|
+
msg = "Cannot add cross join to a non-SELECT expression."
|
|
192
|
+
raise SQLBuilderError(msg)
|
|
193
|
+
table_expr: exp.Expression
|
|
194
|
+
if isinstance(table, str):
|
|
195
|
+
table_expr = parse_table_expression(table, alias)
|
|
196
|
+
elif has_query_builder_parameters(table):
|
|
197
|
+
if hasattr(table, "_expression") and getattr(table, "_expression", None) is not None:
|
|
198
|
+
table_expr_value = getattr(table, "_expression", None)
|
|
199
|
+
if table_expr_value is not None:
|
|
200
|
+
subquery_exp = exp.paren(table_expr_value)
|
|
201
|
+
else:
|
|
202
|
+
subquery_exp = exp.paren(exp.Anonymous(this=""))
|
|
203
|
+
table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
204
|
+
else:
|
|
205
|
+
subquery = table.build()
|
|
206
|
+
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
207
|
+
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(builder, "dialect", None)))
|
|
208
|
+
table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
209
|
+
else:
|
|
210
|
+
table_expr = table
|
|
211
|
+
join_expr = exp.Join(this=table_expr, kind="CROSS")
|
|
212
|
+
builder._expression = builder._expression.join(join_expr, copy=False)
|
|
213
|
+
return cast("Self", builder)
|
|
214
|
+
|
|
215
|
+
def lateral_join(
|
|
216
|
+
self,
|
|
217
|
+
table: Union[str, exp.Expression, Any],
|
|
218
|
+
on: Optional[Union[str, exp.Expression, "SQL"]] = None,
|
|
219
|
+
alias: Optional[str] = None,
|
|
220
|
+
) -> Self:
|
|
221
|
+
"""Create a LATERAL JOIN.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
table: Table, subquery, or table function to join
|
|
225
|
+
on: Optional join condition (for LATERAL JOINs with ON clause)
|
|
226
|
+
alias: Optional alias for the joined table/subquery
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Self for method chaining
|
|
230
|
+
|
|
231
|
+
Example:
|
|
232
|
+
```python
|
|
233
|
+
query = (
|
|
234
|
+
sql.select("u.name", "arr.value")
|
|
235
|
+
.from_("users u")
|
|
236
|
+
.lateral_join("UNNEST(u.tags)", alias="arr")
|
|
237
|
+
)
|
|
238
|
+
```
|
|
239
|
+
"""
|
|
240
|
+
return self.join(table, on=on, alias=alias, join_type="INNER", lateral=True)
|
|
241
|
+
|
|
242
|
+
def left_lateral_join(
|
|
243
|
+
self,
|
|
244
|
+
table: Union[str, exp.Expression, Any],
|
|
245
|
+
on: Optional[Union[str, exp.Expression, "SQL"]] = None,
|
|
246
|
+
alias: Optional[str] = None,
|
|
247
|
+
) -> Self:
|
|
248
|
+
"""Create a LEFT LATERAL JOIN.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
table: Table, subquery, or table function to join
|
|
252
|
+
on: Optional join condition
|
|
253
|
+
alias: Optional alias for the joined table/subquery
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Self for method chaining
|
|
257
|
+
"""
|
|
258
|
+
return self.join(table, on=on, alias=alias, join_type="LEFT", lateral=True)
|
|
259
|
+
|
|
260
|
+
def cross_lateral_join(self, table: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
|
|
261
|
+
"""Create a CROSS LATERAL JOIN (no ON condition).
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
table: Table, subquery, or table function to join
|
|
265
|
+
alias: Optional alias for the joined table/subquery
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Self for method chaining
|
|
269
|
+
"""
|
|
270
|
+
return self.join(table, on=None, alias=alias, join_type="CROSS", lateral=True)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@trait
|
|
274
|
+
class JoinBuilder:
|
|
275
|
+
"""Builder for JOIN operations with fluent syntax.
|
|
276
|
+
|
|
277
|
+
Example:
|
|
278
|
+
```python
|
|
279
|
+
from sqlspec import sql
|
|
280
|
+
|
|
281
|
+
# sql.left_join_("posts").on("users.id = posts.user_id")
|
|
282
|
+
join_clause = sql.left_join_("posts").on(
|
|
283
|
+
"users.id = posts.user_id"
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Or with query builder
|
|
287
|
+
query = (
|
|
288
|
+
sql.select("users.name", "posts.title")
|
|
289
|
+
.from_("users")
|
|
290
|
+
.join(
|
|
291
|
+
sql.left_join_("posts").on(
|
|
292
|
+
"users.id = posts.user_id"
|
|
293
|
+
)
|
|
294
|
+
)
|
|
295
|
+
)
|
|
296
|
+
```
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
def __init__(self, join_type: str, lateral: bool = False) -> None:
|
|
300
|
+
"""Initialize the join builder.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
join_type: Type of join (inner, left, right, full, cross, lateral)
|
|
304
|
+
lateral: Whether this is a LATERAL join
|
|
305
|
+
"""
|
|
306
|
+
self._join_type = join_type.upper()
|
|
307
|
+
self._lateral = lateral
|
|
308
|
+
self._table: Optional[Union[str, exp.Expression]] = None
|
|
309
|
+
self._condition: Optional[exp.Expression] = None
|
|
310
|
+
self._alias: Optional[str] = None
|
|
311
|
+
|
|
312
|
+
def __call__(self, table: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
|
|
313
|
+
"""Set the table to join.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
table: Table name or expression to join
|
|
317
|
+
alias: Optional alias for the table
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
Self for method chaining
|
|
321
|
+
"""
|
|
322
|
+
self._table = table
|
|
323
|
+
self._alias = alias
|
|
324
|
+
return self
|
|
325
|
+
|
|
326
|
+
def on(self, condition: Union[str, exp.Expression]) -> exp.Expression:
|
|
327
|
+
"""Set the join condition and build the JOIN expression.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
condition: JOIN condition (e.g., "users.id = posts.user_id")
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
Complete JOIN expression
|
|
334
|
+
"""
|
|
335
|
+
if not self._table:
|
|
336
|
+
msg = "Table must be set before calling .on()"
|
|
337
|
+
raise SQLBuilderError(msg)
|
|
338
|
+
|
|
339
|
+
# Parse the condition
|
|
340
|
+
condition_expr: exp.Expression
|
|
341
|
+
if isinstance(condition, str):
|
|
342
|
+
parsed: Optional[exp.Expression] = exp.maybe_parse(condition)
|
|
343
|
+
condition_expr = parsed or exp.condition(condition)
|
|
344
|
+
else:
|
|
345
|
+
condition_expr = condition
|
|
346
|
+
|
|
347
|
+
# Build table expression
|
|
348
|
+
table_expr: exp.Expression
|
|
349
|
+
if isinstance(self._table, str):
|
|
350
|
+
table_expr = exp.to_table(self._table)
|
|
351
|
+
if self._alias:
|
|
352
|
+
table_expr = exp.alias_(table_expr, self._alias)
|
|
353
|
+
else:
|
|
354
|
+
table_expr = self._table
|
|
355
|
+
if self._alias:
|
|
356
|
+
table_expr = exp.alias_(table_expr, self._alias)
|
|
357
|
+
|
|
358
|
+
# Create the appropriate join type using same pattern as existing JoinClauseMixin
|
|
359
|
+
if self._join_type in {"INNER JOIN", "INNER", "LATERAL JOIN"}:
|
|
360
|
+
join_expr = exp.Join(this=table_expr, on=condition_expr)
|
|
361
|
+
elif self._join_type in {"LEFT JOIN", "LEFT"}:
|
|
362
|
+
join_expr = exp.Join(this=table_expr, on=condition_expr, side="LEFT")
|
|
363
|
+
elif self._join_type in {"RIGHT JOIN", "RIGHT"}:
|
|
364
|
+
join_expr = exp.Join(this=table_expr, on=condition_expr, side="RIGHT")
|
|
365
|
+
elif self._join_type in {"FULL JOIN", "FULL"}:
|
|
366
|
+
join_expr = exp.Join(this=table_expr, on=condition_expr, side="FULL", kind="OUTER")
|
|
367
|
+
elif self._join_type in {"CROSS JOIN", "CROSS"}:
|
|
368
|
+
# CROSS JOIN doesn't use ON condition
|
|
369
|
+
join_expr = exp.Join(this=table_expr, kind="CROSS")
|
|
370
|
+
else:
|
|
371
|
+
join_expr = exp.Join(this=table_expr, on=condition_expr)
|
|
372
|
+
|
|
373
|
+
if self._lateral or self._join_type == "LATERAL JOIN":
|
|
374
|
+
current_kind = join_expr.args.get("kind")
|
|
375
|
+
current_side = join_expr.args.get("side")
|
|
376
|
+
|
|
377
|
+
if current_kind == "CROSS":
|
|
378
|
+
join_expr.set("kind", "CROSS LATERAL")
|
|
379
|
+
elif current_kind == "OUTER" and current_side == "FULL":
|
|
380
|
+
join_expr.set("side", "FULL") # Keep side
|
|
381
|
+
join_expr.set("kind", "OUTER LATERAL")
|
|
382
|
+
elif current_side:
|
|
383
|
+
join_expr.set("kind", f"{current_side} LATERAL")
|
|
384
|
+
join_expr.set("side", None) # Clear side to avoid duplication
|
|
385
|
+
else:
|
|
386
|
+
join_expr.set("kind", "LATERAL")
|
|
387
|
+
|
|
388
|
+
return join_expr
|