matrx-orm 3.1.200__tar.gz → 3.1.201__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.200 → matrx_orm-3.1.201}/CHANGELOG.md +5 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/PKG-INFO +1 -1
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/FEATURE.md +30 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/async_db_manager.py +10 -0
- matrx_orm-3.1.201/matrx_orm/core/read_write.py +221 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/pytest_lock_guard.py +12 -4
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/pyproject.toml +1 -1
- matrx_orm-3.1.201/tests/level1/test_read_write_declared.py +108 -0
- matrx_orm-3.1.201/tests/test_a_backend_handed_over_mid_transaction_never_takes_our_writes.py +311 -0
- matrx_orm-3.1.201/tests/test_a_read_only_backend_never_refuses_our_writes.py +187 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/.gitignore +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/AGENTS.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/BASE_CLASS_METHODS.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/CLAUDE.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/MODEL_API.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/docs/migrations.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/FEATURE.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/adapters/MODULE_README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/admin/FEATURE.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/admin/router.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/catalog.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/catalog_sql.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/admin_lane.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/base.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/config.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/connections.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/db_function.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/expressions.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/fields.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/introspection.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/one_database.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/pool_maintenance.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/pool_watch.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/pooler_role.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/registry.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/resilience.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/rls_session.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/session_context.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/statement_ceiling.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/tls.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/transaction.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/core/write_retry.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/identity_rule.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/local_sqlite.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/migrations/diff.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/no_org_assignment.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/FEATURE.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/catalog_select.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/db_functions.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/dynamic_crud.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/graph_walk.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/latest_rows.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/relational_backfill.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/staging_load.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/operations/update.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/platform_access.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/platform_db.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/pytest_live_db_guard.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/pytest_plugin.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/python_sql/db_objects.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/query/FEATURE.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/query/builder.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/query/executor.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/resources/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/resources/additional-assigners-2026-09-12.json +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/resources/org_assignment_trigger_baseline.json +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/FEATURE.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/code_handler.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/codegen_writer.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/FEATURE.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/attachments.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/backup_restore.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/crypto.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/host_authorization.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/items.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/model.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/mutation_receipts.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/native_enable.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/native_lifecycle.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/native_passkeys.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/native_source.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/org_service.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/org_vault_gate.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/service.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/vault_history.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/session/README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/session/coalesce.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/session/fallback.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/session/flush.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/session/lifecycle.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/session/managed.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/session/op.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/session/session.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/state.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/matrx_orm/write_actor.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/scripts/release.sh +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/conftest.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_adapter_registry.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_admin_db_columns.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_admin_response_rendering.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_admin_search_negation.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_association_trigger_actor_naming.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_associations_wiring.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_bulk_hydration_offload.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_case_when.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_catalog.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_column_scoped_writes.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_committed_row_guard.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_composite_pk_write_paths.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_connection_primitives.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_count_limit_param_leak.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_deadlock_retry.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_derived_table.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_dynamic_crud.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_expression_primitives.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_fk_cycle_resolution.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_from_alias.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_graph_walk.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_introspect_rls_policies.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_join_and_aggregate.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_latest_rows.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_local_sqlite.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_lock_contention_retry.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_managed_write_guard.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_optimistic_concurrency.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_org_assignment_inventory.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_orm_banners_are_client_safe.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_parameter_name_hydration.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_percentile_cont.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_persisted_count.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_person_scoped_session_bypasses_shared_cache.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_pool_create_never_wedges.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_pool_holder_census.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_pool_loop_recreation.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_pool_maintenance.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_pydantic_bridge.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_query_builder.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_query_timeout.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_read_connection_retry.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_registry.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_relational_backfill.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_resilience.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_rls_session_scope.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_schema_exists.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_standalone_write_session_context.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_subquery_nested_expressions.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_tls.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_transaction_database_identity.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_transaction_gucs_one_round_trip.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_transaction_pool_release_sends_nothing.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_typed_join_projection.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_typed_predicates.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_update_with_rebase.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_upsert_default_leak.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_watched_lifecycle.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_write_queue_deadline.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level2/test_migrations_live.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/level2/verify_committed_row_guard_pg17.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/live_pooler_role.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/native_source_cases.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/native_source_oracle.rs +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema/test_composite_unique_generation.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema/test_dto_identity_generation.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema/test_entity_capability_generation.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema/test_enum_ordering.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema/test_junction_analysis.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema/test_schema_analysis.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema/test_sql_expression_defaults.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema/test_table_relationships_query.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema/test_view_generation.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema_builder/test_array_default_parsing.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema_builder/test_focused_generation_plan.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema_builder/test_host_models_aggregator.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/schema_builder/test_never_filter_columns.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/test_a_failed_capture_still_names_the_failure.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/test_cache_no_downgrade.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/test_coalesce.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/test_dag.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/test_disk_spill_fallback.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/test_flush_individual_fallback.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/test_governed_write.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/test_live_db_guard_plugin.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/test_record_error_bounded_insert.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/test_record_error_storm.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/session/test_spill_guard_plugin.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_array_arg_element_type.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_attachment_mutation_serialization.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_backup_alias_conflict.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_backup_history_restore.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_backup_restore_live.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_call_function_as.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_call_function_none_mode.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_composite_fk_is_not_a_column_fk.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_db_functions.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_dsn_password_masking.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_dynamic_select_unregistered.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_immutable_insert_or_exact_verify.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_keyed_digest.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_native_enable.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_native_lifecycle.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_native_lifecycle_fences.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_native_passkey_receipts.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_native_source.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_no_blocking_asyncpg_tls.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_one_database.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_order_by_desc_nulls_last.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_org_vault_gate.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_pooler_role_guard.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_secret_audit_names_its_organization.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_secrets_battery.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_secrets_minting_keys.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_shared_lock_guard.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_sql_param_casts.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_statement_ceiling_warning.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_sync_twins_guard.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_update_result_truthiness.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_user_visible_lookup.py +0 -0
- {matrx_orm-3.1.200 → matrx_orm-3.1.201}/tests/test_vault_aggregate_recovery_stateful.py +0 -0
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- **Fix: the shared-lock sampler no longer leaves its timeouts on a pooled backend.**
|
|
6
|
+
`pytest_lock_guard` sent session-level `set lock_timeout` / `set statement_timeout`
|
|
7
|
+
through the transaction pooler, where they stay on the backend for the next client
|
|
8
|
+
(both found leaked on the clone's pool, 2026-10-01). It now uses `SET LOCAL` inside
|
|
9
|
+
each sample's own transaction.
|
|
5
10
|
- **Fix: a driver timeout longer than the server's statement ceiling screams.**
|
|
6
11
|
`call_function` / `call_function_as` / `execute_query` read the connection's
|
|
7
12
|
effective `statement_timeout` and log one WARNING per call site when the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: matrx-orm
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.201
|
|
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
|
|
@@ -282,6 +282,34 @@ trip from a laptop): 435 → ~200 round trips. Pinned by
|
|
|
282
282
|
`tests/level1/test_transaction_pool_release_sends_nothing.py` and
|
|
283
283
|
`tests/level1/test_rls_session_scope.py`.
|
|
284
284
|
|
|
285
|
+
**A backend another client left read-only never refuses our writes.** A transaction
|
|
286
|
+
pooler shares server connections with every client of the same login and never resets
|
|
287
|
+
one between clients, so a session-level `SET default_transaction_read_only = on` (or
|
|
288
|
+
`SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY`) sent outside a transaction — the
|
|
289
|
+
shape of an ad-hoc "read-only production" helper — leaves that backend read-only for
|
|
290
|
+
everyone (2026-10-01 incident: dispatchers, workflow claims, request audit, chat writes;
|
|
291
|
+
recurring since 2026-09-12). A transaction-mode pool's connections are
|
|
292
|
+
`ReadWriteConnection` (`read_write.py`): every top-level BEGIN naming no access mode is
|
|
293
|
+
sent as `... READ WRITE` (zero extra round trips), and an autocommit statement refused as
|
|
294
|
+
read-only re-runs once inside `BEGIN READ WRITE` with a WARNING naming the cause. An
|
|
295
|
+
explicit `READ ONLY` is kept; the session is never rewritten. Read-only work through the
|
|
296
|
+
pooler belongs in `BEGIN READ ONLY`, never a session SET. Pinned by
|
|
297
|
+
`tests/test_a_read_only_backend_never_refuses_our_writes.py` (loopback `ISOLATED_PG17_DSN`)
|
|
298
|
+
and `tests/level1/test_read_write_declared.py`.
|
|
299
|
+
|
|
300
|
+
**A backend handed over inside another client's transaction never takes our writes.** The
|
|
301
|
+
second shape of the same incident: a probe sent `begin read only; …; rollback;` through the
|
|
302
|
+
pooler, an error or timeout skipped the `rollback`, and the pooler handed the open READ ONLY
|
|
303
|
+
transaction to the server. The pooler answers our handshake "idle", so asyncpg learns of it
|
|
304
|
+
only from the reply. A statement that comes back inside a transaction this connection never
|
|
305
|
+
opened is the stranger's: rolled back, logged at ERROR with the remedy, re-run once in our own
|
|
306
|
+
`BEGIN READ WRITE … COMMIT` (the rollback undid it, so exactly once). A top-level BEGIN answered
|
|
307
|
+
with 25001 is the same: rolled back and begun again; a second stranger raises
|
|
308
|
+
`InheritedTransactionError` by name. Pinned by
|
|
309
|
+
`tests/test_a_backend_handed_over_mid_transaction_never_takes_our_writes.py`, which runs an
|
|
310
|
+
in-process transaction pooler that never resets its one server connection (loopback
|
|
311
|
+
`ISOLATED_PG17_DSN`; 4 of 5 fail on the code before it).
|
|
312
|
+
|
|
285
313
|
**The destructive live proof is loopback-only.**
|
|
286
314
|
`tests/live_pooler_role.py::assert_isolated_pooler_target` runs before DSN
|
|
287
315
|
construction or connection and refuses every shared or remote database. The proof
|
|
@@ -360,6 +388,8 @@ can never disagree about who the blocker was.
|
|
|
360
388
|
Verified against the live database: 2026-08-21.
|
|
361
389
|
|
|
362
390
|
## Change log
|
|
391
|
+
- 2026-10-01: `ReadWriteConnection` rolls back a stranger's open transaction it was handed (statement reply or 25001 on BEGIN), re-runs our statement in its own transaction, and names a repeat `InheritedTransactionError`.
|
|
392
|
+
- 2026-10-01: `ReadWriteConnection` for transaction-mode pools — every BEGIN declares READ WRITE and a read-only-refused autocommit statement re-runs once in one; a pooled backend another client left read-only at session level no longer fails the platform's writes.
|
|
363
393
|
- 2026-09-30: Fewer round trips per request (agent-launch latency, lane deep): one-statement proof of a clean empty read; ceilings + declared context + `rls_session` settings in ONE BEGIN-time `set_config` statement (`transaction(opening_gucs=)`, `set_local_gucs`, `emit_local_gucs`); transaction-mode pools release with no reset statement. 435 → ~200 round trips on a flashcard launch.
|
|
364
394
|
- 2026-09-30: `statement_ceiling.py` — a driver timeout above the server's statement ceiling is logged once per call site; `SERVER_CEILING` sentinel. ~19 callers passed 60-600 s under the 30 s `postgres` ceiling (the nightly keyword classifier was cancelled at 30 s for eight days).
|
|
365
395
|
- 2026-09-12 (later): ONE retry mechanism. Two independent lock-retry
|
|
@@ -25,6 +25,7 @@ from matrx_orm.core.pooler_role import (
|
|
|
25
25
|
reprove_empty_read,
|
|
26
26
|
)
|
|
27
27
|
from matrx_orm.core.statement_ceiling import resolve_driver_timeout
|
|
28
|
+
from matrx_orm.core.read_write import ReadWriteConnection
|
|
28
29
|
from matrx_orm.core.tls import resolve_asyncpg_ssl
|
|
29
30
|
from matrx_orm.exceptions import (
|
|
30
31
|
AdapterError,
|
|
@@ -693,6 +694,15 @@ class AsyncDatabaseManager:
|
|
|
693
694
|
if effective_mode == "transaction"
|
|
694
695
|
else None
|
|
695
696
|
),
|
|
697
|
+
# A transaction pooler hands us server connections other
|
|
698
|
+
# clients may have left read-only at SESSION level; every
|
|
699
|
+
# BEGIN declares READ WRITE and a refused autocommit
|
|
700
|
+
# statement re-runs once in one (core/read_write.py).
|
|
701
|
+
connection_class=(
|
|
702
|
+
ReadWriteConnection
|
|
703
|
+
if effective_mode == "transaction"
|
|
704
|
+
else asyncpg.Connection
|
|
705
|
+
),
|
|
696
706
|
)
|
|
697
707
|
try:
|
|
698
708
|
async with asyncio.timeout(create_timeout):
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""A pooled backend another client left read-only never refuses our writes.
|
|
2
|
+
|
|
3
|
+
A transaction-mode pooler (Supavisor / PgBouncer, port 6543) shares its server
|
|
4
|
+
connections between EVERY client that logs in as the same user, and never resets
|
|
5
|
+
one between clients. A client that runs a session-level read-only setting outside a
|
|
6
|
+
transaction — ``SET default_transaction_read_only = on``,
|
|
7
|
+
``SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY`` (the shape of an ad-hoc
|
|
8
|
+
"read-only production" helper) — leaves it on whichever server connection the pooler
|
|
9
|
+
handed it. Every later transaction routed there starts read-only, and every write
|
|
10
|
+
fails with ``cannot execute ... in a read-only transaction`` while the database is
|
|
11
|
+
writable. (2026-10-01: SMS and notification dispatchers, workflow claims, the request
|
|
12
|
+
audit and chat writes; recurring since at least 2026-09-12. Proven live: one of 21
|
|
13
|
+
pooled backends carried ``default_transaction_read_only = on``, ``source = session``.)
|
|
14
|
+
|
|
15
|
+
``ReadWriteConnection`` is the connection class of every transaction-mode pool:
|
|
16
|
+
|
|
17
|
+
* every top-level ``BEGIN`` that names no access mode is sent as ``BEGIN ... READ
|
|
18
|
+
WRITE`` — the ORM's own ``transaction()`` and any raw ``conn.transaction()``
|
|
19
|
+
alike, at no extra round trip. An explicit ``READ ONLY`` is kept.
|
|
20
|
+
* an AUTOCOMMIT statement the backend refuses as read-only (SQLSTATE 25006) is re-run
|
|
21
|
+
once inside one explicit ``BEGIN READ WRITE`` transaction, and the event is logged
|
|
22
|
+
with its remedy. The refusal proves the statement never ran: Postgres checks the
|
|
23
|
+
access mode before a statement modifies anything, and an implicit transaction
|
|
24
|
+
rolls back whole.
|
|
25
|
+
|
|
26
|
+
THE SECOND SHAPE — a server connection handed over INSIDE another client's transaction
|
|
27
|
+
(2026-10-01: live probes sent ``begin read only; …; rollback;`` as one string through the
|
|
28
|
+
pooler; an error or timeout skipped the ``rollback`` and the open READ ONLY transaction went to
|
|
29
|
+
the platform server). A transaction pooler answers our handshake with "idle" itself, so asyncpg
|
|
30
|
+
only learns of the stranger's transaction from the reply to our next statement:
|
|
31
|
+
|
|
32
|
+
* a statement that comes back inside a transaction this connection never opened (asyncpg's
|
|
33
|
+
``_top_xact`` unset, no BEGIN of ours open) ran inside a STRANGER's transaction: that
|
|
34
|
+
transaction is rolled back, logged at ERROR with its remedy, and the statement re-runs once in
|
|
35
|
+
our own ``BEGIN READ WRITE … COMMIT``. The rollback undid whatever the statement did, so the
|
|
36
|
+
re-run is exactly once.
|
|
37
|
+
* a top-level ``BEGIN`` the server answers with SQLSTATE 25001 ("there is already a transaction
|
|
38
|
+
in progress" / "read-write mode must be set before any query") is the same stranger: rolled
|
|
39
|
+
back, begun again once; a second stranger raises ``InheritedTransactionError`` by name —
|
|
40
|
+
never a bare 25006 three statements later.
|
|
41
|
+
|
|
42
|
+
It never touches the session it was handed — the poison stays visible to whoever
|
|
43
|
+
investigates (``SHOW default_transaction_read_only``) and the guard stays per
|
|
44
|
+
transaction. Session-mode pools keep the plain asyncpg connection: their server
|
|
45
|
+
connection is ours alone and asyncpg's release reset clears it.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
from __future__ import annotations
|
|
49
|
+
|
|
50
|
+
import logging
|
|
51
|
+
import re
|
|
52
|
+
from collections.abc import Awaitable, Callable
|
|
53
|
+
from typing import Any
|
|
54
|
+
|
|
55
|
+
import asyncpg
|
|
56
|
+
|
|
57
|
+
logger = logging.getLogger(__name__)
|
|
58
|
+
|
|
59
|
+
# One statement that opens a top-level transaction: BEGIN / BEGIN WORK / BEGIN
|
|
60
|
+
# TRANSACTION / START TRANSACTION, optionally with modes and one trailing ';'.
|
|
61
|
+
_BEGIN_STATEMENT = re.compile(
|
|
62
|
+
r"^\s*(?:BEGIN|START\s+TRANSACTION)\b(?P<modes>[^;]*?)\s*;?\s*$",
|
|
63
|
+
re.IGNORECASE | re.DOTALL,
|
|
64
|
+
)
|
|
65
|
+
_ACCESS_MODE = re.compile(r"\bREAD\s+(?:ONLY|WRITE)\b", re.IGNORECASE)
|
|
66
|
+
# A statement that ends the top-level transaction (not ROLLBACK TO / RELEASE a savepoint).
|
|
67
|
+
_END_STATEMENT = re.compile(
|
|
68
|
+
r"^\s*(?:COMMIT|END|ABORT|ROLLBACK)\b(?!\s+(?:TO|PREPARED)\b)(?!\s+(?:WORK|TRANSACTION)\s+TO\b)",
|
|
69
|
+
re.IGNORECASE,
|
|
70
|
+
)
|
|
71
|
+
_ACTIVE_SQL_TRANSACTION = "25001"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def declare_read_write(query: str) -> str:
|
|
75
|
+
"""``query`` with ``READ WRITE`` declared when it is a BEGIN naming no access mode.
|
|
76
|
+
|
|
77
|
+
Anything else — a savepoint, a multi-statement script, a BEGIN that already says
|
|
78
|
+
``READ ONLY`` or ``READ WRITE`` — is returned unchanged.
|
|
79
|
+
"""
|
|
80
|
+
match = _BEGIN_STATEMENT.match(query)
|
|
81
|
+
if match is None or _ACCESS_MODE.search(match.group("modes")):
|
|
82
|
+
return query
|
|
83
|
+
return query.strip().rstrip(";").rstrip() + " READ WRITE"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
_REFUSED_REMEDY = (
|
|
87
|
+
"[matrx_orm] A pooled server connection refused an autocommit statement as "
|
|
88
|
+
"read-only: another client of the transaction pooler left a SESSION-level "
|
|
89
|
+
"read-only default on it (SET default_transaction_read_only / SET SESSION "
|
|
90
|
+
"CHARACTERISTICS AS TRANSACTION READ ONLY sent outside a transaction). The "
|
|
91
|
+
"statement never ran and was re-run inside BEGIN READ WRITE. Remedy: find the "
|
|
92
|
+
"client sending that SET through the pooler — read-only work belongs in "
|
|
93
|
+
"BEGIN READ ONLY, one transaction at a time. Statement: %s"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
_STRANGER_REMEDY = (
|
|
98
|
+
"[matrx_orm] The pooler handed this connection a server connection still inside "
|
|
99
|
+
"another client's open transaction (a client of the transaction pooler left without "
|
|
100
|
+
"COMMIT/ROLLBACK, e.g. a 'begin read only; ...; rollback;' probe that errored or timed "
|
|
101
|
+
"out). That transaction was rolled back and our statement re-runs in its own "
|
|
102
|
+
"transaction. Remedy: find the client — probes belong on a DIRECT connection (:5432) "
|
|
103
|
+
"with ROLLBACK in a finally. Statement: %s"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class InheritedTransactionError(RuntimeError):
|
|
108
|
+
"""Every server connection the pooler offered was inside another client's transaction."""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ReadWriteConnection(asyncpg.Connection): # type: ignore[misc]
|
|
112
|
+
"""The connection class of a transaction-mode pool (see module docstring)."""
|
|
113
|
+
|
|
114
|
+
_matrx_own_xact = False # a top-level BEGIN of ours is open
|
|
115
|
+
_matrx_saw_25001 = False # the server warned "already a transaction in progress"
|
|
116
|
+
|
|
117
|
+
def _process_log_message(self, fields: Any, last_query: Any) -> None:
|
|
118
|
+
if fields.get("C") == _ACTIVE_SQL_TRANSACTION:
|
|
119
|
+
self._matrx_saw_25001 = True
|
|
120
|
+
super()._process_log_message(fields, last_query)
|
|
121
|
+
|
|
122
|
+
async def _reset(self) -> None:
|
|
123
|
+
self._matrx_own_xact = False
|
|
124
|
+
await super()._reset()
|
|
125
|
+
|
|
126
|
+
def _in_strangers_transaction(self) -> bool:
|
|
127
|
+
return self.is_in_transaction() and self._top_xact is None and not self._matrx_own_xact
|
|
128
|
+
|
|
129
|
+
async def _drop_strangers_transaction(self, query: str) -> None:
|
|
130
|
+
logger.error(_STRANGER_REMEDY, " ".join(str(query).split())[:160])
|
|
131
|
+
self._matrx_own_xact = False
|
|
132
|
+
await asyncpg.Connection.execute(self, "ROLLBACK")
|
|
133
|
+
|
|
134
|
+
async def _begin(self, query: str) -> str:
|
|
135
|
+
"""A top-level BEGIN that never silently joins a stranger's transaction."""
|
|
136
|
+
if self._in_strangers_transaction():
|
|
137
|
+
await self._drop_strangers_transaction(query)
|
|
138
|
+
for _ in range(2):
|
|
139
|
+
self._matrx_saw_25001 = False
|
|
140
|
+
try:
|
|
141
|
+
result = await asyncpg.Connection.execute(self, query)
|
|
142
|
+
except asyncpg.exceptions.ActiveSQLTransactionError:
|
|
143
|
+
pass
|
|
144
|
+
else:
|
|
145
|
+
if not self._matrx_saw_25001:
|
|
146
|
+
self._matrx_own_xact = True
|
|
147
|
+
return result
|
|
148
|
+
await self._drop_strangers_transaction(query)
|
|
149
|
+
raise InheritedTransactionError(
|
|
150
|
+
"Two server connections in a row were inside another client's open transaction; "
|
|
151
|
+
"nothing was written. A client of the transaction pooler is leaving transactions "
|
|
152
|
+
"open (see matrx_orm/core/read_write.py)."
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
async def _in_own_transaction(
|
|
156
|
+
self, call: Callable[..., Awaitable[Any]], query: str, args: tuple[Any, ...], kwargs: dict[str, Any]
|
|
157
|
+
) -> Any:
|
|
158
|
+
await self._begin("BEGIN READ WRITE")
|
|
159
|
+
try:
|
|
160
|
+
result = await call(query, *args, **kwargs)
|
|
161
|
+
except BaseException:
|
|
162
|
+
self._matrx_own_xact = False
|
|
163
|
+
try:
|
|
164
|
+
await asyncpg.Connection.execute(self, "ROLLBACK")
|
|
165
|
+
except Exception: # noqa: BLE001 — the pool's release rolls back what this cannot
|
|
166
|
+
pass
|
|
167
|
+
raise
|
|
168
|
+
self._matrx_own_xact = False
|
|
169
|
+
await asyncpg.Connection.execute(self, "COMMIT")
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
async def _healed(
|
|
173
|
+
self, call: Callable[..., Awaitable[Any]], query: str, args: tuple[Any, ...], kwargs: dict[str, Any]
|
|
174
|
+
) -> Any:
|
|
175
|
+
if self._in_strangers_transaction():
|
|
176
|
+
await self._drop_strangers_transaction(query)
|
|
177
|
+
if self.is_in_transaction():
|
|
178
|
+
result = await call(query, *args, **kwargs)
|
|
179
|
+
if self._top_xact is None and _END_STATEMENT.match(str(query)):
|
|
180
|
+
self._matrx_own_xact = False
|
|
181
|
+
return result
|
|
182
|
+
try:
|
|
183
|
+
result = await call(query, *args, **kwargs)
|
|
184
|
+
except asyncpg.exceptions.ReadOnlySQLTransactionError:
|
|
185
|
+
if self.is_in_transaction():
|
|
186
|
+
await self._drop_strangers_transaction(query)
|
|
187
|
+
else:
|
|
188
|
+
logger.warning(_REFUSED_REMEDY, " ".join(str(query).split())[:160])
|
|
189
|
+
except Exception:
|
|
190
|
+
if not self._in_strangers_transaction():
|
|
191
|
+
raise
|
|
192
|
+
await self._drop_strangers_transaction(query)
|
|
193
|
+
else:
|
|
194
|
+
if not self._in_strangers_transaction():
|
|
195
|
+
return result
|
|
196
|
+
# The reply came from inside a stranger's transaction; rolling it back undid it.
|
|
197
|
+
await self._drop_strangers_transaction(query)
|
|
198
|
+
return await self._in_own_transaction(call, query, args, kwargs)
|
|
199
|
+
|
|
200
|
+
async def execute(self, query: str, *args: Any, **kwargs: Any) -> str:
|
|
201
|
+
if not args:
|
|
202
|
+
declared = declare_read_write(query)
|
|
203
|
+
if _BEGIN_STATEMENT.match(declared) and not (self.is_in_transaction() and not self._in_strangers_transaction()):
|
|
204
|
+
return await self._begin(declared)
|
|
205
|
+
query = declared
|
|
206
|
+
return await self._healed(super().execute, query, args, kwargs)
|
|
207
|
+
|
|
208
|
+
async def fetch(self, query: str, *args: Any, **kwargs: Any) -> list[Any]:
|
|
209
|
+
return await self._healed(super().fetch, query, args, kwargs)
|
|
210
|
+
|
|
211
|
+
async def fetchrow(self, query: str, *args: Any, **kwargs: Any) -> Any:
|
|
212
|
+
return await self._healed(super().fetchrow, query, args, kwargs)
|
|
213
|
+
|
|
214
|
+
async def fetchval(self, query: str, *args: Any, **kwargs: Any) -> Any:
|
|
215
|
+
return await self._healed(super().fetchval, query, args, kwargs)
|
|
216
|
+
|
|
217
|
+
async def executemany(self, command: str, args: Any, **kwargs: Any) -> None:
|
|
218
|
+
return await self._healed(super().executemany, command, (args,), kwargs)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
__all__ = ["InheritedTransactionError", "ReadWriteConnection", "declare_read_write"]
|
|
@@ -413,13 +413,21 @@ def _run_sampler() -> None: # pragma: no cover — exercised by the guard's own
|
|
|
413
413
|
with psycopg.connect(
|
|
414
414
|
_STATE.dsn, autocommit=True, prepare_threshold=None
|
|
415
415
|
) as conn, conn.cursor() as cur:
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
416
|
+
# The limits are SET LOCAL inside each statement's own transaction, never a session SET:
|
|
417
|
+
# through Supavisor's transaction pooler a session SET stays on the backend and the next
|
|
418
|
+
# client inherits it (lock_timeout=2s / statement_timeout=10s were found leaked on the
|
|
419
|
+
# clone's pool, 2026-10-01 — common-docs v5/PROGRESS-POOLER-LEAK-GUARD.md).
|
|
420
|
+
def limited(fn):
|
|
421
|
+
with conn.transaction():
|
|
422
|
+
cur.execute("set local lock_timeout = '2s'")
|
|
423
|
+
cur.execute("set local statement_timeout = '10s'")
|
|
424
|
+
return fn()
|
|
425
|
+
|
|
426
|
+
_STATE.baseline_oids = limited(lambda: _baseline_oids(cur))
|
|
419
427
|
_STATE.active = True
|
|
420
428
|
while not _STATE.stop.wait(SAMPLE_INTERVAL_SECONDS):
|
|
421
429
|
try:
|
|
422
|
-
found = _sample_once(cur, time.monotonic())
|
|
430
|
+
found = limited(lambda: _sample_once(cur, time.monotonic()))
|
|
423
431
|
except Exception as exc: # noqa: BLE001 — never kill the run over a sample
|
|
424
432
|
log.warning("[%s] sample failed: %s", _PLUGIN_NAME, exc)
|
|
425
433
|
continue
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "matrx-orm"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.201"
|
|
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" }
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""A transaction-mode pool declares READ WRITE on every BEGIN (no database needed).
|
|
2
|
+
|
|
3
|
+
The live half — a backend another client left read-only at session level, written
|
|
4
|
+
through anyway — is ``tests/test_a_read_only_backend_never_refuses_our_writes.py``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import asyncpg
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from matrx_orm.core import async_db_manager as adm
|
|
15
|
+
from matrx_orm.core.async_db_manager import AsyncDatabaseManager
|
|
16
|
+
from matrx_orm.core.config import DatabaseProjectConfig, register_database, registry
|
|
17
|
+
from matrx_orm.core.read_write import ReadWriteConnection, declare_read_write
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.mark.parametrize(
|
|
21
|
+
("sent", "expected"),
|
|
22
|
+
[
|
|
23
|
+
("BEGIN", "BEGIN READ WRITE"),
|
|
24
|
+
("BEGIN;", "BEGIN READ WRITE"),
|
|
25
|
+
("begin", "begin READ WRITE"),
|
|
26
|
+
("BEGIN ISOLATION LEVEL REPEATABLE READ;", "BEGIN ISOLATION LEVEL REPEATABLE READ READ WRITE"),
|
|
27
|
+
("BEGIN ISOLATION LEVEL READ COMMITTED;", "BEGIN ISOLATION LEVEL READ COMMITTED READ WRITE"),
|
|
28
|
+
("START TRANSACTION", "START TRANSACTION READ WRITE"),
|
|
29
|
+
("BEGIN WORK", "BEGIN WORK READ WRITE"),
|
|
30
|
+
],
|
|
31
|
+
)
|
|
32
|
+
def test_a_begin_naming_no_access_mode_declares_read_write(sent: str, expected: str) -> None:
|
|
33
|
+
assert declare_read_write(sent) == expected
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.mark.parametrize(
|
|
37
|
+
"sent",
|
|
38
|
+
[
|
|
39
|
+
"BEGIN READ ONLY;",
|
|
40
|
+
"BEGIN ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE;",
|
|
41
|
+
"begin read only",
|
|
42
|
+
"BEGIN READ WRITE",
|
|
43
|
+
"SAVEPOINT sp_1;",
|
|
44
|
+
"BEGIN; SET LOCAL statement_timeout = 1000",
|
|
45
|
+
"COMMIT;",
|
|
46
|
+
"SELECT 'BEGIN'",
|
|
47
|
+
],
|
|
48
|
+
)
|
|
49
|
+
def test_everything_else_is_sent_unchanged(sent: str) -> None:
|
|
50
|
+
assert declare_read_write(sent) == sent
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class _FakePool:
|
|
54
|
+
def get_size(self) -> int:
|
|
55
|
+
return 1
|
|
56
|
+
|
|
57
|
+
def terminate(self) -> None:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@pytest.mark.asyncio
|
|
62
|
+
@pytest.mark.parametrize(
|
|
63
|
+
("port", "expected"), [(6543, ReadWriteConnection), (5432, asyncpg.Connection)]
|
|
64
|
+
)
|
|
65
|
+
async def test_only_a_transaction_mode_pool_uses_the_read_write_connection(
|
|
66
|
+
monkeypatch, port: int, expected: type
|
|
67
|
+
) -> None:
|
|
68
|
+
seen: dict[str, Any] = {}
|
|
69
|
+
|
|
70
|
+
async def _make(pool: _FakePool) -> _FakePool:
|
|
71
|
+
return pool
|
|
72
|
+
|
|
73
|
+
def fake_create_pool(**kwargs: Any):
|
|
74
|
+
seen.update(kwargs)
|
|
75
|
+
return _make(_FakePool())
|
|
76
|
+
|
|
77
|
+
monkeypatch.setattr(adm.asyncpg, "create_pool", fake_create_pool)
|
|
78
|
+
monkeypatch.setattr(AsyncDatabaseManager, "_start_keepalive", classmethod(lambda *_a: None))
|
|
79
|
+
name = f"read_write_class_{port}"
|
|
80
|
+
registry._configs.pop(name, None)
|
|
81
|
+
register_database(
|
|
82
|
+
DatabaseProjectConfig(
|
|
83
|
+
name=name,
|
|
84
|
+
alias=f"{name}_alias",
|
|
85
|
+
host="127.0.0.1",
|
|
86
|
+
port=str(port),
|
|
87
|
+
database_name="postgres",
|
|
88
|
+
user="postgres",
|
|
89
|
+
password="unused",
|
|
90
|
+
pool_min=1,
|
|
91
|
+
pool_max=2,
|
|
92
|
+
ssl=False,
|
|
93
|
+
write_queue_enabled=False,
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
try:
|
|
97
|
+
await AsyncDatabaseManager.get_pool(name)
|
|
98
|
+
finally:
|
|
99
|
+
AsyncDatabaseManager._pools.pop(name, None)
|
|
100
|
+
AsyncDatabaseManager._pool_loops.pop(name, None)
|
|
101
|
+
AsyncDatabaseManager._locks.pop(name, None)
|
|
102
|
+
AsyncDatabaseManager._growth_locks.pop(name, None)
|
|
103
|
+
task = AsyncDatabaseManager._keepalive_tasks.pop(name, None)
|
|
104
|
+
if task is not None:
|
|
105
|
+
task.cancel()
|
|
106
|
+
registry._configs.pop(name, None)
|
|
107
|
+
|
|
108
|
+
assert seen.get("connection_class") is expected
|