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.
Files changed (330) hide show
  1. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/.gitignore +6 -0
  2. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/PKG-INFO +1 -1
  3. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/catalog.py +77 -44
  4. matrx_orm-3.1.69/matrx_orm/catalog_sql.py +115 -0
  5. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/introspection.py +11 -8
  6. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/python_sql/table_detailed_relationships.py +85 -55
  7. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/entity_capabilities.py +34 -14
  8. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/helpers/base_generators.py +29 -16
  9. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/pyproject.toml +1 -1
  10. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_catalog.py +13 -5
  11. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_schema_exists.py +9 -4
  12. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_migrations_live.py +6 -2
  13. matrx_orm-3.1.69/tests/schema/test_table_relationships_query.py +65 -0
  14. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/AGENTS.md +0 -0
  15. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/BASE_CLASS_METHODS.md +0 -0
  16. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/CLAUDE.md +0 -0
  17. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/MODEL_API.md +0 -0
  18. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/README.md +0 -0
  19. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/database/orm/extended/managers/ai_model_base.py +0 -0
  20. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/docs/migrations.md +0 -0
  21. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/MODULE_README.md +0 -0
  22. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/__init__.py +0 -0
  23. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/adapters/MODULE_README.md +0 -0
  24. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/adapters/__init__.py +0 -0
  25. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/adapters/async_postgresql.py +0 -0
  26. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/adapters/base_adapter.py +0 -0
  27. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/adapters/postgresql.py +0 -0
  28. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
  29. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/adapters/supabase_adapter.py +0 -0
  30. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/admin/__init__.py +0 -0
  31. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/admin/router.py +0 -0
  32. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/api/__init__.py +0 -0
  33. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/api/auth.py +0 -0
  34. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/api/config.py +0 -0
  35. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/api/handlers.py +0 -0
  36. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/api/protocol.py +0 -0
  37. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/api/server.py +0 -0
  38. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/associations.py +0 -0
  39. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/cache_debug.py +0 -0
  40. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/client/MODULE_README.md +0 -0
  41. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/client/__init__.py +0 -0
  42. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/client/postgres_connection.py +0 -0
  43. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/client/postgrest.py +0 -0
  44. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/client/supabase_auth.py +0 -0
  45. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/client/supabase_config.py +0 -0
  46. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/client/supabase_manager.py +0 -0
  47. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/EXTENDED-TASK.md +0 -0
  48. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/FEATURE.md +0 -0
  49. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/MODULE_README.md +0 -0
  50. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
  51. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/TASKS.md +0 -0
  52. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/__init__.py +0 -0
  53. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/async_db_manager.py +0 -0
  54. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/base.py +0 -0
  55. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/config.py +0 -0
  56. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/connections.py +0 -0
  57. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/db_function.py +0 -0
  58. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/diagnostics.py +0 -0
  59. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/expressions.py +0 -0
  60. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/extended.py +0 -0
  61. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/fields.py +0 -0
  62. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/listen.py +0 -0
  63. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/loop_filters.py +0 -0
  64. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/model_dto.py +0 -0
  65. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/model_view.py +0 -0
  66. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/one_database.py +0 -0
  67. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/paginator.py +0 -0
  68. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/pool_maintenance.py +0 -0
  69. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/pydantic_bridge.py +0 -0
  70. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/registry.py +0 -0
  71. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/relations.py +0 -0
  72. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/resilience.py +0 -0
  73. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/rls_session.py +0 -0
  74. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/signals.py +0 -0
  75. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/tls.py +0 -0
  76. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/transaction.py +0 -0
  77. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/types.py +0 -0
  78. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/core/write_queue.py +0 -0
  79. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/entity.py +0 -0
  80. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/error_handling.py +0 -0
  81. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/exceptions.py +0 -0
  82. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/extended/__init__.py +0 -0
  83. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/extended/app_error_handler.py +0 -0
  84. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/identity_rule.py +0 -0
  85. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/middleware/__init__.py +0 -0
  86. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/middleware/base.py +0 -0
  87. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/MODULE_README.md +0 -0
  88. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/__init__.py +0 -0
  89. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/cli.py +0 -0
  90. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/ddl.py +0 -0
  91. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/diff.py +0 -0
  92. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/executor.py +0 -0
  93. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/integration.py +0 -0
  94. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/loader.py +0 -0
  95. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/operations.py +0 -0
  96. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/state.py +0 -0
  97. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/migrations/table_filter.py +0 -0
  98. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/MODULE_README.md +0 -0
  99. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/__init__.py +0 -0
  100. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/bulk_update_values.py +0 -0
  101. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/conflict.py +0 -0
  102. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/conflict_writes.py +0 -0
  103. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/create.py +0 -0
  104. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/db_functions.py +0 -0
  105. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/delete.py +0 -0
  106. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/dynamic_admin.py +0 -0
  107. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/dynamic_crud.py +0 -0
  108. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/graph_walk.py +0 -0
  109. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/latest_rows.py +0 -0
  110. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/queue_claim.py +0 -0
  111. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/read.py +0 -0
  112. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/relational_backfill.py +0 -0
  113. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/staging_load.py +0 -0
  114. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/operations/update.py +0 -0
  115. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/platform_db.py +0 -0
  116. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/py.typed +0 -0
  117. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/python_sql/MODULE_README.md +0 -0
  118. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/python_sql/__init__.py +0 -0
  119. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/python_sql/db_objects.py +0 -0
  120. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
  121. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/query/FEATURE.md +0 -0
  122. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/query/MODULE_README.md +0 -0
  123. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/query/__init__.py +0 -0
  124. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/query/builder.py +0 -0
  125. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/query/executor.py +0 -0
  126. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/MODULE_README.md +0 -0
  127. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/__init__.py +0 -0
  128. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/code_handler.py +0 -0
  129. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/codegen_writer.py +0 -0
  130. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/columns.py +0 -0
  131. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/common.py +0 -0
  132. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/diff_preview.py +0 -0
  133. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/generator.py +0 -0
  134. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
  135. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
  136. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
  137. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/package_wiring.py +0 -0
  138. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/relationships.py +0 -0
  139. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/runner.py +0 -0
  140. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/schema.py +0 -0
  141. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/schema_manager.py +0 -0
  142. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/tables.py +0 -0
  143. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/schema_builder/views.py +0 -0
  144. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/secrets_battery/FEATURE.md +0 -0
  145. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/secrets_battery/__init__.py +0 -0
  146. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/secrets_battery/attachments.py +0 -0
  147. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/secrets_battery/crypto.py +0 -0
  148. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/secrets_battery/items.py +0 -0
  149. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/secrets_battery/model.py +0 -0
  150. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/secrets_battery/org_service.py +0 -0
  151. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/secrets_battery/service.py +0 -0
  152. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/README.md +0 -0
  153. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/__init__.py +0 -0
  154. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/coalesce.py +0 -0
  155. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/dag.py +0 -0
  156. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/errors.py +0 -0
  157. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/fallback.py +0 -0
  158. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/flush.py +0 -0
  159. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/lifecycle.py +0 -0
  160. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/managed.py +0 -0
  161. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/op.py +0 -0
  162. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/reads.py +0 -0
  163. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/session.py +0 -0
  164. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/session/telemetry.py +0 -0
  165. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/sql_executor/MODULE_README.md +0 -0
  166. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/sql_executor/__init__.py +0 -0
  167. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/sql_executor/executor.py +0 -0
  168. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/sql_executor/queries.py +0 -0
  169. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/sql_executor/registry.py +0 -0
  170. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/sql_executor/types.py +0 -0
  171. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/sql_executor/utils.py +0 -0
  172. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/state.py +0 -0
  173. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/utils/__init__.py +0 -0
  174. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/utils/sql_utils.py +0 -0
  175. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/matrx_orm/utils/type_converters.py +0 -0
  176. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/scripts/git-branches.sh +0 -0
  177. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/scripts/publish.sh +0 -0
  178. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/scripts/release.sh +0 -0
  179. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/MODULE_README.md +0 -0
  180. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/__init__.py +0 -0
  181. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/conftest.py +0 -0
  182. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/MODULE_README.md +0 -0
  183. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/__init__.py +0 -0
  184. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_admin_db_columns.py +0 -0
  185. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_admin_search_negation.py +0 -0
  186. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_admin_search_sentinels.py +0 -0
  187. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_advisory_lock.py +0 -0
  188. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_agent_message.py +0 -0
  189. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_api_auth.py +0 -0
  190. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_api_config.py +0 -0
  191. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_api_handlers.py +0 -0
  192. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_api_protocol.py +0 -0
  193. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_array_agg_order_by.py +0 -0
  194. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_associations.py +0 -0
  195. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_bulk_update_by_pk.py +0 -0
  196. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
  197. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_cache_debug.py +0 -0
  198. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_call_function.py +0 -0
  199. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_case_when.py +0 -0
  200. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_composite_pk_filter.py +0 -0
  201. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_composite_pk_write_paths.py +0 -0
  202. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_config.py +0 -0
  203. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_conflict_target.py +0 -0
  204. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_conflict_writes.py +0 -0
  205. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_connection_codecs.py +0 -0
  206. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_connection_poison_guard.py +0 -0
  207. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_connection_primitives.py +0 -0
  208. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_count_composite_distinct.py +0 -0
  209. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_db_function_field.py +0 -0
  210. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_ddl_generator.py +0 -0
  211. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_derived_table.py +0 -0
  212. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_dirty_tracking.py +0 -0
  213. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_dynamic_crud.py +0 -0
  214. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_exceptions.py +0 -0
  215. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
  216. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_expression_primitives.py +0 -0
  217. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_fields.py +0 -0
  218. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
  219. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_from_alias.py +0 -0
  220. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_fts.py +0 -0
  221. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_func_expression_args.py +0 -0
  222. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_graph_walk.py +0 -0
  223. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_having_annotate_alias.py +0 -0
  224. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
  225. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_introspect_rls_policies.py +0 -0
  226. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_join_and_aggregate.py +0 -0
  227. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_latest_rows.py +0 -0
  228. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_listen_notify.py +0 -0
  229. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_managed_write_guard.py +0 -0
  230. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_matrx_entity.py +0 -0
  231. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_migration_diff_types.py +0 -0
  232. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_migration_loader.py +0 -0
  233. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_model_instance.py +0 -0
  234. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_model_meta.py +0 -0
  235. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_optimistic_concurrency.py +0 -0
  236. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_order_by_expression.py +0 -0
  237. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_parameter_name_hydration.py +0 -0
  238. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_patch_jsonb_path.py +0 -0
  239. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_pool_loop_recreation.py +0 -0
  240. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_pool_maintenance.py +0 -0
  241. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_postgrest_filters.py +0 -0
  242. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_pydantic_bridge.py +0 -0
  243. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_query_builder.py +0 -0
  244. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_query_error_detail.py +0 -0
  245. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_query_executor_sql.py +0 -0
  246. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_query_timeout.py +0 -0
  247. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
  248. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_queue_claim.py +0 -0
  249. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_registry.py +0 -0
  250. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_registry_multi_database.py +0 -0
  251. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_registry_multi_schema.py +0 -0
  252. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_relational_backfill.py +0 -0
  253. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_relations.py +0 -0
  254. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_resilience.py +0 -0
  255. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_session_advisory_lock.py +0 -0
  256. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_state_cache.py +0 -0
  257. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_subquery_filter_raw.py +0 -0
  258. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_subquery_in_filter.py +0 -0
  259. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_supabase_auth.py +0 -0
  260. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_supabase_config.py +0 -0
  261. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_tls.py +0 -0
  262. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_typed_join_projection.py +0 -0
  263. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_typed_predicates.py +0 -0
  264. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_update_case_expression.py +0 -0
  265. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_update_subquery.py +0 -0
  266. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_update_with_rebase.py +0 -0
  267. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_upsert_default_leak.py +0 -0
  268. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_upsert_with_conflict.py +0 -0
  269. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_values_jsonb_decode.py +0 -0
  270. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_watched_lifecycle.py +0 -0
  271. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level1/test_write_retry_semantics.py +0 -0
  272. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/MODULE_README.md +0 -0
  273. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/__init__.py +0 -0
  274. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/conftest.py +0 -0
  275. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_bulk_ops.py +0 -0
  276. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_cache_integration.py +0 -0
  277. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_crud.py +0 -0
  278. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_foreign_keys.py +0 -0
  279. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_m2m.py +0 -0
  280. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_manager.py +0 -0
  281. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_query_execution.py +0 -0
  282. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/level2/test_schema_diff.py +0 -0
  283. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project/.env.example +0 -0
  284. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project/README.md +0 -0
  285. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project/__init__.py +0 -0
  286. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project/generate.py +0 -0
  287. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project/generated/.gitkeep +0 -0
  288. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project/matrx_orm.yaml +0 -0
  289. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project/test_schema_generation.py +0 -0
  290. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project_desktop/.env.example +0 -0
  291. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project_desktop/README.md +0 -0
  292. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project_desktop/__init__.py +0 -0
  293. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project_desktop/client_example.py +0 -0
  294. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project_desktop/client_example.ts +0 -0
  295. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project_desktop/client_supabase_example.py +0 -0
  296. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/sample_project_desktop/server.py +0 -0
  297. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/entity_tests.py +0 -0
  298. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_base_generation.py +0 -0
  299. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_composite_pk_fk_generation.py +0 -0
  300. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_composite_unique_generation.py +0 -0
  301. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_dto_identity_generation.py +0 -0
  302. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_entity_capability_generation.py +0 -0
  303. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_generate_schema.py +0 -0
  304. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_junction_analysis.py +0 -0
  305. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_multi_schema_output.py +0 -0
  306. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_sql_expression_defaults.py +0 -0
  307. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema/test_view_generation.py +0 -0
  308. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema_builder/test_focused_generation_plan.py +0 -0
  309. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/schema_builder/test_host_models_aggregator.py +0 -0
  310. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/__init__.py +0 -0
  311. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/conftest.py +0 -0
  312. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_cache_no_downgrade.py +0 -0
  313. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_capture_poison_proof.py +0 -0
  314. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_coalesce.py +0 -0
  315. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_dag.py +0 -0
  316. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_disk_spill_fallback.py +0 -0
  317. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_flush_individual_fallback.py +0 -0
  318. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_governed_write.py +0 -0
  319. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_managed.py +0 -0
  320. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_op_enqueue_site.py +0 -0
  321. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_reads.py +0 -0
  322. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/session/test_session.py +0 -0
  323. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/test_database_name_alias.py +0 -0
  324. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/test_db_functions.py +0 -0
  325. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/test_model_cls_refactor.py +0 -0
  326. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/test_no_blocking_asyncpg_tls.py +0 -0
  327. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/test_one_database.py +0 -0
  328. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/test_secrets_battery.py +0 -0
  329. {matrx_orm-3.1.67 → matrx_orm-3.1.69}/tests/test_sql_param_casts.py +0 -0
  330. {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.67
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 from information_schema.tables."""
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 information_schema.tables
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 information_schema.schemata
69
- WHERE schema_name <> ALL($1::text[])
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 information_schema.tables
84
- WHERE table_name = $1
85
- AND table_schema <> ALL($2::text[])
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 information_schema.table_constraints tc
110
- JOIN information_schema.key_column_usage kcu
111
- ON tc.constraint_name = kcu.constraint_name
112
- AND tc.table_schema = kcu.table_schema
113
- AND tc.table_name = kcu.table_name
114
- WHERE tc.constraint_type = 'PRIMARY KEY'
115
- AND tc.table_schema = c.table_schema
116
- AND tc.table_name = c.table_name
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, table_name, COUNT(*)::int AS column_count
134
- FROM information_schema.columns
135
- WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
136
- GROUP BY table_schema, table_name
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 information_schema.columns col
238
- WHERE col.table_schema = et.schema_name
239
- AND col.table_name = et.table_name
240
- AND col.column_name = 'deleted_at'
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 information_schema.columns col
245
- WHERE col.table_schema = et.schema_name
246
- AND col.table_name = et.table_name
247
- AND col.column_name = 'version'
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 information_schema.tables tb
252
- WHERE tb.table_schema = et.schema_name
253
- AND tb.table_name = et.table_name
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 information_schema.tables
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 information_schema.columns o
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 information_schema.columns c
428
- JOIN information_schema.tables tb
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
- SELECT 1 FROM information_schema.columns c
606
- WHERE c.table_schema = n.nspname
607
- AND c.table_name = cl.relname
608
- AND c.column_name = miss.col
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
- _COLUMNS_SQL = """
75
- SELECT table_name, column_name
76
- FROM information_schema.columns
77
- WHERE table_schema = $1
78
- AND table_name = ANY($2::text[])
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
- ``information_schema.schemata`` probe.
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
- sql = "SELECT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = $1)"
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
- tc.table_schema,
25
- tc.constraint_name,
26
- tc.table_name,
27
- kcu.column_name,
28
- ccu.table_name AS foreign_table_name,
29
- ccu.column_name AS foreign_column_name,
30
- -- Check for unique constraints and primary keys
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 information_schema.table_constraints tc2
34
- WHERE tc2.table_schema = tc.table_schema
35
- AND tc2.table_name = tc.table_name
36
- AND tc2.constraint_type IN ('PRIMARY KEY', 'UNIQUE')
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 information_schema.table_constraints tc2
48
- WHERE tc2.table_schema = tc.table_schema
49
- AND tc2.table_name = ccu.table_name
50
- AND tc2.constraint_type IN ('PRIMARY KEY', 'UNIQUE')
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
- -- Check for junction tables (many-to-many)
76
+ -- More than one FK on the referencing table (junction candidate)
60
77
  (
61
- SELECT COUNT(*)
62
- FROM information_schema.table_constraints tc2
63
- WHERE tc2.table_schema = tc.table_schema
64
- AND tc2.table_name = tc.table_name
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
- information_schema.table_constraints AS tc
69
- JOIN information_schema.key_column_usage AS kcu
70
- ON tc.constraint_name = kcu.constraint_name
71
- AND tc.table_schema = kcu.table_schema
72
- JOIN information_schema.constraint_column_usage AS ccu
73
- ON ccu.constraint_name = tc.constraint_name
74
- AND ccu.table_schema = tc.table_schema
75
- WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = %s
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 information_schema.tables
80
- WHERE table_schema = %s AND table_type = 'BASE TABLE'
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 THEN 'self_reference'
87
- ELSE fk.foreign_table_name
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
- 'relationship_type',
94
- CASE
95
- WHEN fk.foreign_table_name = at.table_name THEN 'self-referential'
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) AS referenced_by
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 information_schema.columns col
57
- WHERE col.table_schema = et.schema_name
58
- AND col.table_name = et.table_name
59
- AND col.column_name = 'deleted_at'
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 information_schema.columns col
63
- WHERE col.table_schema = et.schema_name
64
- AND col.table_name = et.table_name
65
- AND col.column_name = 'organization_id'
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 information_schema.columns col
70
- WHERE col.table_schema = et.schema_name
71
- AND col.table_name = et.table_name
72
- AND col.column_name IN ('source_type', 'source_id', 'target_type', 'target_id')
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
- SELECT 1 FROM information_schema.tables
80
- WHERE table_schema = 'platform' AND table_name = 'entity_types'
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