matrx-orm 3.1.6__tar.gz → 3.1.8__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.6 → matrx_orm-3.1.8}/BASE_CLASS_METHODS.md +16 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/PKG-INFO +1 -1
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/__init__.py +2 -1
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/base.py +65 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/db_function.py +25 -1
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/query/builder.py +191 -111
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/code_handler.py +3 -3
- matrx_orm-3.1.8/matrx_orm/schema_builder/codegen_writer.py +114 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/pyproject.toml +1 -1
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_filter_raw.py +4 -1
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_join_and_aggregate.py +4 -1
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_query_builder.py +15 -2
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_query_timeout.py +5 -2
- matrx_orm-3.1.8/tests/level1/test_querybuilder_clone_semantics.py +238 -0
- matrx_orm-3.1.8/tests/test_sync_twins_guard.py +52 -0
- matrx_orm-3.1.6/tests/level1/test_querybuilder_mutation_semantics.py +0 -114
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/.gitignore +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/CLAUDE.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/MODEL_API.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/docs/migrations.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/adapters/MODULE_README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/adapters/postgresql.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/admin/router.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/async_db_manager.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/config.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/expressions.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/fields.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/introspection.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/registry.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/resilience.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/rls_session.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/transaction.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/migrations/diff.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/operations/staging_load.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/operations/update.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/python_sql/db_objects.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/query/executor.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/session/README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/session/coalesce.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/session/fallback.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/session/flush.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/session/lifecycle.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/session/managed.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/session/op.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/session/session.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/state.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/scripts/release.sh +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/conftest.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_admin_db_columns.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_case_when.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_composite_pk_write_paths.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_execute_admin_sql.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_expression_primitives.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_from_alias.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_introspect_rls_policies.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_managed_write_guard.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_registry.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_resilience.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_schema_exists.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_watched_lifecycle.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level2/test_migrations_live.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/session/test_coalesce.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/session/test_dag.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/session/test_disk_spill_fallback.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/session/test_flush_individual_fallback.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/session/test_governed_write.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.6 → matrx_orm-3.1.8}/tests/test_sql_param_casts.py +0 -0
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# ORM Manager Base Class Methods
|
|
2
2
|
|
|
3
|
+
> **`QueryBuilder` chaining is clone-on-chain (copy-on-write), like Django's
|
|
4
|
+
> `QuerySet`.** Every chain method (`.filter()`, `.exclude()`, `.order_by()`,
|
|
5
|
+
> `.join()`, `.annotate()`, `.filter_raw()`, ...) returns a NEW `QueryBuilder`
|
|
6
|
+
> and never mutates the one it was called on. It is safe to save a base
|
|
7
|
+
> queryset and branch off it multiple times — each branch is independent:
|
|
8
|
+
>
|
|
9
|
+
> ```python
|
|
10
|
+
> base = Model.filter(doc_id=x)
|
|
11
|
+
> raw_count = await base.exclude(raw_text="").count()
|
|
12
|
+
> clean_count = await base.exclude(cleaned_text="").count() # independent of raw_count
|
|
13
|
+
> ```
|
|
14
|
+
>
|
|
15
|
+
> The one thing to remember: always use the RETURN VALUE of a chain call. A
|
|
16
|
+
> bare `qb.filter(x=1)` statement (return discarded) is a no-op on `qb`
|
|
17
|
+
> itself — reassign (`qb = qb.filter(x=1)`) or chain directly.
|
|
18
|
+
|
|
3
19
|
> **Model-direct aliases (single-table work).** For small, isolated, single-table
|
|
4
20
|
> work you do NOT need the manager — the generated `Model` class itself exposes
|
|
5
21
|
> the same vocabulary as classmethods: `load_by_id(id)`, `load_by_id_or_none(id)`,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matrx-orm
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.8
|
|
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
|
|
@@ -27,7 +27,7 @@ from .core.config import (
|
|
|
27
27
|
resolve_database_name,
|
|
28
28
|
resolve_pool_mode,
|
|
29
29
|
)
|
|
30
|
-
from .core.db_function import ArrayArg, call_function
|
|
30
|
+
from .core.db_function import ArrayArg, call_function, call_function_sync
|
|
31
31
|
from .entity import EntityCapabilityError, MatrxEntity
|
|
32
32
|
from .core.diagnostics import collect_pool_diagnostics, log_pool_diagnostics
|
|
33
33
|
from .core.introspection import introspect_columns, introspect_rls_policies, schema_exists
|
|
@@ -318,6 +318,7 @@ __all__ = [
|
|
|
318
318
|
"install_asyncpg_idle_close_filter",
|
|
319
319
|
# ── DB-side function calls (RPC) ──────────────────────────────────────────
|
|
320
320
|
"call_function",
|
|
321
|
+
"call_function_sync",
|
|
321
322
|
"MatrxEntity",
|
|
322
323
|
"EntityCapabilityError",
|
|
323
324
|
"ArrayArg",
|
|
@@ -1263,6 +1263,71 @@ class Model(RuntimeMixin, metaclass=ModelMeta):
|
|
|
1263
1263
|
|
|
1264
1264
|
return run_sync(cls.filter(**kwargs).all())
|
|
1265
1265
|
|
|
1266
|
+
@classmethod
|
|
1267
|
+
def create_sync(cls, **kwargs: Any) -> Self:
|
|
1268
|
+
"""Synchronous twin of create(). Sync contexts only."""
|
|
1269
|
+
try:
|
|
1270
|
+
asyncio.get_running_loop()
|
|
1271
|
+
raise RuntimeError(
|
|
1272
|
+
"Model.create_sync() called in an async context. Use await Model.create(...) instead."
|
|
1273
|
+
)
|
|
1274
|
+
except RuntimeError as e:
|
|
1275
|
+
if "no running event loop" not in str(e):
|
|
1276
|
+
raise
|
|
1277
|
+
|
|
1278
|
+
return run_sync(cls.create(**kwargs))
|
|
1279
|
+
|
|
1280
|
+
@classmethod
|
|
1281
|
+
def upsert_sync(
|
|
1282
|
+
cls,
|
|
1283
|
+
data: dict[str, Any],
|
|
1284
|
+
conflict_fields: list[str] | None = None,
|
|
1285
|
+
update_fields: list[str] | None = None,
|
|
1286
|
+
*,
|
|
1287
|
+
on_conflict: list[str] | ConflictTarget | None = None, # noqa: F821
|
|
1288
|
+
) -> Self:
|
|
1289
|
+
"""Synchronous twin of upsert(). Sync contexts only."""
|
|
1290
|
+
try:
|
|
1291
|
+
asyncio.get_running_loop()
|
|
1292
|
+
raise RuntimeError(
|
|
1293
|
+
"Model.upsert_sync() called in an async context. Use await Model.upsert(...) instead."
|
|
1294
|
+
)
|
|
1295
|
+
except RuntimeError as e:
|
|
1296
|
+
if "no running event loop" not in str(e):
|
|
1297
|
+
raise
|
|
1298
|
+
|
|
1299
|
+
return run_sync(
|
|
1300
|
+
cls.upsert(data, conflict_fields, update_fields, on_conflict=on_conflict)
|
|
1301
|
+
)
|
|
1302
|
+
|
|
1303
|
+
@classmethod
|
|
1304
|
+
def update_where_sync(cls, filters: dict[str, Any], **updates: Any) -> UpdateResult:
|
|
1305
|
+
"""Synchronous twin of update_where(). Sync contexts only."""
|
|
1306
|
+
try:
|
|
1307
|
+
asyncio.get_running_loop()
|
|
1308
|
+
raise RuntimeError(
|
|
1309
|
+
"Model.update_where_sync() called in an async context. Use await Model.update_where(...) instead."
|
|
1310
|
+
)
|
|
1311
|
+
except RuntimeError as e:
|
|
1312
|
+
if "no running event loop" not in str(e):
|
|
1313
|
+
raise
|
|
1314
|
+
|
|
1315
|
+
return run_sync(cls.update_where(filters, **updates))
|
|
1316
|
+
|
|
1317
|
+
@classmethod
|
|
1318
|
+
def delete_where_sync(cls, **filters: Any) -> int:
|
|
1319
|
+
"""Synchronous twin of delete_where(). Sync contexts only."""
|
|
1320
|
+
try:
|
|
1321
|
+
asyncio.get_running_loop()
|
|
1322
|
+
raise RuntimeError(
|
|
1323
|
+
"Model.delete_where_sync() called in an async context. Use await Model.delete_where(...) instead."
|
|
1324
|
+
)
|
|
1325
|
+
except RuntimeError as e:
|
|
1326
|
+
if "no running event loop" not in str(e):
|
|
1327
|
+
raise
|
|
1328
|
+
|
|
1329
|
+
return run_sync(cls.delete_where(**filters))
|
|
1330
|
+
|
|
1266
1331
|
@classmethod
|
|
1267
1332
|
async def all(cls) -> list[Self]:
|
|
1268
1333
|
try:
|
|
@@ -70,7 +70,7 @@ from typing import Any, Literal
|
|
|
70
70
|
from matrx_orm.core.async_db_manager import AsyncDatabaseManager
|
|
71
71
|
from matrx_orm.core.transaction import get_active_connection
|
|
72
72
|
|
|
73
|
-
__all__ = ["call_function", "ArrayArg"]
|
|
73
|
+
__all__ = ["call_function", "call_function_sync", "ArrayArg"]
|
|
74
74
|
|
|
75
75
|
# Postgres unquoted-identifier shape. Deliberately strict — this is the only
|
|
76
76
|
# thing standing between a caller-supplied name and the query string, so a
|
|
@@ -195,3 +195,27 @@ async def call_function(
|
|
|
195
195
|
rows = await conn.fetch(sql, *bound_args)
|
|
196
196
|
return [dict(r) for r in rows]
|
|
197
197
|
return await conn.fetchval(sql, *bound_args)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def call_function_sync(
|
|
201
|
+
database: str,
|
|
202
|
+
schema: str,
|
|
203
|
+
function: str,
|
|
204
|
+
*args: Any,
|
|
205
|
+
mode: Literal["scalar", "none", "rows"] = "scalar",
|
|
206
|
+
field: str | None = None,
|
|
207
|
+
) -> Any:
|
|
208
|
+
"""Synchronous twin of :func:`call_function`. Sync contexts only."""
|
|
209
|
+
import asyncio
|
|
210
|
+
|
|
211
|
+
from matrx_orm.core.async_db_manager import run_sync
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
asyncio.get_running_loop()
|
|
215
|
+
raise RuntimeError(
|
|
216
|
+
"call_function_sync() called in an async context. Use await call_function(...) instead."
|
|
217
|
+
)
|
|
218
|
+
except RuntimeError as e:
|
|
219
|
+
if "no running event loop" not in str(e):
|
|
220
|
+
raise
|
|
221
|
+
return run_sync(call_function(database, schema, function, *args, mode=mode, field=field))
|
|
@@ -127,46 +127,68 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
127
127
|
"""Lazy, chainable query spec — built up via `.filter()`/`.exclude()`/etc,
|
|
128
128
|
executed on `.all()`/`.count()`/`.exists()`/`.values()`/....
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
(`.filter()`, `.exclude()`, `.join()`, `.annotate()`,
|
|
132
|
-
`.order_by()`, `.limit()`, ...)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
off a saved queryset is side-effect-free
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
This is intentional current behavior, not an oversight to "just fix" —
|
|
162
|
-
switching to clone-on-chain is a real, wide-blast-radius ORM change
|
|
163
|
-
(every `matrx-*` package + `aidream/services/*` calls this) that needs
|
|
164
|
-
its own audited migration, not a silent semantic flip. Tracked as a
|
|
165
|
-
platform follow-up; see `tests/level1/test_querybuilder_mutation_semantics.py`
|
|
166
|
-
for the pinned-current-behavior regression test that must be updated
|
|
167
|
-
(not just deleted) the day this changes.
|
|
130
|
+
**Clone-on-chain (copy-on-write) — Django-`QuerySet`-SAFE.** Every chain
|
|
131
|
+
method (`.filter()`, `.exclude()`, `.join()`, `.annotate()`,
|
|
132
|
+
`.filter_raw()`, `.order_by()`, `.limit()`, ...) returns a NEW
|
|
133
|
+
`QueryBuilder` that shares nothing mutable with `self` — the original
|
|
134
|
+
builder is never modified. This matches Django's `QuerySet`: branching
|
|
135
|
+
off a saved queryset is side-effect-free, and reusing a variable across
|
|
136
|
+
independent calls no longer accumulates state.
|
|
137
|
+
|
|
138
|
+
::
|
|
139
|
+
|
|
140
|
+
base = Model.filter(doc_id=x)
|
|
141
|
+
raw_count = await base.exclude(raw_text="").count()
|
|
142
|
+
clean_count = await base.exclude(cleaned_text="").count()
|
|
143
|
+
# Independent — `clean_count` does NOT carry the raw_text exclude.
|
|
144
|
+
# `base` itself is untouched by either branch.
|
|
145
|
+
|
|
146
|
+
(Before this, the two branches above bled into each other — the exact
|
|
147
|
+
shape that shipped a real count-corruption bug in
|
|
148
|
+
`aidream/services/rag/library_status.py`, worked around there by
|
|
149
|
+
building fresh querysets. That workaround is no longer required, though
|
|
150
|
+
still harmless.)
|
|
151
|
+
|
|
152
|
+
**Still true:** always use the RETURN VALUE of a chain call — a bare
|
|
153
|
+
`qb.filter(x=1)` statement (return discarded, not reassigned) is now a
|
|
154
|
+
no-op on `qb` itself (the filter lives only on the discarded clone).
|
|
155
|
+
Reassign (`qb = qb.filter(x=1)`) or chain directly
|
|
156
|
+
(`Model.filter(x=1).exclude(y=2)`).
|
|
157
|
+
|
|
158
|
+
Terminal methods (`.all()`, `.first()`, `.get()`, `.count()`,
|
|
159
|
+
`.exists()`, `.values()`, `.update()`, `.delete()`, `.aggregate()`, ...)
|
|
160
|
+
execute the query and do NOT clone.
|
|
168
161
|
"""
|
|
169
162
|
|
|
163
|
+
def _clone(self) -> QueryBuilder[ModelT]:
|
|
164
|
+
"""Return a new QueryBuilder sharing `self`'s scalar state but with
|
|
165
|
+
every mutable container (lists/dicts/sets this builder appends to)
|
|
166
|
+
copied — so mutating the clone's containers never touches `self`'s,
|
|
167
|
+
and vice versa. Elements inside those containers (filter dicts, Q
|
|
168
|
+
objects, join specs, ...) are treated as immutable value objects and
|
|
169
|
+
are NOT deep-copied — only the containers themselves are copied.
|
|
170
|
+
"""
|
|
171
|
+
new = self.__class__.__new__(self.__class__)
|
|
172
|
+
new.__dict__.update(self.__dict__)
|
|
173
|
+
new.filters = list(self.filters)
|
|
174
|
+
new.excludes = list(self.excludes)
|
|
175
|
+
new.order_by_fields = list(self.order_by_fields)
|
|
176
|
+
new.select_fields = list(self.select_fields)
|
|
177
|
+
new.deferred_fields = set(self.deferred_fields)
|
|
178
|
+
new.only_fields = set(self.only_fields)
|
|
179
|
+
new.prefetch_fields = list(self.prefetch_fields)
|
|
180
|
+
new.joins = list(self.joins)
|
|
181
|
+
new.group_by_fields = list(self.group_by_fields)
|
|
182
|
+
new.having_filters = list(self.having_filters)
|
|
183
|
+
new.aggregations = list(self.aggregations)
|
|
184
|
+
new.distinct_fields = list(self.distinct_fields) if self.distinct_fields is not None else None
|
|
185
|
+
new.for_update_opts = dict(self.for_update_opts) if self.for_update_opts is not None else None
|
|
186
|
+
new.select_related_fields = list(self.select_related_fields)
|
|
187
|
+
new.ctes = list(self.ctes)
|
|
188
|
+
new.vector_order = dict(self.vector_order) if self.vector_order is not None else None
|
|
189
|
+
new.raw_filters = list(self.raw_filters)
|
|
190
|
+
return new
|
|
191
|
+
|
|
170
192
|
model: type[ModelT]
|
|
171
193
|
database: str
|
|
172
194
|
filters: list[Any] # dict[str,Any] or Q objects
|
|
@@ -235,18 +257,17 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
235
257
|
.filter(Q(status="active") | Q(role="admin"))
|
|
236
258
|
.filter(Q(a=1), b=2) # AND composition
|
|
237
259
|
|
|
238
|
-
|
|
239
|
-
docstring). Reusing a saved builder across independent branches
|
|
240
|
-
accumulates filters onto every branch.
|
|
260
|
+
Clone-on-chain: returns a NEW builder; `self` is unchanged.
|
|
241
261
|
"""
|
|
242
262
|
from ..core.expressions import Exists, Q, TsMatch
|
|
243
263
|
|
|
264
|
+
new = self._clone()
|
|
244
265
|
for arg in args:
|
|
245
266
|
# Positional predicate expressions the executor renders from the
|
|
246
267
|
# filters list: Q, Exists(...) (correlated subquery), and
|
|
247
268
|
# TsMatch(...) (full-text WHERE match).
|
|
248
269
|
if isinstance(arg, (Q, Exists, TsMatch)):
|
|
249
|
-
|
|
270
|
+
new.filters.append(arg)
|
|
250
271
|
else:
|
|
251
272
|
raise TypeError(
|
|
252
273
|
"Positional filter argument must be a Q, Exists, or TsMatch object, "
|
|
@@ -254,8 +275,8 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
254
275
|
)
|
|
255
276
|
if kwargs:
|
|
256
277
|
self._validate_filter_keys(kwargs)
|
|
257
|
-
|
|
258
|
-
return
|
|
278
|
+
new.filters.append(kwargs)
|
|
279
|
+
return new
|
|
259
280
|
|
|
260
281
|
def _validate_filter_keys(self, kwargs: dict[str, Any]) -> None:
|
|
261
282
|
"""Reject filter kwargs whose field/operator don't exist on the model.
|
|
@@ -330,15 +351,15 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
330
351
|
def exclude(self, **kwargs: Any) -> QueryBuilder[ModelT]:
|
|
331
352
|
"""Apply SQL exclusion filters.
|
|
332
353
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
independent read instead.
|
|
354
|
+
Clone-on-chain: returns a NEW builder; `self` is unchanged. Calling
|
|
355
|
+
`.exclude()` twice off the SAME saved builder variable now gives two
|
|
356
|
+
INDEPENDENT branches (each excludes only its own kwargs) — it does
|
|
357
|
+
not AND both excludes together the way in-place mutation used to.
|
|
338
358
|
"""
|
|
359
|
+
new = self._clone()
|
|
339
360
|
if kwargs:
|
|
340
|
-
|
|
341
|
-
return
|
|
361
|
+
new.excludes.append(kwargs)
|
|
362
|
+
return new
|
|
342
363
|
|
|
343
364
|
def filter_raw(self, sql: str, *params: Any) -> QueryBuilder[ModelT]:
|
|
344
365
|
"""Add a parametrized raw SQL fragment to the WHERE clause (AND-ed).
|
|
@@ -392,33 +413,40 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
392
413
|
f"but found placeholders {sorted(found) or 'none'} for {len(params)} param(s).\n"
|
|
393
414
|
f" SQL: {sql!r}"
|
|
394
415
|
)
|
|
395
|
-
self.
|
|
396
|
-
|
|
416
|
+
new = self._clone()
|
|
417
|
+
new.raw_filters.append((sql, list(params)))
|
|
418
|
+
return new
|
|
397
419
|
|
|
398
420
|
def order_by(self, *fields: str | Any) -> QueryBuilder[ModelT]:
|
|
399
|
-
self.
|
|
400
|
-
|
|
421
|
+
new = self._clone()
|
|
422
|
+
new.order_by_fields.extend(fields)
|
|
423
|
+
return new
|
|
401
424
|
|
|
402
425
|
def limit(self, value: int) -> QueryBuilder[ModelT]:
|
|
403
|
-
|
|
404
|
-
|
|
426
|
+
new = self._clone()
|
|
427
|
+
new.limit_val = value
|
|
428
|
+
return new
|
|
405
429
|
|
|
406
430
|
def offset(self, value: int) -> QueryBuilder[ModelT]:
|
|
407
|
-
|
|
408
|
-
|
|
431
|
+
new = self._clone()
|
|
432
|
+
new.offset_val = value
|
|
433
|
+
return new
|
|
409
434
|
|
|
410
435
|
def select(self, *fields: str) -> QueryBuilder[ModelT]:
|
|
411
|
-
self.
|
|
412
|
-
|
|
436
|
+
new = self._clone()
|
|
437
|
+
new.select_fields.extend(fields)
|
|
438
|
+
return new
|
|
413
439
|
|
|
414
440
|
def prefetch_related(self, *fields: str) -> QueryBuilder[ModelT]:
|
|
415
|
-
self.
|
|
416
|
-
|
|
441
|
+
new = self._clone()
|
|
442
|
+
new.prefetch_fields.extend(fields)
|
|
443
|
+
return new
|
|
417
444
|
|
|
418
445
|
def select_related(self, *fields: str) -> QueryBuilder[ModelT]:
|
|
419
446
|
"""Eager-load FK targets via JOIN in the same query (no N+1)."""
|
|
420
|
-
self.
|
|
421
|
-
|
|
447
|
+
new = self._clone()
|
|
448
|
+
new.select_related_fields.extend(fields)
|
|
449
|
+
return new
|
|
422
450
|
|
|
423
451
|
def join(
|
|
424
452
|
self,
|
|
@@ -482,7 +510,8 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
482
510
|
on_sql, on_params = partner.render()
|
|
483
511
|
qb.join(KgEntities, on=f"e2.id = {on_sql}", on_params=on_params, alias="e2")
|
|
484
512
|
"""
|
|
485
|
-
self.
|
|
513
|
+
new = self._clone()
|
|
514
|
+
new.joins.append(
|
|
486
515
|
{
|
|
487
516
|
"model": model_cls,
|
|
488
517
|
"on": on,
|
|
@@ -491,15 +520,17 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
491
520
|
"on_params": list(on_params) if on_params else [],
|
|
492
521
|
}
|
|
493
522
|
)
|
|
494
|
-
return
|
|
523
|
+
return new
|
|
495
524
|
|
|
496
525
|
def group_by(self, *fields: str) -> QueryBuilder[ModelT]:
|
|
497
|
-
self.
|
|
498
|
-
|
|
526
|
+
new = self._clone()
|
|
527
|
+
new.group_by_fields.extend(fields)
|
|
528
|
+
return new
|
|
499
529
|
|
|
500
530
|
def having(self, **kwargs: Any) -> QueryBuilder[ModelT]:
|
|
501
|
-
self.
|
|
502
|
-
|
|
531
|
+
new = self._clone()
|
|
532
|
+
new.having_filters.append(kwargs)
|
|
533
|
+
return new
|
|
503
534
|
|
|
504
535
|
def annotate(self, **kwargs: Any) -> QueryBuilder[ModelT]:
|
|
505
536
|
"""Add computed columns (aggregates or functions) to the SELECT.
|
|
@@ -508,9 +539,10 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
508
539
|
|
|
509
540
|
.annotate(total=Sum("amount"), name_lower=Lower("username"))
|
|
510
541
|
"""
|
|
542
|
+
new = self._clone()
|
|
511
543
|
for key, value in kwargs.items():
|
|
512
|
-
|
|
513
|
-
return
|
|
544
|
+
new.aggregations.append({"name": key, "function": value})
|
|
545
|
+
return new
|
|
514
546
|
|
|
515
547
|
def distinct(self, *fields: str) -> QueryBuilder[ModelT]:
|
|
516
548
|
"""Add DISTINCT or DISTINCT ON to the query.
|
|
@@ -518,8 +550,9 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
518
550
|
No args → ``SELECT DISTINCT ...``
|
|
519
551
|
With args → ``SELECT DISTINCT ON (f1, f2) ...`` (PostgreSQL-specific)
|
|
520
552
|
"""
|
|
521
|
-
|
|
522
|
-
|
|
553
|
+
new = self._clone()
|
|
554
|
+
new.distinct_fields = list(fields)
|
|
555
|
+
return new
|
|
523
556
|
|
|
524
557
|
def select_for_update(
|
|
525
558
|
self,
|
|
@@ -531,8 +564,9 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
531
564
|
|
|
532
565
|
Must be used inside a transaction.
|
|
533
566
|
"""
|
|
534
|
-
|
|
535
|
-
|
|
567
|
+
new = self._clone()
|
|
568
|
+
new.for_update_opts = {"nowait": nowait, "skip_locked": skip_locked, "of": of or []}
|
|
569
|
+
return new
|
|
536
570
|
|
|
537
571
|
def using(self, database: str) -> QueryBuilder[ModelT]:
|
|
538
572
|
"""Override the database for this query.
|
|
@@ -541,8 +575,9 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
541
575
|
|
|
542
576
|
await User.filter(is_active=True).using("replica").all()
|
|
543
577
|
"""
|
|
544
|
-
|
|
545
|
-
|
|
578
|
+
new = self._clone()
|
|
579
|
+
new.database = database
|
|
580
|
+
return new
|
|
546
581
|
|
|
547
582
|
def timeout(self, seconds: float) -> QueryBuilder[ModelT]:
|
|
548
583
|
"""Override the STATEMENT timeout for this query only (seconds).
|
|
@@ -566,8 +601,9 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
566
601
|
"""
|
|
567
602
|
if seconds is not None and seconds <= 0:
|
|
568
603
|
raise ValueError(f"timeout() requires a positive number of seconds, got {seconds!r}")
|
|
569
|
-
|
|
570
|
-
|
|
604
|
+
new = self._clone()
|
|
605
|
+
new.timeout_val = seconds
|
|
606
|
+
return new
|
|
571
607
|
|
|
572
608
|
def from_alias(self, alias: str) -> QueryBuilder[ModelT]:
|
|
573
609
|
"""Alias the base (FROM) table — renders ``FROM <table> AS <alias>``.
|
|
@@ -589,8 +625,9 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
589
625
|
f"from_alias must be a plain ASCII identifier, got {alias!r} — it is "
|
|
590
626
|
"trusted, code-supplied text (a short alias like 'c'), never user input"
|
|
591
627
|
)
|
|
592
|
-
|
|
593
|
-
|
|
628
|
+
new = self._clone()
|
|
629
|
+
new.from_alias_name = alias
|
|
630
|
+
return new
|
|
594
631
|
|
|
595
632
|
def only(self, *fields: str) -> QueryBuilder[ModelT]:
|
|
596
633
|
"""Load only these columns. Unlisted columns on the model instance will be None.
|
|
@@ -599,9 +636,10 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
599
636
|
|
|
600
637
|
await User.filter(is_active=True).only("id", "email").all()
|
|
601
638
|
"""
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
639
|
+
new = self._clone()
|
|
640
|
+
new.only_fields = set(fields)
|
|
641
|
+
new.select_fields = list(fields)
|
|
642
|
+
return new
|
|
605
643
|
|
|
606
644
|
def defer(self, *fields: str) -> QueryBuilder[ModelT]:
|
|
607
645
|
"""Exclude these columns from the SELECT. They will be None on the returned instances.
|
|
@@ -610,8 +648,9 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
610
648
|
|
|
611
649
|
await User.filter(is_active=True).defer("large_blob", "heavy_json").all()
|
|
612
650
|
"""
|
|
613
|
-
|
|
614
|
-
|
|
651
|
+
new = self._clone()
|
|
652
|
+
new.deferred_fields = set(fields)
|
|
653
|
+
return new
|
|
615
654
|
|
|
616
655
|
def with_cte(self, *ctes: Any) -> QueryBuilder[ModelT]:
|
|
617
656
|
"""Prepend one or more CTEs (WITH clauses) to the query.
|
|
@@ -627,8 +666,9 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
627
666
|
tree = CTE("org_tree", raw_recursive_sql, recursive=True)
|
|
628
667
|
rows = await Org.filter().with_cte(tree).raw_sql(\"SELECT * FROM org_tree\")
|
|
629
668
|
"""
|
|
630
|
-
self.
|
|
631
|
-
|
|
669
|
+
new = self._clone()
|
|
670
|
+
new.ctes.extend(ctes)
|
|
671
|
+
return new
|
|
632
672
|
|
|
633
673
|
def nearest(
|
|
634
674
|
self,
|
|
@@ -665,20 +705,22 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
665
705
|
for doc in results:
|
|
666
706
|
print(doc._vector_distance, doc.title)
|
|
667
707
|
"""
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
708
|
+
new = self._clone()
|
|
709
|
+
new.vector_order = {"column": column, "vector": vector, "metric": metric}
|
|
710
|
+
new.vector_null_guard = null_guard
|
|
711
|
+
return new
|
|
671
712
|
|
|
672
713
|
def reverse(self) -> QueryBuilder[ModelT]:
|
|
673
714
|
"""Reverse each ordering term."""
|
|
715
|
+
new = self._clone()
|
|
674
716
|
reversed_fields = []
|
|
675
|
-
for term in
|
|
717
|
+
for term in new.order_by_fields:
|
|
676
718
|
if isinstance(term, str):
|
|
677
719
|
reversed_fields.append(term[1:] if term.startswith("-") else f"-{term}")
|
|
678
720
|
else:
|
|
679
721
|
reversed_fields.append(term)
|
|
680
|
-
|
|
681
|
-
return
|
|
722
|
+
new.order_by_fields = reversed_fields
|
|
723
|
+
return new
|
|
682
724
|
|
|
683
725
|
# ------------------------------------------------------------------
|
|
684
726
|
# Internal query dict construction
|
|
@@ -814,8 +856,7 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
814
856
|
"""Order by primary key (all columns, for composite PKs) descending
|
|
815
857
|
and fetch the first record."""
|
|
816
858
|
pk_names = self.model._meta.primary_keys or ["id"]
|
|
817
|
-
self.order_by(*(f"-{pk}" for pk in pk_names))
|
|
818
|
-
return await self.first()
|
|
859
|
+
return await self.order_by(*(f"-{pk}" for pk in pk_names)).first()
|
|
819
860
|
|
|
820
861
|
async def get(self) -> ModelT:
|
|
821
862
|
"""Retrieve exactly one matching record; raises if 0 or >1 found.
|
|
@@ -917,11 +958,14 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
917
958
|
total=Sum("amount"), avg=Avg("amount"), count=Count("id")
|
|
918
959
|
)
|
|
919
960
|
"""
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
961
|
+
# Terminal, but takes query-shaping args — build on a clone so `self`
|
|
962
|
+
# is never mutated (clone-on-chain contract: a reused base builder must
|
|
963
|
+
# stay pristine after a derived .aggregate() call).
|
|
964
|
+
target = self._clone() if kwargs else self
|
|
965
|
+
for key, value in kwargs.items():
|
|
966
|
+
target.aggregations.append({"name": key, "function": value})
|
|
923
967
|
try:
|
|
924
|
-
return await
|
|
968
|
+
return await target._get_executor().aggregate()
|
|
925
969
|
except ORMException as e:
|
|
926
970
|
e.enrich(
|
|
927
971
|
model=self.model, operation="aggregate", filters=self._merge_filters_excludes()
|
|
@@ -929,9 +973,10 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
929
973
|
raise
|
|
930
974
|
|
|
931
975
|
async def values(self, *fields: str) -> list[dict[str, Any]]:
|
|
976
|
+
target = self._clone() if fields else self
|
|
932
977
|
if fields:
|
|
933
|
-
|
|
934
|
-
return await
|
|
978
|
+
target.select_fields = list(fields)
|
|
979
|
+
return await target._get_executor().values()
|
|
935
980
|
|
|
936
981
|
def compile(self, *fields: str) -> tuple[str, list[Any]]:
|
|
937
982
|
"""Build the SELECT SQL + bound-parameter list WITHOUT executing.
|
|
@@ -950,9 +995,10 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
950
995
|
await conn.execute("SET LOCAL hnsw.iterative_scan = 'relaxed_order'")
|
|
951
996
|
rows = await conn.fetch(sql, *params)
|
|
952
997
|
"""
|
|
998
|
+
target = self._clone() if fields else self
|
|
953
999
|
if fields:
|
|
954
|
-
|
|
955
|
-
return
|
|
1000
|
+
target.select_fields = list(fields)
|
|
1001
|
+
return target._get_executor()._build_query()
|
|
956
1002
|
|
|
957
1003
|
async def fetch_via(
|
|
958
1004
|
self,
|
|
@@ -1000,14 +1046,47 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
1000
1046
|
async def values_list(
|
|
1001
1047
|
self, *fields: str, flat: bool = False
|
|
1002
1048
|
) -> list[tuple[Any, ...]] | list[Any]:
|
|
1049
|
+
target = self._clone() if fields else self
|
|
1003
1050
|
if fields:
|
|
1004
|
-
|
|
1005
|
-
return await
|
|
1051
|
+
target.select_fields = list(fields)
|
|
1052
|
+
return await target._get_executor().values_list(flat=flat)
|
|
1006
1053
|
|
|
1007
1054
|
# ------------------------------------------------------------------
|
|
1008
1055
|
# Synchronous wrappers
|
|
1009
1056
|
# ------------------------------------------------------------------
|
|
1010
1057
|
|
|
1058
|
+
def all_sync(self) -> list[ModelT]:
|
|
1059
|
+
"""Synchronous execution of the built query. Sync contexts only."""
|
|
1060
|
+
import asyncio
|
|
1061
|
+
|
|
1062
|
+
from matrx_orm.core.async_db_manager import run_sync
|
|
1063
|
+
|
|
1064
|
+
try:
|
|
1065
|
+
asyncio.get_running_loop()
|
|
1066
|
+
raise RuntimeError(
|
|
1067
|
+
"QueryBuilder.all_sync() called in an async context. Use await .all() instead."
|
|
1068
|
+
)
|
|
1069
|
+
except RuntimeError as e:
|
|
1070
|
+
if "no running event loop" not in str(e):
|
|
1071
|
+
raise
|
|
1072
|
+
return run_sync(self.all())
|
|
1073
|
+
|
|
1074
|
+
def first_sync(self) -> ModelT | None:
|
|
1075
|
+
"""Synchronous twin of first(). Sync contexts only."""
|
|
1076
|
+
import asyncio
|
|
1077
|
+
|
|
1078
|
+
from matrx_orm.core.async_db_manager import run_sync
|
|
1079
|
+
|
|
1080
|
+
try:
|
|
1081
|
+
asyncio.get_running_loop()
|
|
1082
|
+
raise RuntimeError(
|
|
1083
|
+
"QueryBuilder.first_sync() called in an async context. Use await .first() instead."
|
|
1084
|
+
)
|
|
1085
|
+
except RuntimeError as e:
|
|
1086
|
+
if "no running event loop" not in str(e):
|
|
1087
|
+
raise
|
|
1088
|
+
return run_sync(self.first())
|
|
1089
|
+
|
|
1011
1090
|
def values_sync(self, *fields: str) -> list[dict[str, Any]]:
|
|
1012
1091
|
"""Synchronous wrapper for values()."""
|
|
1013
1092
|
try:
|
|
@@ -1047,8 +1126,9 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
1047
1126
|
if isinstance(k, slice):
|
|
1048
1127
|
start = k.start or 0
|
|
1049
1128
|
stop = k.stop if k.stop is not None else 0
|
|
1129
|
+
new = self
|
|
1050
1130
|
if stop > 0:
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
return
|
|
1131
|
+
new = new.limit(stop - start)
|
|
1132
|
+
new = new.offset(start)
|
|
1133
|
+
return new
|
|
1054
1134
|
raise TypeError("Index access not supported, use slicing")
|