matrx-orm 3.1.80__tar.gz → 3.1.82__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.80 → matrx_orm-3.1.82}/PKG-INFO +1 -1
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/FEATURE.md +11 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/expressions.py +155 -40
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/registry.py +10 -1
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/secrets_battery/FEATURE.md +11 -3
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/secrets_battery/org_service.py +25 -6
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/pyproject.toml +1 -1
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_expression_primitives.py +15 -14
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_registry.py +2 -4
- matrx_orm-3.1.82/tests/level1/test_subquery_nested_expressions.py +167 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_secrets_battery.py +41 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/.gitignore +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/AGENTS.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/BASE_CLASS_METHODS.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/CLAUDE.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/MODEL_API.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/docs/migrations.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/FEATURE.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/adapters/MODULE_README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/adapters/postgresql.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/admin/FEATURE.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/admin/router.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/catalog.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/catalog_sql.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/async_db_manager.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/base.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/config.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/connections.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/db_function.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/fields.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/introspection.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/one_database.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/pool_maintenance.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/pool_watch.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/pooler_role.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/resilience.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/rls_session.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/tls.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/transaction.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/identity_rule.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/local_sqlite.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/diff.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/FEATURE.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/db_functions.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/dynamic_crud.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/graph_walk.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/latest_rows.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/relational_backfill.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/staging_load.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/update.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/platform_db.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/python_sql/db_objects.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/query/FEATURE.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/query/builder.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/query/executor.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/FEATURE.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/code_handler.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/codegen_writer.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/secrets_battery/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/secrets_battery/attachments.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/secrets_battery/crypto.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/secrets_battery/items.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/secrets_battery/model.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/secrets_battery/service.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/coalesce.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/fallback.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/flush.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/lifecycle.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/managed.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/op.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/session.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/state.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/scripts/release.sh +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/conftest.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_admin_db_columns.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_admin_response_rendering.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_admin_search_negation.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_bulk_hydration_offload.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_case_when.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_catalog.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_composite_pk_write_paths.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_connection_primitives.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_derived_table.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_dynamic_crud.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_from_alias.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_graph_walk.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_introspect_rls_policies.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_join_and_aggregate.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_latest_rows.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_local_sqlite.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_managed_write_guard.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_optimistic_concurrency.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_parameter_name_hydration.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_pool_holder_census.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_pool_loop_recreation.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_pool_maintenance.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_pydantic_bridge.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_query_builder.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_query_timeout.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_relational_backfill.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_resilience.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_rls_session_scope.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_schema_exists.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_tls.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_typed_join_projection.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_typed_predicates.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_update_with_rebase.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_upsert_default_leak.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_watched_lifecycle.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_migrations_live.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/live_pooler_role.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_composite_unique_generation.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_dto_identity_generation.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_entity_capability_generation.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_junction_analysis.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_sql_expression_defaults.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_table_relationships_query.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_view_generation.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema_builder/test_array_default_parsing.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema_builder/test_focused_generation_plan.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema_builder/test_host_models_aggregator.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_cache_no_downgrade.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_coalesce.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_dag.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_disk_spill_fallback.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_flush_individual_fallback.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_governed_write.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_db_functions.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_no_blocking_asyncpg_tls.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_one_database.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_pooler_role_guard.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_sql_param_casts.py +0 -0
- {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_sync_twins_guard.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: matrx-orm
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.82
|
|
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
|
|
@@ -25,6 +25,15 @@ guard and ensures diagnostic sinks such as `public.app_log` and
|
|
|
25
25
|
Pinned by `tests/level1/test_model_meta.py` and
|
|
26
26
|
`tests/level1/test_managed_write_guard.py`.
|
|
27
27
|
|
|
28
|
+
**Every registered model is discoverable by its resolved table identity.**
|
|
29
|
+
`ModelRegistry` indexes `_meta.table_name`, including the snake-case default
|
|
30
|
+
derived by `ModelMeta`; `_table_name` is only the compatibility fallback. A
|
|
31
|
+
generated model therefore remains reachable through
|
|
32
|
+
`get_model_by_table_name(schema, table)` even when its class does not repeat the
|
|
33
|
+
resolved table name as a raw attribute.
|
|
34
|
+
|
|
35
|
+
Pinned by `tests/level1/test_registry.py`.
|
|
36
|
+
|
|
28
37
|
## Database execution errors
|
|
29
38
|
|
|
30
39
|
`AsyncDatabaseManager.execute_query` must classify every PostgreSQL integrity
|
|
@@ -119,6 +128,8 @@ Verified against code: 2026-08-17.
|
|
|
119
128
|
|
|
120
129
|
## Change log
|
|
121
130
|
|
|
131
|
+
- 2026-08-18: Indexed ModelMeta-derived table names so generated models remain
|
|
132
|
+
discoverable by exact schema/table identity.
|
|
122
133
|
- 2026-08-18: Preserved successful query results when asyncpg times out while
|
|
123
134
|
resetting and discarding the released connection.
|
|
124
135
|
- 2026-08-18: Let waiters reuse healthy connections released behind a slow
|
|
@@ -810,28 +810,133 @@ class Exists(Predicate):
|
|
|
810
810
|
return f"Exists({self.queryset!r})"
|
|
811
811
|
|
|
812
812
|
|
|
813
|
-
|
|
814
|
-
"""
|
|
813
|
+
_SUBQUERY_COMPARISON_OPERATORS = {
|
|
814
|
+
"eq": "=",
|
|
815
|
+
"ne": "<>",
|
|
816
|
+
"gt": ">",
|
|
817
|
+
"gte": ">=",
|
|
818
|
+
"lt": "<",
|
|
819
|
+
"lte": "<=",
|
|
820
|
+
}
|
|
821
|
+
|
|
815
822
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
823
|
+
def _render_subquery_condition(
|
|
824
|
+
field_name: str,
|
|
825
|
+
operator: str,
|
|
826
|
+
value: Any,
|
|
827
|
+
*,
|
|
828
|
+
current_table: str,
|
|
829
|
+
outer_table: str,
|
|
830
|
+
params: list[Any],
|
|
831
|
+
sub_params: list[Any],
|
|
832
|
+
) -> str:
|
|
833
|
+
"""Render ONE ``WHERE`` condition INSIDE a subquery, expression values included.
|
|
834
|
+
|
|
835
|
+
The inner query of a Subquery/Exists is a real query: its own filters may
|
|
836
|
+
carry a nested ``Subquery``/``Exists``/``Func`` value, exactly like the
|
|
837
|
+
outer builder's ``_build_condition_with_expr`` handles. Before this, the
|
|
838
|
+
inner renderer fell through to ``_build_condition``, which emitted
|
|
839
|
+
``field = ANY($n)`` and bound the *expression object itself* as a
|
|
840
|
+
parameter — asyncpg then failed to encode it at execute time (the
|
|
841
|
+
aidream ``conversations`` search+provider failure).
|
|
842
|
+
|
|
843
|
+
Placeholder numbering: expression values render against ``params +
|
|
844
|
+
sub_params`` so their ``$n`` are already absolute, and the params they
|
|
845
|
+
append are folded back into ``sub_params`` in order.
|
|
819
846
|
"""
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
847
|
+
from matrx_orm.query.executor import _SKIP_PARAM, QueryExecutor
|
|
848
|
+
|
|
849
|
+
if isinstance(value, OuterRef):
|
|
850
|
+
return f"{field_name} = {outer_table}.{value.field_name}"
|
|
851
|
+
|
|
852
|
+
if isinstance(value, Subquery | Exists | Func):
|
|
853
|
+
combined = [*params, *sub_params]
|
|
854
|
+
already_bound = len(combined)
|
|
855
|
+
if isinstance(value, Func):
|
|
856
|
+
expr_sql = value.as_sql(combined)
|
|
857
|
+
sql_operator = _SUBQUERY_COMPARISON_OPERATORS.get(operator)
|
|
858
|
+
if sql_operator is None:
|
|
859
|
+
raise ValueError(
|
|
860
|
+
f"Lookup {operator!r} is not supported with a database expression"
|
|
831
861
|
)
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
862
|
+
condition = f"{field_name} {sql_operator} {expr_sql}"
|
|
863
|
+
elif isinstance(value, Exists):
|
|
864
|
+
condition = value.as_sql(
|
|
865
|
+
combined,
|
|
866
|
+
current_table=current_table,
|
|
867
|
+
outer_table=outer_table,
|
|
868
|
+
)
|
|
869
|
+
else:
|
|
870
|
+
sub_sql = value.as_sql(current_table, combined)
|
|
871
|
+
condition = (
|
|
872
|
+
f"{field_name} IN {sub_sql}"
|
|
873
|
+
if operator == "in"
|
|
874
|
+
else f"{field_name} = {sub_sql}"
|
|
875
|
+
)
|
|
876
|
+
sub_params.extend(combined[already_bound:])
|
|
877
|
+
return condition
|
|
878
|
+
|
|
879
|
+
cond, param = QueryExecutor._build_condition(
|
|
880
|
+
field_name, operator, value, len(params) + len(sub_params) + 1
|
|
881
|
+
)
|
|
882
|
+
if param is not _SKIP_PARAM:
|
|
883
|
+
# json_key with (key, expected) maps to two placeholders.
|
|
884
|
+
if isinstance(param, tuple):
|
|
885
|
+
sub_params.extend(param)
|
|
886
|
+
else:
|
|
887
|
+
sub_params.append(param)
|
|
888
|
+
return cond
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def _render_subquery_q(
|
|
892
|
+
q: Any,
|
|
893
|
+
*,
|
|
894
|
+
current_table: str,
|
|
895
|
+
outer_table: str,
|
|
896
|
+
params: list[Any],
|
|
897
|
+
sub_params: list[Any],
|
|
898
|
+
) -> str:
|
|
899
|
+
"""Render a Q tree (compound / negated included) inside a subquery."""
|
|
900
|
+
if q.is_leaf:
|
|
901
|
+
conditions = []
|
|
902
|
+
for key, value in q.kwargs.items():
|
|
903
|
+
field_name, operator = _parse_lookup_simple(key)
|
|
904
|
+
conditions.append(
|
|
905
|
+
_render_subquery_condition(
|
|
906
|
+
F(field_name).field_name,
|
|
907
|
+
operator,
|
|
908
|
+
value,
|
|
909
|
+
current_table=current_table,
|
|
910
|
+
outer_table=outer_table,
|
|
911
|
+
params=params,
|
|
912
|
+
sub_params=sub_params,
|
|
913
|
+
)
|
|
914
|
+
)
|
|
915
|
+
sql = " AND ".join(c for c in conditions if c)
|
|
916
|
+
else:
|
|
917
|
+
child_sqls = [
|
|
918
|
+
_render_subquery_q(
|
|
919
|
+
child,
|
|
920
|
+
current_table=current_table,
|
|
921
|
+
outer_table=outer_table,
|
|
922
|
+
params=params,
|
|
923
|
+
sub_params=sub_params,
|
|
924
|
+
)
|
|
925
|
+
for child in q.children
|
|
926
|
+
]
|
|
927
|
+
child_sqls = [c for c in child_sqls if c]
|
|
928
|
+
if not child_sqls:
|
|
929
|
+
return ""
|
|
930
|
+
if q.connector not in {"AND", "OR"}:
|
|
931
|
+
raise ValueError(f"Unsupported Q connector: {q.connector!r}")
|
|
932
|
+
sql = (
|
|
933
|
+
child_sqls[0]
|
|
934
|
+
if len(child_sqls) == 1
|
|
935
|
+
else "(" + f" {q.connector} ".join(child_sqls) + ")"
|
|
936
|
+
)
|
|
937
|
+
if sql and q.negated:
|
|
938
|
+
sql = f"NOT ({sql})"
|
|
939
|
+
return sql
|
|
835
940
|
|
|
836
941
|
|
|
837
942
|
def _render_subquery(
|
|
@@ -875,10 +980,11 @@ def _render_subquery(
|
|
|
875
980
|
|
|
876
981
|
# ``_build_query()`` returns ``filters`` as a LIST of dict / Q items (never a
|
|
877
982
|
# dict) — iterating it as a dict silently broke every correlated Subquery/
|
|
878
|
-
# Exists.
|
|
879
|
-
#
|
|
983
|
+
# Exists. Each item is rendered IN ORDER so placeholder numbers and the
|
|
984
|
+
# params appended to ``sub_params`` stay in lockstep. The inner query is a
|
|
985
|
+
# real query: compound/negated Q and nested Subquery/Exists/Func values all
|
|
986
|
+
# render here, rather than raising or binding an expression as a parameter.
|
|
880
987
|
filter_items = query_dict.get("filters", [])
|
|
881
|
-
ordinary_filter_items: list[Any] = []
|
|
882
988
|
current_table = query_dict.get("from_alias") or table
|
|
883
989
|
outer_param_count = len(params)
|
|
884
990
|
for item in filter_items:
|
|
@@ -889,25 +995,34 @@ def _render_subquery(
|
|
|
889
995
|
outer_table=outer_table,
|
|
890
996
|
)
|
|
891
997
|
where_conditions.append(_shift_param_refs(predicate_sql, outer_param_count))
|
|
892
|
-
else:
|
|
893
|
-
ordinary_filter_items.append(item)
|
|
894
|
-
|
|
895
|
-
for key, value in _iter_subquery_conditions(ordinary_filter_items):
|
|
896
|
-
field_name, operator = _parse_lookup_simple(key)
|
|
897
|
-
field_name = F(field_name).field_name
|
|
898
|
-
# Resolve OuterRef → outer_table.field
|
|
899
|
-
if isinstance(value, OuterRef):
|
|
900
|
-
condition = f"{field_name} = {outer_table}.{value.field_name}"
|
|
901
|
-
where_conditions.append(condition)
|
|
902
998
|
continue
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
999
|
+
if isinstance(item, dict):
|
|
1000
|
+
for key, value in item.items():
|
|
1001
|
+
field_name, operator = _parse_lookup_simple(key)
|
|
1002
|
+
where_conditions.append(
|
|
1003
|
+
_render_subquery_condition(
|
|
1004
|
+
F(field_name).field_name,
|
|
1005
|
+
operator,
|
|
1006
|
+
value,
|
|
1007
|
+
current_table=current_table,
|
|
1008
|
+
outer_table=outer_table,
|
|
1009
|
+
params=params,
|
|
1010
|
+
sub_params=sub_params,
|
|
1011
|
+
)
|
|
1012
|
+
)
|
|
1013
|
+
continue
|
|
1014
|
+
if isinstance(item, Q):
|
|
1015
|
+
rendered = _render_subquery_q(
|
|
1016
|
+
item,
|
|
1017
|
+
current_table=current_table,
|
|
1018
|
+
outer_table=outer_table,
|
|
1019
|
+
params=params,
|
|
1020
|
+
sub_params=sub_params,
|
|
1021
|
+
)
|
|
1022
|
+
if rendered:
|
|
1023
|
+
where_conditions.append(rendered)
|
|
1024
|
+
continue
|
|
1025
|
+
raise TypeError(f"Unexpected subquery filter item: {type(item)!r}")
|
|
911
1026
|
|
|
912
1027
|
# Explicit JOINs (``.join()`` on the INNER queryset) — the correlated-rollup
|
|
913
1028
|
# pattern often needs to count/aggregate through a join (e.g. "how many of
|
|
@@ -48,6 +48,15 @@ def _database_of(model_class: type[Model]) -> str:
|
|
|
48
48
|
return raw
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
def _table_of(model_class: type[Model]) -> str | None:
|
|
52
|
+
"""The canonical table name, including ModelMeta-derived defaults."""
|
|
53
|
+
meta = getattr(model_class, "_meta", None)
|
|
54
|
+
table = getattr(meta, "table_name", None) if meta is not None else None
|
|
55
|
+
if not table:
|
|
56
|
+
table = getattr(model_class, "_table_name", None)
|
|
57
|
+
return table or None
|
|
58
|
+
|
|
59
|
+
|
|
51
60
|
class ModelRegistry:
|
|
52
61
|
"""Global model registry keyed on ``(database, schema, class_name)``.
|
|
53
62
|
|
|
@@ -88,7 +97,7 @@ class ModelRegistry:
|
|
|
88
97
|
)
|
|
89
98
|
cls._models[key] = model_class
|
|
90
99
|
cls._by_name.setdefault(name, {})[(database, schema)] = model_class
|
|
91
|
-
table_name =
|
|
100
|
+
table_name = _table_of(model_class)
|
|
92
101
|
if table_name:
|
|
93
102
|
table_key = (database, schema, table_name)
|
|
94
103
|
table_existing = cls._by_table.get(table_key)
|
|
@@ -174,6 +174,8 @@ A restricted secret is readable only by a user holding a `can_use` row in
|
|
|
174
174
|
flag the host computes and passes down. Since Phase 1/2 the ITEM is the sharing
|
|
175
175
|
unit: grants carry `credential_item_id` (field-level `user_secret_id` grants
|
|
176
176
|
remain readable during migration; a CHECK requires at least one target).
|
|
177
|
+
Bulk restricted-org resolution accepts either the canonical item-level grant
|
|
178
|
+
or a legacy field-level grant; new writers create item-level grants only.
|
|
177
179
|
`set_item_grants` takes per-grantee `ItemGrantSpec {user_id, can_use,
|
|
178
180
|
can_manage}` — `can_manage` = edit + reveal-revealable, never
|
|
179
181
|
share/transfer/delete — and writes ITEM-level rows only (exactly one target
|
|
@@ -213,8 +215,9 @@ losing the trail must not also lose the work.
|
|
|
213
215
|
|
|
214
216
|
## Key rotation
|
|
215
217
|
|
|
216
|
-
`scripts/rotate_credentials_key.py` (aidream) re-encrypts every
|
|
217
|
-
key to a NEW one: batched keyset
|
|
218
|
+
`scripts/rotate_credentials_key.py` (aidream) re-encrypts every field and
|
|
219
|
+
protected-file attachment from an OLD key to a NEW one: batched keyset
|
|
220
|
+
pagination, per-row verify-before-commit,
|
|
218
221
|
naturally resumable (already-under-new-key rows are skipped), state-file
|
|
219
222
|
checkpoints with key-fingerprint guards, `--dry-run`, and symmetric rollback
|
|
220
223
|
(re-run with the keys swapped). It builds its Fernets ONLY through this
|
|
@@ -248,7 +251,8 @@ contributed org copies show `out_of_sync` afterward (cosmetic).
|
|
|
248
251
|
resolution — a broken row must not deny every other key.
|
|
249
252
|
|
|
250
253
|
Drift guard: `scripts/validate_secrets_decrypt.py` (loud, non-blocking, in
|
|
251
|
-
`release.sh`) asserts every active
|
|
254
|
+
`release.sh`) asserts every active field and attachment still decrypts with the
|
|
255
|
+
configured key.
|
|
252
256
|
|
|
253
257
|
Verified against live code + the live `users.user_secrets` /
|
|
254
258
|
`users.credential_items` schema 2026-07-23 (item-layer smoke run live: bundle
|
|
@@ -256,6 +260,10 @@ create, reveal, sealed denial, editable gate, rotate, item grants, soft delete).
|
|
|
256
260
|
|
|
257
261
|
## Change Log
|
|
258
262
|
|
|
263
|
+
- **2026-08-18** — Extended key rotation and decrypt-drift validation across
|
|
264
|
+
protected-file attachment ciphertext, with field-and-file regression proof.
|
|
265
|
+
- **2026-08-18** — Made restricted organization bulk resolution honor canonical
|
|
266
|
+
item-level grants while retaining legacy field-grant reads.
|
|
259
267
|
- **2026-08-11** — Added first-class encrypted credential-file attachments with
|
|
260
268
|
parent-owned access/lifecycle, a 25 MiB cap, one-way sealing, audited human
|
|
261
269
|
download, trusted execution resolution, and fork/delete inheritance.
|
|
@@ -524,25 +524,44 @@ async def resolve_org_secrets(
|
|
|
524
524
|
filters["key__isnull"] = False
|
|
525
525
|
if keys:
|
|
526
526
|
filters["key__in"] = keys
|
|
527
|
-
rows = await UserSecret.filter(**filters).values(
|
|
527
|
+
rows = await UserSecret.filter(**filters).values(
|
|
528
|
+
"id", "credential_item_id", "key", "value_encrypted", "access_mode"
|
|
529
|
+
)
|
|
528
530
|
if for_sandbox:
|
|
529
531
|
rows = _keyed_for_sandbox(rows, scope=f"organization {organization_id}")
|
|
530
532
|
if not rows:
|
|
531
533
|
return {}
|
|
532
534
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
+
restricted_rows = [r for r in rows if r.get("access_mode") == "restricted"]
|
|
536
|
+
restricted_ids = [str(r["id"]) for r in restricted_rows]
|
|
537
|
+
restricted_item_ids = sorted(
|
|
538
|
+
{str(r["credential_item_id"]) for r in restricted_rows if r.get("credential_item_id")}
|
|
539
|
+
)
|
|
540
|
+
granted_field_ids: set[str] = set()
|
|
541
|
+
granted_item_ids: set[str] = set()
|
|
535
542
|
if restricted_ids and user_id and not is_org_admin:
|
|
536
|
-
|
|
543
|
+
field_grants = await UserSecretGrant.filter(
|
|
537
544
|
user_secret_id__in=restricted_ids, user_id=user_id, can_use=True
|
|
538
545
|
).values("user_secret_id")
|
|
539
|
-
|
|
546
|
+
granted_field_ids = {str(g["user_secret_id"]) for g in field_grants}
|
|
547
|
+
if restricted_item_ids:
|
|
548
|
+
item_grants = await UserSecretGrant.filter(
|
|
549
|
+
credential_item_id__in=restricted_item_ids,
|
|
550
|
+
user_id=user_id,
|
|
551
|
+
can_use=True,
|
|
552
|
+
).values("credential_item_id")
|
|
553
|
+
granted_item_ids = {str(g["credential_item_id"]) for g in item_grants}
|
|
540
554
|
|
|
541
555
|
resolved: dict[str, str] = {}
|
|
542
556
|
used_ids: list[str] = []
|
|
543
557
|
for row in rows:
|
|
544
558
|
if row.get("access_mode") == "restricted" and not is_org_admin:
|
|
545
|
-
|
|
559
|
+
field_granted = str(row["id"]) in granted_field_ids
|
|
560
|
+
item_granted = (
|
|
561
|
+
bool(row.get("credential_item_id"))
|
|
562
|
+
and str(row["credential_item_id"]) in granted_item_ids
|
|
563
|
+
)
|
|
564
|
+
if not field_granted and not item_granted:
|
|
546
565
|
continue
|
|
547
566
|
try:
|
|
548
567
|
resolved[str(row["key"])] = decrypt_value(row["value_encrypted"])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "matrx-orm"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.82"
|
|
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" }
|
|
@@ -21,7 +21,6 @@ from matrx_orm.core.expressions import (
|
|
|
21
21
|
Q,
|
|
22
22
|
Subquery,
|
|
23
23
|
TsRank,
|
|
24
|
-
_iter_subquery_conditions,
|
|
25
24
|
)
|
|
26
25
|
from matrx_orm.query.executor import QueryExecutor, _render_update_expression
|
|
27
26
|
|
|
@@ -78,22 +77,24 @@ def test_ts_rank_allows_alternate_config():
|
|
|
78
77
|
assert "to_tsvector('spanish', body)" in sql
|
|
79
78
|
|
|
80
79
|
|
|
81
|
-
# ──
|
|
80
|
+
# ── Inner-query filter rendering (dict / leaf Q / compound Q / negated Q) ───
|
|
81
|
+
# The flattener that raised on compound/negated Q was replaced by
|
|
82
|
+
# _render_subquery_q; see test_subquery_nested_expressions.py for the tree
|
|
83
|
+
# cases. These pin the flat shapes at the SQL-text level.
|
|
82
84
|
|
|
83
85
|
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
assert list(_iter_subquery_conditions(items)) == [("a", 1), ("b", 2)]
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def test_iter_subquery_conditions_rejects_compound_q():
|
|
90
|
-
with pytest.raises(NotImplementedError, match="simple keyword"):
|
|
91
|
-
list(_iter_subquery_conditions([Q(a=1) | Q(b=2)]))
|
|
92
|
-
|
|
86
|
+
def test_subquery_renders_dict_and_leaf_q_filters():
|
|
87
|
+
from matrx_orm.core.registry import ModelRegistry
|
|
93
88
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
89
|
+
ModelRegistry.clear()
|
|
90
|
+
_parent, child = _models()
|
|
91
|
+
params: list = []
|
|
92
|
+
sql = Subquery(child.filter(n=1).filter(Q(site_id="s")).select("id")).as_sql(
|
|
93
|
+
"public.sites", params
|
|
94
|
+
)
|
|
95
|
+
assert "WHERE n = $1 AND site_id = $2" in sql
|
|
96
|
+
assert params == [1, "s"]
|
|
97
|
+
ModelRegistry.clear()
|
|
97
98
|
|
|
98
99
|
|
|
99
100
|
# ── End-to-end SQL (pattern B — dummy adapter) ──────────────────────────────
|
|
@@ -91,12 +91,10 @@ class TestGetModelByTable:
|
|
|
91
91
|
ModelRegistry.register(M)
|
|
92
92
|
assert ModelRegistry.get_model_by_table("public", "table_test_2") is M
|
|
93
93
|
|
|
94
|
-
def
|
|
94
|
+
def test_modelmeta_derived_table_name_is_indexed(self):
|
|
95
95
|
M = _make_model("NoTableName")
|
|
96
96
|
ModelRegistry.register(M)
|
|
97
|
-
|
|
98
|
-
# and must not accidentally satisfy an unrelated lookup.
|
|
99
|
-
assert ModelRegistry.get_model_by_table("public", "") is None
|
|
97
|
+
assert ModelRegistry.get_model_by_table("public", "no_table_name") is M
|
|
100
98
|
|
|
101
99
|
def test_wrong_schema_returns_none(self):
|
|
102
100
|
M = _make_model("TableTest3", table_name="table_test_3", schema="widgets")
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Level 1: a Subquery whose INNER queryset itself carries an expression filter.
|
|
2
|
+
|
|
3
|
+
Grown from a live production failure in aidream's ``conversations`` tool
|
|
4
|
+
("search my Claude Code conversations"): the search path scopes a message-text
|
|
5
|
+
lookup to the caller's conversations with
|
|
6
|
+
``Message.filter(conversation_id__in=Subquery(summaries))`` — and ``summaries``
|
|
7
|
+
was itself already filtered with ``id__in=Subquery(coding_sessions)`` for the
|
|
8
|
+
provider filter. The OUTER builder understands an expression value
|
|
9
|
+
(``_build_condition_with_expr``); ``_render_subquery`` did not — it fell
|
|
10
|
+
through to ``_build_condition``, emitting ``id = ANY($n)`` and BINDING THE
|
|
11
|
+
``Subquery`` OBJECT AS A PARAMETER. asyncpg then failed to encode it, so
|
|
12
|
+
search+provider died while search-alone and list+provider both worked.
|
|
13
|
+
|
|
14
|
+
The same renderer also rejected a compound/negated ``Q`` in an inner queryset
|
|
15
|
+
(``origin_class='unknown'`` renders ``Q(...) | Q(...)``), which is the same
|
|
16
|
+
"the inner query is a real query too" gap.
|
|
17
|
+
|
|
18
|
+
Pure SQL-text assertions — no DB needed.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import pytest
|
|
24
|
+
from matrx_orm.core.base import Model, ModelMeta
|
|
25
|
+
from matrx_orm.core.expressions import Now, OuterRef, Q, Subquery
|
|
26
|
+
from matrx_orm.core.fields import BooleanField, DateTimeField, TextField, UUIDField
|
|
27
|
+
from matrx_orm.core.registry import ModelRegistry
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.fixture(autouse=True)
|
|
31
|
+
def _clean_registry():
|
|
32
|
+
ModelRegistry.clear()
|
|
33
|
+
yield
|
|
34
|
+
ModelRegistry.clear()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _models():
|
|
38
|
+
Summary = ModelMeta(
|
|
39
|
+
"SummaryM",
|
|
40
|
+
(Model,),
|
|
41
|
+
{
|
|
42
|
+
"id": UUIDField(primary_key=True),
|
|
43
|
+
"user_id": TextField(null=True),
|
|
44
|
+
"origin_class": TextField(null=True),
|
|
45
|
+
"updated_at": DateTimeField(null=True),
|
|
46
|
+
"_database": "test_db",
|
|
47
|
+
"_table_name": "conversation_summary",
|
|
48
|
+
"_db_schema": "chat",
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
Session = ModelMeta(
|
|
52
|
+
"SessionM",
|
|
53
|
+
(Model,),
|
|
54
|
+
{
|
|
55
|
+
"id": UUIDField(primary_key=True),
|
|
56
|
+
"conversation_id": UUIDField(null=True),
|
|
57
|
+
"provider": TextField(null=True),
|
|
58
|
+
"created_by": TextField(null=True),
|
|
59
|
+
"_database": "test_db",
|
|
60
|
+
"_table_name": "coding_session",
|
|
61
|
+
"_db_schema": "chat",
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
Message = ModelMeta(
|
|
65
|
+
"MessageM",
|
|
66
|
+
(Model,),
|
|
67
|
+
{
|
|
68
|
+
"id": UUIDField(primary_key=True),
|
|
69
|
+
"conversation_id": UUIDField(null=True),
|
|
70
|
+
"is_visible_to_user": BooleanField(default=True),
|
|
71
|
+
"_database": "test_db",
|
|
72
|
+
"_table_name": "message",
|
|
73
|
+
"_db_schema": "chat",
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
return Summary, Session, Message
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class TestNestedSubqueryValue:
|
|
80
|
+
def test_inner_subquery_renders_as_sql_not_a_bound_parameter(self):
|
|
81
|
+
Summary, Session, Message = _models()
|
|
82
|
+
sessions = Session.filter(created_by="u1", provider="claude_code").select("conversation_id")
|
|
83
|
+
summaries = Summary.filter(user_id="u1").filter(id__in=Subquery(sessions)).select("id")
|
|
84
|
+
|
|
85
|
+
qb = Message.filter(conversation_id__in=Subquery(summaries))
|
|
86
|
+
executor = qb._get_executor()
|
|
87
|
+
params: list = []
|
|
88
|
+
cond, _ = executor._build_condition_with_expr(
|
|
89
|
+
"conversation_id", "in", Subquery(summaries), params
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# The nested provider filter must be SQL, never a bind parameter.
|
|
93
|
+
assert "id IN (SELECT conversation_id FROM chat.coding_session" in cond
|
|
94
|
+
assert "ANY(" not in cond
|
|
95
|
+
assert all(not isinstance(p, Subquery) for p in params)
|
|
96
|
+
# Every placeholder in the rendered condition must have a real param.
|
|
97
|
+
assert params == ["u1", "u1", "claude_code"]
|
|
98
|
+
assert "$1" in cond and "$2" in cond and "$3" in cond
|
|
99
|
+
assert "$4" not in cond
|
|
100
|
+
|
|
101
|
+
def test_placeholder_numbering_is_contiguous_with_outer_params(self):
|
|
102
|
+
Summary, Session, Message = _models()
|
|
103
|
+
sessions = Session.filter(provider="codex").select("conversation_id")
|
|
104
|
+
summaries = Summary.filter(user_id="u9").filter(id__in=Subquery(sessions)).select("id")
|
|
105
|
+
|
|
106
|
+
qb = Message.filter(conversation_id__in=Subquery(summaries))
|
|
107
|
+
executor = qb._get_executor()
|
|
108
|
+
params: list = ["already-bound"]
|
|
109
|
+
cond, _ = executor._build_condition_with_expr(
|
|
110
|
+
"conversation_id", "in", Subquery(summaries), params
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
assert params == ["already-bound", "u9", "codex"]
|
|
114
|
+
assert "$2" in cond and "$3" in cond
|
|
115
|
+
assert "$1" not in cond
|
|
116
|
+
|
|
117
|
+
def test_compound_q_in_inner_queryset_renders(self):
|
|
118
|
+
Summary, _Session, Message = _models()
|
|
119
|
+
summaries = (
|
|
120
|
+
Summary.filter(user_id="u1")
|
|
121
|
+
.filter(Q(origin_class="unknown") | Q(origin_class__isnull=True))
|
|
122
|
+
.select("id")
|
|
123
|
+
)
|
|
124
|
+
params: list = []
|
|
125
|
+
sql = Subquery(summaries).as_sql("chat.message", params)
|
|
126
|
+
|
|
127
|
+
assert "(origin_class = $2 OR origin_class IS NULL)" in sql
|
|
128
|
+
assert params == ["u1", "unknown"]
|
|
129
|
+
|
|
130
|
+
def test_negated_q_in_inner_queryset_renders(self):
|
|
131
|
+
Summary, _Session, _Message = _models()
|
|
132
|
+
summaries = Summary.filter(user_id="u1").filter(~Q(origin_class="human")).select("id")
|
|
133
|
+
params: list = []
|
|
134
|
+
sql = Subquery(summaries).as_sql("chat.message", params)
|
|
135
|
+
|
|
136
|
+
assert "NOT (origin_class = $2)" in sql
|
|
137
|
+
assert params == ["u1", "human"]
|
|
138
|
+
|
|
139
|
+
def test_outer_ref_still_correlates_through_a_q(self):
|
|
140
|
+
Summary, Session, _Message = _models()
|
|
141
|
+
inner = Session.filter(Q(conversation_id=OuterRef("id"))).select("conversation_id")
|
|
142
|
+
params: list = []
|
|
143
|
+
sql = Subquery(inner).as_sql("chat.conversation_summary", params)
|
|
144
|
+
|
|
145
|
+
assert "conversation_id = chat.conversation_summary.id" in sql
|
|
146
|
+
assert params == []
|
|
147
|
+
|
|
148
|
+
def test_database_function_value_inside_inner_queryset(self):
|
|
149
|
+
Summary, Session, _Message = _models()
|
|
150
|
+
Session_ = ModelMeta(
|
|
151
|
+
"SessionTimeM",
|
|
152
|
+
(Model,),
|
|
153
|
+
{
|
|
154
|
+
"id": UUIDField(primary_key=True),
|
|
155
|
+
"conversation_id": UUIDField(null=True),
|
|
156
|
+
"last_seen_at": DateTimeField(null=True),
|
|
157
|
+
"_database": "test_db",
|
|
158
|
+
"_table_name": "coding_session",
|
|
159
|
+
"_db_schema": "chat",
|
|
160
|
+
},
|
|
161
|
+
)
|
|
162
|
+
inner = Session_.filter(last_seen_at__lt=Now()).select("conversation_id")
|
|
163
|
+
params: list = []
|
|
164
|
+
sql = Subquery(inner).as_sql("chat.conversation_summary", params)
|
|
165
|
+
|
|
166
|
+
assert "last_seen_at < NOW()" in sql
|
|
167
|
+
assert params == []
|
|
@@ -459,6 +459,47 @@ async def test_non_sandbox_resolution_still_drops_keyless_rows_silently(monkeypa
|
|
|
459
459
|
assert not [r for r in caplog.records if r.levelno >= logging.ERROR]
|
|
460
460
|
|
|
461
461
|
|
|
462
|
+
@pytest.mark.asyncio
|
|
463
|
+
async def test_restricted_org_resolution_honors_item_level_grants(monkeypatch):
|
|
464
|
+
from matrx_orm.secrets_battery import org_service
|
|
465
|
+
|
|
466
|
+
rows = [
|
|
467
|
+
{
|
|
468
|
+
"id": "field-1",
|
|
469
|
+
"credential_item_id": "item-1",
|
|
470
|
+
"key": "APPLE_PRIVATE_KEY",
|
|
471
|
+
"value_encrypted": b"ciphertext",
|
|
472
|
+
"access_mode": "restricted",
|
|
473
|
+
}
|
|
474
|
+
]
|
|
475
|
+
secret_filters: list[dict] = []
|
|
476
|
+
grant_filters: list[dict] = []
|
|
477
|
+
monkeypatch.setattr(org_service, "UserSecret", _fake_user_secret(rows, secret_filters))
|
|
478
|
+
monkeypatch.setattr(org_service, "decrypt_value", lambda _ciphertext: "private-key")
|
|
479
|
+
|
|
480
|
+
class _FakeGrant:
|
|
481
|
+
@classmethod
|
|
482
|
+
def filter(cls, **filters):
|
|
483
|
+
grant_filters.append(filters)
|
|
484
|
+
if "credential_item_id__in" in filters:
|
|
485
|
+
return _FakeQB([{"credential_item_id": "item-1"}])
|
|
486
|
+
return _FakeQB([])
|
|
487
|
+
|
|
488
|
+
monkeypatch.setattr(org_service, "UserSecretGrant", _FakeGrant)
|
|
489
|
+
|
|
490
|
+
resolved = await org_service.resolve_org_secrets("org-1", user_id="user-1")
|
|
491
|
+
|
|
492
|
+
assert resolved == {"APPLE_PRIVATE_KEY": "private-key"}
|
|
493
|
+
assert grant_filters == [
|
|
494
|
+
{"user_secret_id__in": ["field-1"], "user_id": "user-1", "can_use": True},
|
|
495
|
+
{
|
|
496
|
+
"credential_item_id__in": ["item-1"],
|
|
497
|
+
"user_id": "user-1",
|
|
498
|
+
"can_use": True,
|
|
499
|
+
},
|
|
500
|
+
]
|
|
501
|
+
|
|
502
|
+
|
|
462
503
|
# ── Sealed reveal refusal + liveness filters ──────────────────────────────
|
|
463
504
|
|
|
464
505
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|