matrx-orm 3.1.67__tar.gz → 3.1.69__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.67 → matrx_orm-3.1.69}/.gitignore +6 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/PKG-INFO +1 -1
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/catalog.py +77 -44
- matrx_orm-3.1.69/matrx_orm/catalog_sql.py +115 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/introspection.py +11 -8
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/python_sql/table_detailed_relationships.py +85 -55
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/entity_capabilities.py +34 -14
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/helpers/base_generators.py +29 -16
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/pyproject.toml +1 -1
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_catalog.py +13 -5
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_schema_exists.py +9 -4
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_migrations_live.py +6 -2
- matrx_orm-3.1.69/tests/schema/test_table_relationships_query.py +65 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/AGENTS.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/BASE_CLASS_METHODS.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/CLAUDE.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/MODEL_API.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/docs/migrations.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/adapters/MODULE_README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/adapters/postgresql.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/admin/router.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/FEATURE.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/async_db_manager.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/base.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/config.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/connections.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/db_function.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/expressions.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/fields.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/one_database.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/pool_maintenance.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/registry.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/resilience.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/rls_session.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/tls.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/transaction.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/identity_rule.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/diff.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/db_functions.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/dynamic_crud.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/graph_walk.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/latest_rows.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/relational_backfill.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/staging_load.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/update.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/platform_db.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/python_sql/db_objects.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/query/FEATURE.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/query/builder.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/query/executor.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/code_handler.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/codegen_writer.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/secrets_battery/FEATURE.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/secrets_battery/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/secrets_battery/attachments.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/secrets_battery/crypto.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/secrets_battery/items.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/secrets_battery/model.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/secrets_battery/org_service.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/secrets_battery/service.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/coalesce.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/fallback.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/flush.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/lifecycle.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/managed.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/op.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/session.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/state.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/scripts/release.sh +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/conftest.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_admin_db_columns.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_admin_search_negation.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_case_when.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_composite_pk_write_paths.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_connection_primitives.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_derived_table.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_dynamic_crud.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_expression_primitives.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_from_alias.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_graph_walk.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_introspect_rls_policies.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_join_and_aggregate.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_latest_rows.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_managed_write_guard.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_optimistic_concurrency.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_parameter_name_hydration.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_pool_loop_recreation.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_pool_maintenance.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_pydantic_bridge.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_query_builder.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_query_timeout.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_registry.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_relational_backfill.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_resilience.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_tls.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_typed_join_projection.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_typed_predicates.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_update_with_rebase.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_upsert_default_leak.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_watched_lifecycle.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_composite_unique_generation.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_dto_identity_generation.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_entity_capability_generation.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_junction_analysis.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_sql_expression_defaults.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_view_generation.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema_builder/test_focused_generation_plan.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema_builder/test_host_models_aggregator.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_cache_no_downgrade.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_coalesce.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_dag.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_disk_spill_fallback.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_flush_individual_fallback.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_governed_write.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/test_db_functions.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/test_no_blocking_asyncpg_tls.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/test_one_database.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/test_secrets_battery.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/test_sql_param_casts.py +0 -0
- {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/test_sync_twins_guard.py +0 -0
|
@@ -277,3 +277,9 @@ credentials.json
|
|
|
277
277
|
token*.pickle
|
|
278
278
|
token*.json
|
|
279
279
|
tests_trials/rag_tests/kg_export_output/
|
|
280
|
+
|
|
281
|
+
# Pooler-contamination watch output (machine-local evidence, not source)
|
|
282
|
+
db/evidence/
|
|
283
|
+
|
|
284
|
+
# Pooler contamination evidence — machine-local forensic capture, not source
|
|
285
|
+
db/evidence/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: matrx-orm
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.69
|
|
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
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
+
from .catalog_sql import PG_CATALOG_COLUMNS_CTE, PG_CATALOG_TABLES_CTE
|
|
7
8
|
from .core.async_db_manager import AsyncDatabaseManager
|
|
8
9
|
|
|
9
10
|
__all__ = [
|
|
@@ -42,12 +43,18 @@ async def list_relations(
|
|
|
42
43
|
kinds: tuple[str, ...] = ("BASE TABLE", "VIEW"),
|
|
43
44
|
exclude_schemas: list[str] | None = None,
|
|
44
45
|
) -> list[dict[str, str]]:
|
|
45
|
-
"""Return schema/name/kind rows
|
|
46
|
+
"""Return schema/name/kind rows for application relations.
|
|
47
|
+
|
|
48
|
+
pg_catalog, not information_schema: audit callers treat this enumeration as
|
|
49
|
+
the complete relation set, so a privilege-filtered source silently drops live
|
|
50
|
+
tables and reports them missing. See matrx_orm/catalog_sql.py.
|
|
51
|
+
"""
|
|
46
52
|
return await _query(
|
|
47
53
|
database,
|
|
48
|
-
"""
|
|
54
|
+
f"""
|
|
55
|
+
WITH {PG_CATALOG_TABLES_CTE.strip()}
|
|
49
56
|
SELECT table_schema, table_name, table_type
|
|
50
|
-
FROM
|
|
57
|
+
FROM pg_catalog_tables
|
|
51
58
|
WHERE ($1::text[] IS NULL OR table_schema = ANY($1::text[]))
|
|
52
59
|
AND table_type = ANY($2::text[])
|
|
53
60
|
AND ($3::text[] IS NULL OR table_schema <> ALL($3::text[]))
|
|
@@ -64,9 +71,9 @@ async def application_schemas(database: str, *, exclude_schemas: list[str]) -> l
|
|
|
64
71
|
rows = await _query(
|
|
65
72
|
database,
|
|
66
73
|
"""
|
|
67
|
-
SELECT schema_name
|
|
68
|
-
FROM
|
|
69
|
-
WHERE
|
|
74
|
+
SELECT nspname AS schema_name
|
|
75
|
+
FROM pg_namespace
|
|
76
|
+
WHERE nspname <> ALL($1::text[])
|
|
70
77
|
ORDER BY schema_name
|
|
71
78
|
""",
|
|
72
79
|
exclude_schemas,
|
|
@@ -79,10 +86,12 @@ async def relation_schemas(database: str, *, table: str, exclude_schemas: list[s
|
|
|
79
86
|
rows = await _query(
|
|
80
87
|
database,
|
|
81
88
|
"""
|
|
82
|
-
SELECT DISTINCT table_schema
|
|
83
|
-
FROM
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
SELECT DISTINCT n.nspname AS table_schema
|
|
90
|
+
FROM pg_class c
|
|
91
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
92
|
+
WHERE c.relname = $1
|
|
93
|
+
AND c.relkind = ANY ('{r,p,v,m,f}')
|
|
94
|
+
AND n.nspname <> ALL($2::text[])
|
|
86
95
|
ORDER BY table_schema
|
|
87
96
|
""",
|
|
88
97
|
table,
|
|
@@ -105,16 +114,18 @@ async def describe_relation_columns(
|
|
|
105
114
|
c.is_nullable,
|
|
106
115
|
c.column_default,
|
|
107
116
|
EXISTS (
|
|
117
|
+
-- pg_catalog: "is this column part of the primary key" is an
|
|
118
|
+
-- existence decision, and the information_schema constraint views are
|
|
119
|
+
-- privilege-filtered. See matrx_orm/catalog_sql.py.
|
|
108
120
|
SELECT 1
|
|
109
|
-
FROM
|
|
110
|
-
JOIN
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
AND
|
|
116
|
-
AND
|
|
117
|
-
AND kcu.column_name = c.column_name
|
|
121
|
+
FROM pg_index i
|
|
122
|
+
JOIN pg_class pc ON pc.oid = i.indrelid
|
|
123
|
+
JOIN pg_namespace pn ON pn.oid = pc.relnamespace
|
|
124
|
+
JOIN pg_attribute pa ON pa.attrelid = pc.oid AND pa.attnum = ANY (i.indkey)
|
|
125
|
+
WHERE i.indisprimary
|
|
126
|
+
AND pn.nspname = c.table_schema
|
|
127
|
+
AND pc.relname = c.table_name
|
|
128
|
+
AND pa.attname = c.column_name
|
|
118
129
|
) AS is_primary_key
|
|
119
130
|
FROM information_schema.columns c
|
|
120
131
|
WHERE c.table_schema = $1 AND c.table_name = $2
|
|
@@ -130,10 +141,15 @@ async def relation_column_counts(database: str) -> list[dict[str, Any]]:
|
|
|
130
141
|
return await _query(
|
|
131
142
|
database,
|
|
132
143
|
"""
|
|
133
|
-
SELECT table_schema,
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
144
|
+
SELECT n.nspname AS table_schema, c.relname AS table_name,
|
|
145
|
+
COUNT(*)::int AS column_count
|
|
146
|
+
FROM pg_attribute a
|
|
147
|
+
JOIN pg_class c ON c.oid = a.attrelid
|
|
148
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
149
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
150
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
|
151
|
+
AND c.relkind = ANY ('{r,p,v,m,f}')
|
|
152
|
+
GROUP BY n.nspname, c.relname
|
|
137
153
|
ORDER BY table_schema, table_name
|
|
138
154
|
""",
|
|
139
155
|
)
|
|
@@ -234,23 +250,31 @@ async def entity_capability_state(database: str) -> list[dict[str, Any]]:
|
|
|
234
250
|
) AS really_versioned,
|
|
235
251
|
EXISTS (
|
|
236
252
|
SELECT 1
|
|
237
|
-
FROM
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
253
|
+
FROM pg_attribute a
|
|
254
|
+
JOIN pg_class pc ON pc.oid = a.attrelid
|
|
255
|
+
JOIN pg_namespace pn ON pn.oid = pc.relnamespace
|
|
256
|
+
WHERE pn.nspname = et.schema_name
|
|
257
|
+
AND pc.relname = et.table_name
|
|
258
|
+
AND a.attname = 'deleted_at'
|
|
259
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
|
241
260
|
) AS really_soft_delete,
|
|
242
261
|
EXISTS (
|
|
243
262
|
SELECT 1
|
|
244
|
-
FROM
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
263
|
+
FROM pg_attribute a
|
|
264
|
+
JOIN pg_class pc ON pc.oid = a.attrelid
|
|
265
|
+
JOIN pg_namespace pn ON pn.oid = pc.relnamespace
|
|
266
|
+
WHERE pn.nspname = et.schema_name
|
|
267
|
+
AND pc.relname = et.table_name
|
|
268
|
+
AND a.attname = 'version'
|
|
269
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
|
248
270
|
) AS has_version_column,
|
|
249
271
|
EXISTS (
|
|
250
272
|
SELECT 1
|
|
251
|
-
FROM
|
|
252
|
-
|
|
253
|
-
|
|
273
|
+
FROM pg_class pc2
|
|
274
|
+
JOIN pg_namespace pn2 ON pn2.oid = pc2.relnamespace
|
|
275
|
+
WHERE pn2.nspname = et.schema_name
|
|
276
|
+
AND pc2.relname = et.table_name
|
|
277
|
+
AND pc2.relkind = ANY ('{r,p,v,m,f}')
|
|
254
278
|
) AS table_exists
|
|
255
279
|
FROM platform.entity_types et
|
|
256
280
|
ORDER BY et.token
|
|
@@ -263,8 +287,10 @@ async def qualified_relation_names(database: str) -> set[str]:
|
|
|
263
287
|
rows = await _query(
|
|
264
288
|
database,
|
|
265
289
|
"""
|
|
266
|
-
SELECT DISTINCT table_schema, table_name
|
|
267
|
-
FROM
|
|
290
|
+
SELECT DISTINCT n.nspname AS table_schema, c.relname AS table_name
|
|
291
|
+
FROM pg_class c
|
|
292
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
293
|
+
WHERE c.relkind = ANY ('{r,p,v,m,f}')
|
|
268
294
|
ORDER BY table_schema, table_name
|
|
269
295
|
""",
|
|
270
296
|
)
|
|
@@ -414,9 +440,11 @@ async def org_backstop_coverage(database: str) -> list[dict[str, Any]]:
|
|
|
414
440
|
"""Return required org-id relations with owner/backstop booleans."""
|
|
415
441
|
return await _query(
|
|
416
442
|
database,
|
|
417
|
-
"""
|
|
443
|
+
f"""
|
|
444
|
+
WITH {PG_CATALOG_COLUMNS_CTE.strip()},
|
|
445
|
+
{PG_CATALOG_TABLES_CTE.strip()}
|
|
418
446
|
SELECT c.table_schema AS schema, c.table_name,
|
|
419
|
-
EXISTS (SELECT 1 FROM
|
|
447
|
+
EXISTS (SELECT 1 FROM pg_catalog_columns o
|
|
420
448
|
WHERE o.table_schema=c.table_schema AND o.table_name=c.table_name
|
|
421
449
|
AND o.column_name IN ('created_by','user_id','owner_id')) AS has_owner,
|
|
422
450
|
EXISTS (SELECT 1 FROM pg_trigger t
|
|
@@ -424,8 +452,8 @@ async def org_backstop_coverage(database: str) -> list[dict[str, Any]]:
|
|
|
424
452
|
JOIN pg_namespace n ON n.oid=cl.relnamespace
|
|
425
453
|
WHERE n.nspname=c.table_schema AND cl.relname=c.table_name
|
|
426
454
|
AND t.tgname='_stamp_org_default' AND NOT t.tgisinternal) AS has_backstop
|
|
427
|
-
FROM
|
|
428
|
-
JOIN
|
|
455
|
+
FROM pg_catalog_columns c
|
|
456
|
+
JOIN pg_catalog_tables tb
|
|
429
457
|
ON tb.table_schema=c.table_schema AND tb.table_name=c.table_name
|
|
430
458
|
AND tb.table_type='BASE TABLE'
|
|
431
459
|
WHERE c.column_name='organization_id' AND c.is_nullable='NO'
|
|
@@ -602,10 +630,15 @@ async def actor_stamp_column_drift(
|
|
|
602
630
|
WHERE p.proname = $1
|
|
603
631
|
AND NOT t.tgisinternal
|
|
604
632
|
AND NOT EXISTS (
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
633
|
+
-- pg_catalog, not information_schema: a privilege-filtered view here
|
|
634
|
+
-- manufactures "missing column" findings for columns that exist.
|
|
635
|
+
SELECT 1 FROM pg_attribute a2
|
|
636
|
+
JOIN pg_class c2 ON c2.oid = a2.attrelid
|
|
637
|
+
JOIN pg_namespace n2 ON n2.oid = c2.relnamespace
|
|
638
|
+
WHERE n2.nspname = n.nspname
|
|
639
|
+
AND c2.relname = cl.relname
|
|
640
|
+
AND a2.attname = miss.col
|
|
641
|
+
AND a2.attnum > 0 AND NOT a2.attisdropped
|
|
609
642
|
)
|
|
610
643
|
ORDER BY 1, 2, 4
|
|
611
644
|
""",
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Privilege-independent catalog SQL — the ONE source for "does this exist?".
|
|
2
|
+
|
|
3
|
+
WHY THIS EXISTS (FOUND_DEFECTS, "Pooled connections intermittently run as
|
|
4
|
+
`authenticated`, not `postgres`"):
|
|
5
|
+
|
|
6
|
+
``information_schema.columns`` / ``.tables`` / ``.schemata`` are **privilege-
|
|
7
|
+
filtered views**. They do not answer "does this object exist?" — they answer
|
|
8
|
+
"does this object exist *and* does the current role hold some privilege on it?".
|
|
9
|
+
Those two questions give the same answer right up until the session is not the
|
|
10
|
+
role you assumed, and then the view reports EXISTING OBJECTS AS ABSENT with no
|
|
11
|
+
error of any kind.
|
|
12
|
+
|
|
13
|
+
That is not hypothetical here. It corrupted the migration ledger: a
|
|
14
|
+
``fetch_db_state()`` that read ``information_schema`` while the pooler had handed
|
|
15
|
+
back a session running as ``authenticated`` saw a smaller universe, so applied
|
|
16
|
+
migrations were recorded as never applied (MIGRATIONS_STATUS.md APPLIED
|
|
17
|
+
1200 -> 1171). The failure is silent and it is confidently wrong, which is the
|
|
18
|
+
worst combination a guard can have.
|
|
19
|
+
|
|
20
|
+
``pg_catalog`` (``pg_class`` / ``pg_attribute`` / ``pg_namespace``) is readable by
|
|
21
|
+
PUBLIC and is **not** privilege-filtered, so it answers the existence question the
|
|
22
|
+
same way regardless of which role the statement lands as.
|
|
23
|
+
|
|
24
|
+
THE RULE: if the answer decides whether something EXISTS — an existence probe, a
|
|
25
|
+
diff, a set subtraction, a "missing column" finding, a capability flag — it reads
|
|
26
|
+
``pg_catalog``. ``information_schema`` is fine for *display* (browsing a schema,
|
|
27
|
+
rendering a column list to a human) where a short answer is an acceptable answer.
|
|
28
|
+
|
|
29
|
+
Note this is the same class of bug as reading a PostgREST list without paging and
|
|
30
|
+
treating the first 1000 rows as the whole set: a partial answer, presented as
|
|
31
|
+
complete, with nothing raised.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
# Drop-in replacement for `information_schema.columns` for the columns our
|
|
37
|
+
# existence checks actually use. Deliberately mirrors those column NAMES so a
|
|
38
|
+
# query can be converted by swapping the FROM clause and nothing else.
|
|
39
|
+
#
|
|
40
|
+
# is_nullable 'YES'/'NO' — same domain as information_schema
|
|
41
|
+
# column_default text|NULL — the expression, as information_schema renders it
|
|
42
|
+
PG_CATALOG_COLUMNS_CTE = """
|
|
43
|
+
pg_catalog_columns AS (
|
|
44
|
+
SELECT n.nspname AS table_schema,
|
|
45
|
+
c.relname AS table_name,
|
|
46
|
+
a.attname AS column_name,
|
|
47
|
+
a.attnum AS ordinal_position,
|
|
48
|
+
CASE WHEN a.attnotnull THEN 'NO' ELSE 'YES' END AS is_nullable,
|
|
49
|
+
pg_get_expr(ad.adbin, ad.adrelid) AS column_default,
|
|
50
|
+
format_type(a.atttypid, a.atttypmod) AS data_type
|
|
51
|
+
FROM pg_attribute a
|
|
52
|
+
JOIN pg_class c ON c.oid = a.attrelid
|
|
53
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
54
|
+
LEFT JOIN pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum
|
|
55
|
+
WHERE a.attnum > 0
|
|
56
|
+
AND NOT a.attisdropped
|
|
57
|
+
AND c.relkind = ANY ('{r,p,v,m,f}')
|
|
58
|
+
)
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
# Same idea for `information_schema.tables`. `table_type` mirrors the
|
|
62
|
+
# information_schema spelling so callers can swap the FROM clause unchanged.
|
|
63
|
+
PG_CATALOG_TABLES_CTE = """
|
|
64
|
+
pg_catalog_tables AS (
|
|
65
|
+
SELECT n.nspname AS table_schema,
|
|
66
|
+
c.relname AS table_name,
|
|
67
|
+
CASE c.relkind
|
|
68
|
+
WHEN 'r' THEN 'BASE TABLE'
|
|
69
|
+
WHEN 'p' THEN 'BASE TABLE'
|
|
70
|
+
WHEN 'v' THEN 'VIEW'
|
|
71
|
+
WHEN 'm' THEN 'MATERIALIZED VIEW'
|
|
72
|
+
WHEN 'f' THEN 'FOREIGN TABLE'
|
|
73
|
+
END AS table_type
|
|
74
|
+
FROM pg_class c
|
|
75
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
76
|
+
WHERE c.relkind = ANY ('{r,p,v,m,f}')
|
|
77
|
+
)
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
# Standalone existence probes. Parameter-bound, never interpolated.
|
|
81
|
+
SCHEMA_EXISTS_SQL = "SELECT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = $1)"
|
|
82
|
+
|
|
83
|
+
TABLE_EXISTS_SQL = """
|
|
84
|
+
SELECT EXISTS (
|
|
85
|
+
SELECT 1 FROM pg_class c
|
|
86
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
87
|
+
WHERE n.nspname = $1 AND c.relname = $2
|
|
88
|
+
AND c.relkind = ANY ('{r,p,v,m,f}')
|
|
89
|
+
)
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
COLUMN_EXISTS_SQL = """
|
|
93
|
+
SELECT EXISTS (
|
|
94
|
+
SELECT 1 FROM pg_attribute a
|
|
95
|
+
JOIN pg_class c ON c.oid = a.attrelid
|
|
96
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
97
|
+
WHERE n.nspname = $1 AND c.relname = $2 AND a.attname = $3
|
|
98
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
|
99
|
+
)
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
# Columns for a bounded set of tables in one schema — the shape used by the
|
|
103
|
+
# schema-drift guards, where a privilege-shortened answer reads as "the code
|
|
104
|
+
# claims a column the database does not have".
|
|
105
|
+
COLUMNS_FOR_TABLES_SQL = """
|
|
106
|
+
SELECT c.relname AS table_name, a.attname AS column_name
|
|
107
|
+
FROM pg_attribute a
|
|
108
|
+
JOIN pg_class c ON c.oid = a.attrelid
|
|
109
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
110
|
+
WHERE n.nspname = $1
|
|
111
|
+
AND c.relname = ANY ($2::text[])
|
|
112
|
+
AND a.attnum > 0
|
|
113
|
+
AND NOT a.attisdropped
|
|
114
|
+
AND c.relkind = ANY ('{r,p,v,m,f}')
|
|
115
|
+
"""
|
|
@@ -17,6 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
|
|
18
18
|
from typing import Any
|
|
19
19
|
|
|
20
|
+
from matrx_orm.catalog_sql import COLUMNS_FOR_TABLES_SQL, SCHEMA_EXISTS_SQL
|
|
20
21
|
from matrx_orm.core.async_db_manager import AsyncDatabaseManager
|
|
21
22
|
from matrx_orm.core.transaction import get_active_connection
|
|
22
23
|
|
|
@@ -71,12 +72,11 @@ async def introspect_rls_policies(
|
|
|
71
72
|
return [dict(r) for r in rows]
|
|
72
73
|
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
"""
|
|
75
|
+
# pg_catalog, NOT information_schema: this decides whether a column EXISTS, and
|
|
76
|
+
# information_schema is privilege-filtered — under a session that is not the role
|
|
77
|
+
# we assume, present columns read as absent and the drift guard reports the code
|
|
78
|
+
# as wrong about the schema. See matrx_orm/catalog_sql.py.
|
|
79
|
+
_COLUMNS_SQL = COLUMNS_FOR_TABLES_SQL
|
|
80
80
|
|
|
81
81
|
|
|
82
82
|
async def introspect_columns(database: str, schema: str, tables: list[str]) -> dict[str, list[str]]:
|
|
@@ -102,14 +102,17 @@ async def introspect_columns(database: str, schema: str, tables: list[str]) -> d
|
|
|
102
102
|
|
|
103
103
|
async def schema_exists(database: str, schema: str) -> bool:
|
|
104
104
|
"""True if ``schema`` exists in ``database`` — a live
|
|
105
|
-
``
|
|
105
|
+
``pg_catalog`` probe (privilege-independent).
|
|
106
106
|
|
|
107
107
|
Another genuine no-Model-surface catalog question (same class as
|
|
108
108
|
``introspect_rls_policies`` above): "does this schema exist" is a fact
|
|
109
109
|
about Postgres, not a row in an application table. ``schema`` is bound
|
|
110
110
|
as a parameter (``$1``), never interpolated into the query text.
|
|
111
111
|
"""
|
|
112
|
-
|
|
112
|
+
# pg_catalog, not information_schema.schemata — the latter is privilege-
|
|
113
|
+
# filtered and answers "no such schema" for a schema that plainly exists
|
|
114
|
+
# whenever the session is not the role we assume.
|
|
115
|
+
sql = SCHEMA_EXISTS_SQL
|
|
113
116
|
|
|
114
117
|
active_conn = get_active_connection()
|
|
115
118
|
if active_conn is not None:
|
|
@@ -10,6 +10,35 @@ def get_table_relationships(schema, database_project):
|
|
|
10
10
|
"""
|
|
11
11
|
Executes the SQL query to fetch relationship information for all tables in the specified schema.
|
|
12
12
|
|
|
13
|
+
Reads `pg_catalog` (never `information_schema`) for three reasons:
|
|
14
|
+
|
|
15
|
+
1. CROSS-SCHEMA FKs. The old query enumerated constraints through
|
|
16
|
+
information_schema and joined `constraint_column_usage` with
|
|
17
|
+
`ccu.table_schema = tc.table_schema`, which silently DROPPED every FK
|
|
18
|
+
whose REFERENCED table lives in another schema. After the 2026 schema
|
|
19
|
+
reorg that is most of them — `created_by -> users.users.id`,
|
|
20
|
+
`organization_id -> iam.organizations.id`, and so on. Measured against
|
|
21
|
+
Matrx Main on 2026-08-15: platform 12 -> 36 FK rows, crm 32 -> 105,
|
|
22
|
+
chat 33 -> 117, with the pg_catalog set a strict superset every time.
|
|
23
|
+
2. COMPOSITE FKs. `key_column_usage` and `constraint_column_usage` were
|
|
24
|
+
joined on constraint name alone, with no pairing on ordinal position, so
|
|
25
|
+
a two-column FK cartesian-joined into four bogus column pairs. The
|
|
26
|
+
`unnest(...) WITH ORDINALITY` pairing below is exact.
|
|
27
|
+
3. PRIVILEGE FILTERING. information_schema answers "exists AND I hold a
|
|
28
|
+
privilege on it"; under an unexpected pooled role it reports existing
|
|
29
|
+
objects as absent (see scripts/check_existence_source.py). pg_catalog is
|
|
30
|
+
readable by PUBLIC and answers the same way whatever role we land as.
|
|
31
|
+
|
|
32
|
+
KEYS OF `foreign_keys`: the referenced table's BARE name when it lives in
|
|
33
|
+
this schema (unchanged from before, so nothing downstream renames), and
|
|
34
|
+
`schema.table` when it lives outside it. Both halves are also carried
|
|
35
|
+
explicitly in the value as `foreign_schema` / `foreign_table`, matching how
|
|
36
|
+
`analyze_junction_tables` and `db_objects.foreign_key_reference` already
|
|
37
|
+
report cross-schema references. Qualifying only the outsiders is what keeps
|
|
38
|
+
`json_object_agg` from collapsing a local `orders` table and a foreign
|
|
39
|
+
`billing.orders` onto one key. Keys of `referenced_by` are always in this
|
|
40
|
+
schema and stay bare.
|
|
41
|
+
|
|
13
42
|
Parameters:
|
|
14
43
|
schema (str): The schema name for which to fetch table relationships.
|
|
15
44
|
database_project (str): The name of the database configuration to use for the connection
|
|
@@ -21,78 +50,74 @@ def get_table_relationships(schema, database_project):
|
|
|
21
50
|
query = """
|
|
22
51
|
WITH fk_info AS (
|
|
23
52
|
SELECT
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
53
|
+
n.nspname AS table_schema,
|
|
54
|
+
con.conname AS constraint_name,
|
|
55
|
+
c.relname AS table_name,
|
|
56
|
+
a.attname AS column_name,
|
|
57
|
+
nf.nspname AS foreign_table_schema,
|
|
58
|
+
cf.relname AS foreign_table_name,
|
|
59
|
+
af.attname AS foreign_column_name,
|
|
60
|
+
-- Does the referencing column participate in a PK/UNIQUE constraint?
|
|
31
61
|
EXISTS (
|
|
32
62
|
SELECT 1
|
|
33
|
-
FROM
|
|
34
|
-
WHERE
|
|
35
|
-
AND
|
|
36
|
-
AND
|
|
37
|
-
AND tc2.constraint_name IN (
|
|
38
|
-
SELECT constraint_name
|
|
39
|
-
FROM information_schema.key_column_usage
|
|
40
|
-
WHERE table_schema = tc.table_schema
|
|
41
|
-
AND table_name = tc.table_name
|
|
42
|
-
AND column_name = kcu.column_name
|
|
43
|
-
)
|
|
63
|
+
FROM pg_catalog.pg_constraint u
|
|
64
|
+
WHERE u.conrelid = con.conrelid
|
|
65
|
+
AND u.contype IN ('p', 'u')
|
|
66
|
+
AND a.attnum = ANY (u.conkey)
|
|
44
67
|
) AS has_unique_constraint,
|
|
68
|
+
-- ...and the referenced column?
|
|
45
69
|
EXISTS (
|
|
46
70
|
SELECT 1
|
|
47
|
-
FROM
|
|
48
|
-
WHERE
|
|
49
|
-
AND
|
|
50
|
-
AND
|
|
51
|
-
AND tc2.constraint_name IN (
|
|
52
|
-
SELECT constraint_name
|
|
53
|
-
FROM information_schema.key_column_usage
|
|
54
|
-
WHERE table_schema = tc.table_schema
|
|
55
|
-
AND table_name = ccu.table_name
|
|
56
|
-
AND column_name = ccu.column_name
|
|
57
|
-
)
|
|
71
|
+
FROM pg_catalog.pg_constraint u
|
|
72
|
+
WHERE u.conrelid = con.confrelid
|
|
73
|
+
AND u.contype IN ('p', 'u')
|
|
74
|
+
AND af.attnum = ANY (u.conkey)
|
|
58
75
|
) AS referenced_has_unique_constraint,
|
|
59
|
-
--
|
|
76
|
+
-- More than one FK on the referencing table (junction candidate)
|
|
60
77
|
(
|
|
61
|
-
SELECT
|
|
62
|
-
FROM
|
|
63
|
-
WHERE
|
|
64
|
-
AND
|
|
65
|
-
AND tc2.constraint_type = 'FOREIGN KEY'
|
|
78
|
+
SELECT count(*)
|
|
79
|
+
FROM pg_catalog.pg_constraint f2
|
|
80
|
+
WHERE f2.conrelid = con.conrelid
|
|
81
|
+
AND f2.contype = 'f'
|
|
66
82
|
) > 1 AS is_junction_table
|
|
67
|
-
FROM
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
FROM pg_catalog.pg_constraint con
|
|
84
|
+
JOIN pg_catalog.pg_class c ON c.oid = con.conrelid
|
|
85
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
86
|
+
JOIN pg_catalog.pg_class cf ON cf.oid = con.confrelid
|
|
87
|
+
JOIN pg_catalog.pg_namespace nf ON nf.oid = cf.relnamespace
|
|
88
|
+
JOIN unnest(con.conkey) WITH ORDINALITY AS lk(attnum, ord) ON true
|
|
89
|
+
JOIN unnest(con.confkey) WITH ORDINALITY AS fk(attnum, ord) ON fk.ord = lk.ord
|
|
90
|
+
JOIN pg_catalog.pg_attribute a
|
|
91
|
+
ON a.attrelid = con.conrelid AND a.attnum = lk.attnum
|
|
92
|
+
JOIN pg_catalog.pg_attribute af
|
|
93
|
+
ON af.attrelid = con.confrelid AND af.attnum = fk.attnum
|
|
94
|
+
WHERE con.contype = 'f' AND n.nspname = %s
|
|
76
95
|
),
|
|
77
96
|
all_tables AS (
|
|
78
|
-
SELECT table_name
|
|
79
|
-
FROM
|
|
80
|
-
|
|
97
|
+
SELECT c.relname AS table_name
|
|
98
|
+
FROM pg_catalog.pg_class c
|
|
99
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
100
|
+
WHERE n.nspname = %s AND c.relkind = ANY (ARRAY['r', 'p'])
|
|
81
101
|
)
|
|
82
102
|
SELECT
|
|
83
103
|
at.table_name,
|
|
84
104
|
(SELECT json_object_agg(
|
|
85
|
-
CASE
|
|
86
|
-
WHEN fk.foreign_table_name = at.table_name
|
|
87
|
-
|
|
105
|
+
CASE
|
|
106
|
+
WHEN fk.foreign_table_name = at.table_name
|
|
107
|
+
AND fk.foreign_table_schema = fk.table_schema THEN 'self_reference'
|
|
108
|
+
WHEN fk.foreign_table_schema = fk.table_schema THEN fk.foreign_table_name
|
|
109
|
+
ELSE fk.foreign_table_schema || '.' || fk.foreign_table_name
|
|
88
110
|
END,
|
|
89
111
|
json_build_object(
|
|
90
112
|
'constraint_name', fk.constraint_name,
|
|
91
113
|
'column', fk.column_name,
|
|
92
114
|
'foreign_column', fk.foreign_column_name,
|
|
93
|
-
'
|
|
94
|
-
|
|
95
|
-
|
|
115
|
+
'foreign_schema', fk.foreign_table_schema,
|
|
116
|
+
'foreign_table', fk.foreign_table_name,
|
|
117
|
+
'relationship_type',
|
|
118
|
+
CASE
|
|
119
|
+
WHEN fk.foreign_table_name = at.table_name
|
|
120
|
+
AND fk.foreign_table_schema = fk.table_schema THEN 'self-referential'
|
|
96
121
|
WHEN fk.is_junction_table THEN 'many-to-many'
|
|
97
122
|
WHEN fk.has_unique_constraint AND fk.referenced_has_unique_constraint THEN 'one-to-one'
|
|
98
123
|
WHEN NOT fk.has_unique_constraint THEN 'many-to-one'
|
|
@@ -100,6 +125,7 @@ def get_table_relationships(schema, database_project):
|
|
|
100
125
|
ELSE 'undefined'
|
|
101
126
|
END
|
|
102
127
|
)
|
|
128
|
+
ORDER BY fk.constraint_name, fk.column_name
|
|
103
129
|
)
|
|
104
130
|
FROM fk_info fk
|
|
105
131
|
WHERE fk.table_name = at.table_name) AS foreign_keys,
|
|
@@ -108,11 +134,15 @@ def get_table_relationships(schema, database_project):
|
|
|
108
134
|
json_build_object(
|
|
109
135
|
'constraint_name', fk2.constraint_name,
|
|
110
136
|
'column', fk2.column_name,
|
|
111
|
-
'foreign_column', fk2.foreign_column_name
|
|
137
|
+
'foreign_column', fk2.foreign_column_name,
|
|
138
|
+
'foreign_schema', fk2.foreign_table_schema,
|
|
139
|
+
'foreign_table', fk2.foreign_table_name
|
|
112
140
|
)
|
|
141
|
+
ORDER BY fk2.constraint_name, fk2.column_name
|
|
113
142
|
)
|
|
114
143
|
FROM fk_info fk2
|
|
115
|
-
WHERE fk2.foreign_table_name = at.table_name
|
|
144
|
+
WHERE fk2.foreign_table_name = at.table_name
|
|
145
|
+
AND fk2.foreign_table_schema = fk2.table_schema) AS referenced_by
|
|
116
146
|
FROM all_tables at;
|
|
117
147
|
"""
|
|
118
148
|
return execute_sql_query(query, (schema, schema), database_project)
|
|
@@ -29,6 +29,10 @@ from matrx_utils import vcprint
|
|
|
29
29
|
|
|
30
30
|
from matrx_orm.client.postgres_connection import execute_sql_query
|
|
31
31
|
|
|
32
|
+
# Every EXISTS here decides a CAPABILITY that generated model code depends on,
|
|
33
|
+
# so all of them read pg_catalog: information_schema is privilege-filtered and
|
|
34
|
+
# would silently report every capability as absent under a contaminated pooler
|
|
35
|
+
# session. See matrx_orm/catalog_sql.py.
|
|
32
36
|
# One table = one entity. A table registered in platform.entity_types IS an entity
|
|
33
37
|
# (that registration is the platform's own declaration); anything else is a plain table.
|
|
34
38
|
_CAPABILITIES_SQL = """
|
|
@@ -53,31 +57,47 @@ SELECT
|
|
|
53
57
|
AND NOT t.tgisinternal
|
|
54
58
|
) AS is_versioned,
|
|
55
59
|
EXISTS (
|
|
56
|
-
SELECT 1 FROM
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
SELECT 1 FROM pg_attribute a
|
|
61
|
+
JOIN pg_class pc ON pc.oid = a.attrelid
|
|
62
|
+
JOIN pg_namespace pn ON pn.oid = pc.relnamespace
|
|
63
|
+
WHERE pn.nspname = et.schema_name
|
|
64
|
+
AND pc.relname = et.table_name
|
|
65
|
+
AND a.attname = 'deleted_at'
|
|
66
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
|
60
67
|
) AS has_soft_delete,
|
|
61
68
|
EXISTS (
|
|
62
|
-
SELECT 1 FROM
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
SELECT 1 FROM pg_attribute a
|
|
70
|
+
JOIN pg_class pc ON pc.oid = a.attrelid
|
|
71
|
+
JOIN pg_namespace pn ON pn.oid = pc.relnamespace
|
|
72
|
+
WHERE pn.nspname = et.schema_name
|
|
73
|
+
AND pc.relname = et.table_name
|
|
74
|
+
AND a.attname = 'organization_id'
|
|
75
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
|
66
76
|
) AS is_org_scoped,
|
|
67
77
|
4 = (
|
|
68
78
|
SELECT count(*)
|
|
69
|
-
FROM
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
FROM pg_attribute a
|
|
80
|
+
JOIN pg_class pc ON pc.oid = a.attrelid
|
|
81
|
+
JOIN pg_namespace pn ON pn.oid = pc.relnamespace
|
|
82
|
+
WHERE pn.nspname = et.schema_name
|
|
83
|
+
AND pc.relname = et.table_name
|
|
84
|
+
AND a.attname IN ('source_type', 'source_id', 'target_type', 'target_id')
|
|
85
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
|
73
86
|
) AS is_polymorphic_edge_table
|
|
74
87
|
FROM platform.entity_types et
|
|
75
88
|
"""
|
|
76
89
|
|
|
77
90
|
_HAS_ENTITY_TYPES_SQL = """
|
|
78
91
|
SELECT EXISTS (
|
|
79
|
-
|
|
80
|
-
|
|
92
|
+
-- pg_catalog, not information_schema: this decides whether the entity
|
|
93
|
+
-- registry EXISTS. A privilege-filtered view answers "no" for a table that
|
|
94
|
+
-- is plainly there whenever the pooler hands back a session running as
|
|
95
|
+
-- another role, which would generate models with every capability flag
|
|
96
|
+
-- false. See matrx_orm/catalog_sql.py.
|
|
97
|
+
SELECT 1 FROM pg_class c
|
|
98
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
99
|
+
WHERE n.nspname = 'platform' AND c.relname = 'entity_types'
|
|
100
|
+
AND c.relkind = ANY ('{r,p,v,m,f}')
|
|
81
101
|
) AS present
|
|
82
102
|
"""
|
|
83
103
|
|