matrx-orm 3.1.4__tar.gz → 3.1.5__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.
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/PKG-INFO +1 -1
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/expressions.py +75 -3
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/pyproject.toml +1 -1
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_case_when.py +88 -8
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/.gitignore +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/BASE_CLASS_METHODS.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/CLAUDE.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/MODEL_API.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/docs/migrations.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/adapters/MODULE_README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/adapters/postgresql.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/admin/router.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/async_db_manager.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/base.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/config.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/db_function.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/fields.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/introspection.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/registry.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/resilience.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/rls_session.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/transaction.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/migrations/diff.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/operations/staging_load.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/operations/update.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/python_sql/db_objects.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/query/builder.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/query/executor.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/code_handler.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/session/README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/session/coalesce.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/session/fallback.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/session/flush.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/session/lifecycle.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/session/managed.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/session/op.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/session/session.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/state.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/scripts/release.sh +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/conftest.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_admin_db_columns.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_execute_admin_sql.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_expression_primitives.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_filter_raw.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_from_alias.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_introspect_rls_policies.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_join_and_aggregate.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_managed_write_guard.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_query_builder.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_query_timeout.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_querybuilder_mutation_semantics.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_registry.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_resilience.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_schema_exists.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_watched_lifecycle.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level2/test_migrations_live.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/session/test_coalesce.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/session/test_dag.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/session/test_disk_spill_fallback.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/session/test_flush_individual_fallback.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/session/test_governed_write.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.4 → matrx_orm-3.1.5}/tests/test_sql_param_casts.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matrx-orm
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.5
|
|
4
4
|
Summary: Async-first PostgreSQL ORM with bidirectional migrations, schema introspection, many-to-many relationships, and built-in state caching
|
|
5
5
|
Project-URL: Homepage, https://github.com/AI-Matrix-Engine/aidream-current
|
|
6
6
|
Project-URL: Repository, https://github.com/AI-Matrix-Engine/aidream-current
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
from decimal import Decimal
|
|
3
5
|
from typing import Any, TYPE_CHECKING
|
|
6
|
+
from uuid import UUID
|
|
4
7
|
|
|
5
8
|
if TYPE_CHECKING:
|
|
6
9
|
pass
|
|
@@ -533,6 +536,70 @@ def _render_func_arg(arg: Any, params: list[Any]) -> str:
|
|
|
533
536
|
return f"${len(params)}"
|
|
534
537
|
|
|
535
538
|
|
|
539
|
+
# ---------------------------------------------------------------------------
|
|
540
|
+
# Type-anchoring for bound literals in a CASE branch
|
|
541
|
+
# ---------------------------------------------------------------------------
|
|
542
|
+
#
|
|
543
|
+
# Postgres infers a bare `$N`'s type from its surrounding context. A CASE whose
|
|
544
|
+
# THEN/ELSE branches are ALL bound params has NO anchor to infer from, so it
|
|
545
|
+
# falls back to `text` — and asyncpg then refuses to encode a Python int/bool/
|
|
546
|
+
# float into that text slot:
|
|
547
|
+
#
|
|
548
|
+
# invalid input for query argument $5: 0 (expected str, got int)
|
|
549
|
+
#
|
|
550
|
+
# Verified live (project txzxabzwovsujtloxrus):
|
|
551
|
+
# PREPARE p AS SELECT 1 FROM t ORDER BY CASE WHEN lower(name) = $1
|
|
552
|
+
# THEN $2 ELSE $3 END;
|
|
553
|
+
# -> parameter_types = {text,text,text} # $2/$3 silently become text
|
|
554
|
+
# ... THEN $2::bigint ELSE $3::bigint END
|
|
555
|
+
# -> parameter_types = {text,bigint,bigint} # anchored
|
|
556
|
+
#
|
|
557
|
+
# So a CASE-branch literal is bound WITH an explicit cast: the value stays
|
|
558
|
+
# parameterized (zero injection surface) and Postgres gets its type anchor.
|
|
559
|
+
# Anywhere else (`col = $N`, `func($N)`) the column/signature already supplies
|
|
560
|
+
# the anchor, which is why this is scoped to CASE rather than applied to every
|
|
561
|
+
# bound literal — a blanket `::text` would break `uuid_col = $N`.
|
|
562
|
+
#
|
|
563
|
+
# Order matters: bool before int (bool IS a subclass of int), datetime before
|
|
564
|
+
# date (datetime IS a subclass of date).
|
|
565
|
+
_PG_LITERAL_CASTS: tuple[tuple[type, str], ...] = (
|
|
566
|
+
(bool, "boolean"),
|
|
567
|
+
(int, "bigint"),
|
|
568
|
+
(float, "double precision"),
|
|
569
|
+
(Decimal, "numeric"),
|
|
570
|
+
(datetime, "timestamptz"),
|
|
571
|
+
(date, "date"),
|
|
572
|
+
(UUID, "uuid"),
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _pg_cast_for_literal(value: Any) -> str | None:
|
|
577
|
+
"""The Postgres type to anchor a bound Python literal to, or None if unknown."""
|
|
578
|
+
for py_type, pg_type in _PG_LITERAL_CASTS:
|
|
579
|
+
if isinstance(value, py_type):
|
|
580
|
+
return pg_type
|
|
581
|
+
return None
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def _render_case_result(arg: Any, params: list[Any]) -> str:
|
|
585
|
+
"""Render a CASE THEN/ELSE branch, type-anchoring any bound literal.
|
|
586
|
+
|
|
587
|
+
Identical to :func:`_render_func_arg` for columns/expressions; the ONLY
|
|
588
|
+
difference is that a bound literal gets an explicit `::type` cast, because a
|
|
589
|
+
CASE branch is a context Postgres cannot infer a param's type from.
|
|
590
|
+
"""
|
|
591
|
+
if arg is None:
|
|
592
|
+
# An unadorned NULL is unknown-typed too, but it unifies with whatever
|
|
593
|
+
# the sibling branch resolves to — no cast needed, and none is possible.
|
|
594
|
+
return "NULL"
|
|
595
|
+
if isinstance(arg, str) or hasattr(arg, "as_sql") or isinstance(arg, F):
|
|
596
|
+
# Bare strings are column names, per the Func-arg convention.
|
|
597
|
+
return _render_func_arg(arg, params)
|
|
598
|
+
params.append(arg)
|
|
599
|
+
cast = _pg_cast_for_literal(arg)
|
|
600
|
+
return f"${len(params)}::{cast}" if cast else f"${len(params)}"
|
|
601
|
+
|
|
602
|
+
|
|
536
603
|
# ---------------------------------------------------------------------------
|
|
537
604
|
# Case / When — conditional CASE WHEN ... THEN ... ELSE ... END expression
|
|
538
605
|
# ---------------------------------------------------------------------------
|
|
@@ -552,8 +619,13 @@ class When:
|
|
|
552
619
|
|
|
553
620
|
Usage::
|
|
554
621
|
|
|
555
|
-
When("lower(e.name) = $1", phrase, then=0) # -> WHEN lower(e.name) = $1 THEN $2
|
|
622
|
+
When("lower(e.name) = $1", phrase, then=0) # -> WHEN lower(e.name) = $1 THEN $2::bigint
|
|
556
623
|
When("ed.src_id = ANY($1::uuid[])", ids, then="ed.dst_id") # then = bare column, not bound
|
|
624
|
+
|
|
625
|
+
A bound ``then``/``default`` literal is rendered WITH an explicit ``::type``
|
|
626
|
+
cast — a CASE branch gives Postgres no type to infer from, so an unadorned
|
|
627
|
+
``$N`` silently resolves to ``text`` and asyncpg then rejects a Python int
|
|
628
|
+
("expected str, got int"). See :func:`_render_case_result`.
|
|
557
629
|
"""
|
|
558
630
|
|
|
559
631
|
def __init__(self, condition: str, *condition_params: Any, then: Any) -> None:
|
|
@@ -569,7 +641,7 @@ class When:
|
|
|
569
641
|
params.extend(self.condition_params)
|
|
570
642
|
else:
|
|
571
643
|
cond_sql = self.condition
|
|
572
|
-
then_sql =
|
|
644
|
+
then_sql = _render_case_result(self.then, params)
|
|
573
645
|
return f"WHEN {cond_sql} THEN {then_sql}"
|
|
574
646
|
|
|
575
647
|
def __repr__(self) -> str:
|
|
@@ -615,7 +687,7 @@ class Case:
|
|
|
615
687
|
for w in self.whens:
|
|
616
688
|
parts.append(w.as_sql(params))
|
|
617
689
|
if self.default is not None:
|
|
618
|
-
parts.append(f"ELSE {
|
|
690
|
+
parts.append(f"ELSE {_render_case_result(self.default, params)}")
|
|
619
691
|
parts.append("END")
|
|
620
692
|
return " ".join(parts)
|
|
621
693
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "matrx-orm"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.5"
|
|
4
4
|
description = "Async-first PostgreSQL ORM with bidirectional migrations, schema introspection, many-to-many relationships, and built-in state caching"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -48,14 +48,14 @@ def test_case_rejects_non_when_args():
|
|
|
48
48
|
def test_single_when_with_literal_then_and_default():
|
|
49
49
|
params: list = []
|
|
50
50
|
sql = Case(When("status = 'paid'", then=1), default=0).as_sql(params)
|
|
51
|
-
assert sql == "CASE WHEN status = 'paid' THEN $1 ELSE $2 END"
|
|
51
|
+
assert sql == "CASE WHEN status = 'paid' THEN $1::bigint ELSE $2::bigint END"
|
|
52
52
|
assert params == [1, 0]
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
def test_when_condition_with_bound_param():
|
|
56
56
|
params: list = []
|
|
57
57
|
sql = Case(When("lower(e.name) = $1", "acme corp", then=0), default=1).as_sql(params)
|
|
58
|
-
assert sql == "CASE WHEN lower(e.name) = $1 THEN $2 ELSE $3 END"
|
|
58
|
+
assert sql == "CASE WHEN lower(e.name) = $1 THEN $2::bigint ELSE $3::bigint END"
|
|
59
59
|
assert params == ["acme corp", 0, 1]
|
|
60
60
|
|
|
61
61
|
|
|
@@ -75,7 +75,7 @@ def test_when_then_bare_column_name_not_bound():
|
|
|
75
75
|
def test_no_default_omits_else_clause():
|
|
76
76
|
params: list = []
|
|
77
77
|
sql = Case(When("x = 1", then=0)).as_sql(params)
|
|
78
|
-
assert sql == "CASE WHEN x = 1 THEN $1 END"
|
|
78
|
+
assert sql == "CASE WHEN x = 1 THEN $1::bigint END"
|
|
79
79
|
assert "ELSE" not in sql
|
|
80
80
|
|
|
81
81
|
|
|
@@ -86,14 +86,14 @@ def test_multiple_when_branches():
|
|
|
86
86
|
When("tier = 'silver'", then=2),
|
|
87
87
|
default=1,
|
|
88
88
|
).as_sql(params)
|
|
89
|
-
assert sql == "CASE WHEN tier = 'gold' THEN $1 WHEN tier = 'silver' THEN $2 ELSE $3 END"
|
|
89
|
+
assert sql == "CASE WHEN tier = 'gold' THEN $1::bigint WHEN tier = 'silver' THEN $2::bigint ELSE $3::bigint END"
|
|
90
90
|
assert params == [3, 2, 1]
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
def test_condition_params_shift_past_existing_params():
|
|
94
94
|
params: list = ["existing"]
|
|
95
95
|
sql = Case(When("name = $1", "bob", then=1), default=0).as_sql(params)
|
|
96
|
-
assert sql == "CASE WHEN name = $2 THEN $3 ELSE $4 END"
|
|
96
|
+
assert sql == "CASE WHEN name = $2 THEN $3::bigint ELSE $4::bigint END"
|
|
97
97
|
assert params == ["existing", "bob", 1, 0]
|
|
98
98
|
|
|
99
99
|
|
|
@@ -110,7 +110,7 @@ def test_nested_case():
|
|
|
110
110
|
params: list = []
|
|
111
111
|
inner = Case(When("b = 1", then=2), default=3)
|
|
112
112
|
sql = Case(When("a = 1", then=inner), default=0).as_sql(params)
|
|
113
|
-
assert sql == "CASE WHEN a = 1 THEN CASE WHEN b = 1 THEN $1 ELSE $2 END ELSE $3 END"
|
|
113
|
+
assert sql == "CASE WHEN a = 1 THEN CASE WHEN b = 1 THEN $1::bigint ELSE $2::bigint END ELSE $3::bigint END"
|
|
114
114
|
assert params == [2, 3, 0]
|
|
115
115
|
|
|
116
116
|
|
|
@@ -167,7 +167,7 @@ def test_case_in_order_by():
|
|
|
167
167
|
"-amount",
|
|
168
168
|
)
|
|
169
169
|
sql, params = QueryExecutor(qb._build_query())._build_query()
|
|
170
|
-
assert "ORDER BY CASE WHEN status = $1 THEN $2 ELSE $3 END ASC, amount DESC" in sql
|
|
170
|
+
assert "ORDER BY CASE WHEN status = $1 THEN $2::bigint ELSE $3::bigint END ASC, amount DESC" in sql
|
|
171
171
|
assert params == ["paid", 0, 1]
|
|
172
172
|
|
|
173
173
|
|
|
@@ -181,7 +181,7 @@ def test_case_in_order_by_with_nulls_last():
|
|
|
181
181
|
)
|
|
182
182
|
sql, params = QueryExecutor(qb._build_query())._build_query()
|
|
183
183
|
assert (
|
|
184
|
-
"ORDER BY CASE WHEN lower(status) = $1 THEN $2 ELSE $3 END ASC, "
|
|
184
|
+
"ORDER BY CASE WHEN lower(status) = $1 THEN $2::bigint ELSE $3::bigint END ASC, "
|
|
185
185
|
"amount DESC NULLS LAST" in sql
|
|
186
186
|
)
|
|
187
187
|
assert params == ["paid", 0, 1]
|
|
@@ -264,3 +264,83 @@ def test_join_without_on_params_still_works_unchanged():
|
|
|
264
264
|
sql, params = QueryExecutor(qb._build_query())._build_query()
|
|
265
265
|
assert "INNER JOIN rag.kg_entities AS e2 ON e2.id = ed.src_id" in sql
|
|
266
266
|
assert params == []
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# ── Type anchoring: the bug this cast exists to prevent ──────────────────────
|
|
270
|
+
#
|
|
271
|
+
# REGRESSION GUARD. Do NOT "simplify" the `::type` casts out of a CASE branch.
|
|
272
|
+
#
|
|
273
|
+
# Postgres infers a bare `$N`'s type from context. A CASE whose THEN/ELSE
|
|
274
|
+
# branches are ALL bound params has NO context to infer from, so PG resolves
|
|
275
|
+
# them to `text` — and asyncpg then refuses to encode a Python int:
|
|
276
|
+
#
|
|
277
|
+
# asyncpg.exceptions.DataError:
|
|
278
|
+
# invalid input for query argument $5: 0 (expected str, got int)
|
|
279
|
+
#
|
|
280
|
+
# Verified live (project txzxabzwovsujtloxrus):
|
|
281
|
+
# ORDER BY CASE WHEN lower(name) = $1 THEN $2 ELSE $3 END
|
|
282
|
+
# -> parameter_types = {text,text,text} # WRONG, and silent
|
|
283
|
+
# ORDER BY CASE WHEN lower(name) = $1 THEN $2::bigint ELSE $3::bigint END
|
|
284
|
+
# -> parameter_types = {text,bigint,bigint} # anchored
|
|
285
|
+
#
|
|
286
|
+
# This shipped broken: every matrx-rag `rag_search` call died in _entity_recall
|
|
287
|
+
# (2026-07-13). The tests above had PINNED the unanchored SQL, so the suite was
|
|
288
|
+
# green while the feature was 100% down.
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def test_bound_case_literals_are_type_anchored_not_bare_params():
|
|
292
|
+
"""A bound THEN/ELSE literal MUST carry an explicit cast — a bare `$N` in a
|
|
293
|
+
CASE branch is silently typed `text` by Postgres and blows up on an int."""
|
|
294
|
+
params: list = []
|
|
295
|
+
sql = Case(When("lower(e.name) = $1", "acme", then=0), default=1).as_sql(params)
|
|
296
|
+
# The int literals are bound (never inlined) AND anchored.
|
|
297
|
+
assert "THEN $2::bigint" in sql
|
|
298
|
+
assert "ELSE $3::bigint" in sql
|
|
299
|
+
assert params == ["acme", 0, 1]
|
|
300
|
+
# No branch may render as a bare, unanchored parameter.
|
|
301
|
+
assert "THEN $2 " not in sql and not sql.endswith("THEN $2")
|
|
302
|
+
assert "ELSE $3 " not in sql and "ELSE $3 END" not in sql
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@pytest.mark.parametrize(
|
|
306
|
+
"value,expected_cast",
|
|
307
|
+
[
|
|
308
|
+
(0, "bigint"),
|
|
309
|
+
(42, "bigint"),
|
|
310
|
+
(True, "boolean"), # bool BEFORE int — bool is a subclass of int
|
|
311
|
+
(False, "boolean"),
|
|
312
|
+
(1.5, "double precision"),
|
|
313
|
+
],
|
|
314
|
+
)
|
|
315
|
+
def test_case_literal_cast_per_python_type(value, expected_cast):
|
|
316
|
+
params: list = []
|
|
317
|
+
sql = Case(When("x = 1", then=value)).as_sql(params)
|
|
318
|
+
assert sql == f"CASE WHEN x = 1 THEN $1::{expected_cast} END"
|
|
319
|
+
assert params == [value]
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def test_bool_then_is_boolean_not_bigint():
|
|
323
|
+
"""Guards the isinstance ordering: `isinstance(True, int)` is True, so a
|
|
324
|
+
naive int-first map would render a bool as ::bigint."""
|
|
325
|
+
params: list = []
|
|
326
|
+
sql = Case(When("x = 1", then=True), default=False).as_sql(params)
|
|
327
|
+
assert "::boolean" in sql
|
|
328
|
+
assert "::bigint" not in sql
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def test_string_then_is_still_a_bare_column_not_a_cast_param():
|
|
332
|
+
"""The str-is-a-column-name convention is UNCHANGED by the anchoring —
|
|
333
|
+
a str `then` must not suddenly start binding as `$N::text`."""
|
|
334
|
+
params: list = []
|
|
335
|
+
sql = Case(When("a = 1", then="ed.dst_id"), default="ed.src_id").as_sql(params)
|
|
336
|
+
assert sql == "CASE WHEN a = 1 THEN ed.dst_id ELSE ed.src_id END"
|
|
337
|
+
assert params == []
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def test_none_then_renders_null_not_an_anchorless_param():
|
|
341
|
+
"""A bound NULL is unknown-typed too; render it inline so it unifies with
|
|
342
|
+
the sibling branch instead of becoming another text param."""
|
|
343
|
+
params: list = []
|
|
344
|
+
sql = Case(When("a = 1", then=None), default=5).as_sql(params)
|
|
345
|
+
assert sql == "CASE WHEN a = 1 THEN NULL ELSE $1::bigint END"
|
|
346
|
+
assert params == [5]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|