matrx-orm 3.1.78__tar.gz → 3.1.80__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 (346) hide show
  1. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/CLAUDE.md +1 -0
  2. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/PKG-INFO +1 -1
  3. matrx_orm-3.1.80/matrx_orm/FEATURE.md +19 -0
  4. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/__init__.py +2 -1
  5. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/FEATURE.md +21 -3
  6. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/async_db_manager.py +58 -15
  7. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/exceptions.py +54 -28
  8. matrx_orm-3.1.80/matrx_orm/local_sqlite.py +71 -0
  9. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/query/FEATURE.md +13 -0
  10. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/query/builder.py +22 -12
  11. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/query/executor.py +2 -2
  12. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/pyproject.toml +1 -1
  13. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_connection_poison_guard.py +31 -0
  14. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_exceptions.py +38 -0
  15. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_local_sqlite.py +25 -1
  16. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_pool_maintenance.py +33 -0
  17. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_query_builder.py +28 -0
  18. matrx_orm-3.1.78/matrx_orm/local_sqlite.py +0 -40
  19. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/.gitignore +0 -0
  20. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/AGENTS.md +0 -0
  21. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/BASE_CLASS_METHODS.md +0 -0
  22. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/MODEL_API.md +0 -0
  23. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/README.md +0 -0
  24. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/database/orm/extended/managers/ai_model_base.py +0 -0
  25. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/docs/migrations.md +0 -0
  26. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/MODULE_README.md +0 -0
  27. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/adapters/MODULE_README.md +0 -0
  28. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/adapters/__init__.py +0 -0
  29. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/adapters/async_postgresql.py +0 -0
  30. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/adapters/base_adapter.py +0 -0
  31. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/adapters/postgresql.py +0 -0
  32. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
  33. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/adapters/supabase_adapter.py +0 -0
  34. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/admin/FEATURE.md +0 -0
  35. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/admin/__init__.py +0 -0
  36. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/admin/router.py +0 -0
  37. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/api/__init__.py +0 -0
  38. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/api/auth.py +0 -0
  39. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/api/config.py +0 -0
  40. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/api/handlers.py +0 -0
  41. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/api/protocol.py +0 -0
  42. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/api/server.py +0 -0
  43. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/associations.py +0 -0
  44. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/cache_debug.py +0 -0
  45. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/catalog.py +0 -0
  46. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/catalog_sql.py +0 -0
  47. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/client/MODULE_README.md +0 -0
  48. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/client/__init__.py +0 -0
  49. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/client/postgres_connection.py +0 -0
  50. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/client/postgrest.py +0 -0
  51. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/client/supabase_auth.py +0 -0
  52. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/client/supabase_config.py +0 -0
  53. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/client/supabase_manager.py +0 -0
  54. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/EXTENDED-TASK.md +0 -0
  55. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/MODULE_README.md +0 -0
  56. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
  57. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/TASKS.md +0 -0
  58. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/__init__.py +0 -0
  59. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/base.py +0 -0
  60. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/config.py +0 -0
  61. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/connections.py +0 -0
  62. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/db_function.py +0 -0
  63. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/diagnostics.py +0 -0
  64. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/expressions.py +0 -0
  65. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/extended.py +0 -0
  66. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/fields.py +0 -0
  67. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/introspection.py +0 -0
  68. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/listen.py +0 -0
  69. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/loop_filters.py +0 -0
  70. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/model_dto.py +0 -0
  71. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/model_view.py +0 -0
  72. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/one_database.py +0 -0
  73. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/paginator.py +0 -0
  74. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/pool_maintenance.py +0 -0
  75. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/pool_watch.py +0 -0
  76. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/pooler_role.py +0 -0
  77. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/pydantic_bridge.py +0 -0
  78. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/registry.py +0 -0
  79. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/relations.py +0 -0
  80. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/resilience.py +0 -0
  81. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/rls_session.py +0 -0
  82. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/signals.py +0 -0
  83. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/tls.py +0 -0
  84. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/transaction.py +0 -0
  85. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/types.py +0 -0
  86. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/core/write_queue.py +0 -0
  87. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/entity.py +0 -0
  88. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/error_handling.py +0 -0
  89. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/extended/__init__.py +0 -0
  90. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/extended/app_error_handler.py +0 -0
  91. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/identity_rule.py +0 -0
  92. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/middleware/__init__.py +0 -0
  93. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/middleware/base.py +0 -0
  94. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/migrations/MODULE_README.md +0 -0
  95. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/migrations/__init__.py +0 -0
  96. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/migrations/cli.py +0 -0
  97. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/migrations/ddl.py +0 -0
  98. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/migrations/diff.py +0 -0
  99. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/migrations/executor.py +0 -0
  100. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/migrations/integration.py +0 -0
  101. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/migrations/loader.py +0 -0
  102. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/migrations/operations.py +0 -0
  103. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/migrations/state.py +0 -0
  104. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/migrations/table_filter.py +0 -0
  105. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/FEATURE.md +0 -0
  106. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/MODULE_README.md +0 -0
  107. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/__init__.py +0 -0
  108. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/bulk_update_values.py +0 -0
  109. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/conflict.py +0 -0
  110. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/conflict_writes.py +0 -0
  111. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/create.py +0 -0
  112. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/db_functions.py +0 -0
  113. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/delete.py +0 -0
  114. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/dynamic_admin.py +0 -0
  115. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/dynamic_crud.py +0 -0
  116. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/graph_walk.py +0 -0
  117. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/latest_rows.py +0 -0
  118. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/queue_claim.py +0 -0
  119. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/read.py +0 -0
  120. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/relational_backfill.py +0 -0
  121. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/staging_load.py +0 -0
  122. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/operations/update.py +0 -0
  123. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/platform_db.py +0 -0
  124. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/py.typed +0 -0
  125. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/python_sql/MODULE_README.md +0 -0
  126. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/python_sql/__init__.py +0 -0
  127. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/python_sql/db_objects.py +0 -0
  128. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
  129. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
  130. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/query/MODULE_README.md +0 -0
  131. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/query/__init__.py +0 -0
  132. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/FEATURE.md +0 -0
  133. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/MODULE_README.md +0 -0
  134. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/__init__.py +0 -0
  135. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/code_handler.py +0 -0
  136. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/codegen_writer.py +0 -0
  137. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/columns.py +0 -0
  138. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/common.py +0 -0
  139. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/diff_preview.py +0 -0
  140. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
  141. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/generator.py +0 -0
  142. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
  143. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
  144. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
  145. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
  146. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/package_wiring.py +0 -0
  147. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/relationships.py +0 -0
  148. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/runner.py +0 -0
  149. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/schema.py +0 -0
  150. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/schema_manager.py +0 -0
  151. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/tables.py +0 -0
  152. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/schema_builder/views.py +0 -0
  153. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/secrets_battery/FEATURE.md +0 -0
  154. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/secrets_battery/__init__.py +0 -0
  155. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/secrets_battery/attachments.py +0 -0
  156. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/secrets_battery/crypto.py +0 -0
  157. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/secrets_battery/items.py +0 -0
  158. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/secrets_battery/model.py +0 -0
  159. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/secrets_battery/org_service.py +0 -0
  160. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/secrets_battery/service.py +0 -0
  161. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/session/README.md +0 -0
  162. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/session/__init__.py +0 -0
  163. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/session/coalesce.py +0 -0
  164. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/session/dag.py +0 -0
  165. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/session/errors.py +0 -0
  166. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/session/fallback.py +0 -0
  167. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/session/flush.py +0 -0
  168. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/session/lifecycle.py +0 -0
  169. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/session/managed.py +0 -0
  170. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/session/op.py +0 -0
  171. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/session/reads.py +0 -0
  172. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/session/session.py +0 -0
  173. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/session/telemetry.py +0 -0
  174. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/sql_executor/MODULE_README.md +0 -0
  175. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/sql_executor/__init__.py +0 -0
  176. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/sql_executor/executor.py +0 -0
  177. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/sql_executor/queries.py +0 -0
  178. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/sql_executor/registry.py +0 -0
  179. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/sql_executor/types.py +0 -0
  180. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/sql_executor/utils.py +0 -0
  181. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/state.py +0 -0
  182. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/utils/__init__.py +0 -0
  183. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/utils/sql_utils.py +0 -0
  184. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/matrx_orm/utils/type_converters.py +0 -0
  185. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/scripts/git-branches.sh +0 -0
  186. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/scripts/publish.sh +0 -0
  187. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/scripts/release.sh +0 -0
  188. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/MODULE_README.md +0 -0
  189. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/__init__.py +0 -0
  190. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/conftest.py +0 -0
  191. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/MODULE_README.md +0 -0
  192. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/__init__.py +0 -0
  193. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_admin_db_columns.py +0 -0
  194. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_admin_response_rendering.py +0 -0
  195. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_admin_search_negation.py +0 -0
  196. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_admin_search_sentinels.py +0 -0
  197. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_advisory_lock.py +0 -0
  198. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_agent_message.py +0 -0
  199. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_api_auth.py +0 -0
  200. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_api_config.py +0 -0
  201. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_api_handlers.py +0 -0
  202. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_api_protocol.py +0 -0
  203. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_array_agg_order_by.py +0 -0
  204. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_associations.py +0 -0
  205. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_bulk_hydration_offload.py +0 -0
  206. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_bulk_update_by_pk.py +0 -0
  207. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
  208. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_cache_debug.py +0 -0
  209. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_call_function.py +0 -0
  210. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_case_when.py +0 -0
  211. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_catalog.py +0 -0
  212. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_composite_pk_filter.py +0 -0
  213. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_composite_pk_write_paths.py +0 -0
  214. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_config.py +0 -0
  215. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_conflict_target.py +0 -0
  216. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_conflict_writes.py +0 -0
  217. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_connection_codecs.py +0 -0
  218. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_connection_primitives.py +0 -0
  219. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_count_composite_distinct.py +0 -0
  220. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_db_function_field.py +0 -0
  221. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_ddl_generator.py +0 -0
  222. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_derived_table.py +0 -0
  223. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_dirty_tracking.py +0 -0
  224. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_dynamic_crud.py +0 -0
  225. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
  226. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_expression_primitives.py +0 -0
  227. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_fields.py +0 -0
  228. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
  229. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_from_alias.py +0 -0
  230. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_fts.py +0 -0
  231. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_func_expression_args.py +0 -0
  232. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_graph_walk.py +0 -0
  233. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_having_annotate_alias.py +0 -0
  234. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
  235. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_introspect_rls_policies.py +0 -0
  236. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_join_and_aggregate.py +0 -0
  237. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_latest_rows.py +0 -0
  238. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_listen_notify.py +0 -0
  239. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_managed_write_guard.py +0 -0
  240. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_matrx_entity.py +0 -0
  241. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_migration_diff_types.py +0 -0
  242. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_migration_loader.py +0 -0
  243. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_model_instance.py +0 -0
  244. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_model_meta.py +0 -0
  245. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_optimistic_concurrency.py +0 -0
  246. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_order_by_expression.py +0 -0
  247. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_parameter_name_hydration.py +0 -0
  248. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_patch_jsonb_path.py +0 -0
  249. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_pool_holder_census.py +0 -0
  250. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_pool_loop_recreation.py +0 -0
  251. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_postgrest_filters.py +0 -0
  252. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_pydantic_bridge.py +0 -0
  253. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_query_error_detail.py +0 -0
  254. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_query_executor_sql.py +0 -0
  255. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_query_timeout.py +0 -0
  256. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
  257. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_queue_claim.py +0 -0
  258. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_registry.py +0 -0
  259. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_registry_multi_database.py +0 -0
  260. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_registry_multi_schema.py +0 -0
  261. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_relational_backfill.py +0 -0
  262. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_relations.py +0 -0
  263. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_resilience.py +0 -0
  264. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_rls_session_scope.py +0 -0
  265. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_schema_exists.py +0 -0
  266. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_session_advisory_lock.py +0 -0
  267. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_state_cache.py +0 -0
  268. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_subquery_filter_raw.py +0 -0
  269. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_subquery_in_filter.py +0 -0
  270. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_supabase_auth.py +0 -0
  271. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_supabase_config.py +0 -0
  272. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_tls.py +0 -0
  273. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_typed_join_projection.py +0 -0
  274. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_typed_predicates.py +0 -0
  275. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_update_case_expression.py +0 -0
  276. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_update_subquery.py +0 -0
  277. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_update_with_rebase.py +0 -0
  278. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_upsert_default_leak.py +0 -0
  279. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_upsert_with_conflict.py +0 -0
  280. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_values_jsonb_decode.py +0 -0
  281. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_watched_lifecycle.py +0 -0
  282. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level1/test_write_retry_semantics.py +0 -0
  283. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level2/MODULE_README.md +0 -0
  284. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level2/__init__.py +0 -0
  285. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level2/conftest.py +0 -0
  286. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level2/test_bulk_ops.py +0 -0
  287. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level2/test_cache_integration.py +0 -0
  288. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level2/test_crud.py +0 -0
  289. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level2/test_foreign_keys.py +0 -0
  290. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level2/test_m2m.py +0 -0
  291. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level2/test_manager.py +0 -0
  292. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level2/test_migrations_live.py +0 -0
  293. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level2/test_query_execution.py +0 -0
  294. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/level2/test_schema_diff.py +0 -0
  295. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/live_pooler_role.py +0 -0
  296. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/sample_project/.env.example +0 -0
  297. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/sample_project/README.md +0 -0
  298. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/sample_project/__init__.py +0 -0
  299. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/sample_project/generate.py +0 -0
  300. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/sample_project/generated/.gitkeep +0 -0
  301. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/sample_project/matrx_orm.yaml +0 -0
  302. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/sample_project/test_schema_generation.py +0 -0
  303. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/sample_project_desktop/.env.example +0 -0
  304. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/sample_project_desktop/README.md +0 -0
  305. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/sample_project_desktop/__init__.py +0 -0
  306. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/sample_project_desktop/client_example.py +0 -0
  307. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/sample_project_desktop/client_example.ts +0 -0
  308. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/sample_project_desktop/client_supabase_example.py +0 -0
  309. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/sample_project_desktop/server.py +0 -0
  310. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/schema/entity_tests.py +0 -0
  311. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/schema/test_base_generation.py +0 -0
  312. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/schema/test_composite_pk_fk_generation.py +0 -0
  313. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/schema/test_composite_unique_generation.py +0 -0
  314. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/schema/test_dto_identity_generation.py +0 -0
  315. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/schema/test_entity_capability_generation.py +0 -0
  316. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/schema/test_generate_schema.py +0 -0
  317. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/schema/test_junction_analysis.py +0 -0
  318. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/schema/test_multi_schema_output.py +0 -0
  319. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/schema/test_sql_expression_defaults.py +0 -0
  320. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/schema/test_table_relationships_query.py +0 -0
  321. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/schema/test_view_generation.py +0 -0
  322. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/schema_builder/test_array_default_parsing.py +0 -0
  323. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/schema_builder/test_focused_generation_plan.py +0 -0
  324. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/schema_builder/test_host_models_aggregator.py +0 -0
  325. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/session/__init__.py +0 -0
  326. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/session/conftest.py +0 -0
  327. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/session/test_cache_no_downgrade.py +0 -0
  328. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/session/test_capture_poison_proof.py +0 -0
  329. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/session/test_coalesce.py +0 -0
  330. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/session/test_dag.py +0 -0
  331. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/session/test_disk_spill_fallback.py +0 -0
  332. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/session/test_flush_individual_fallback.py +0 -0
  333. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/session/test_governed_write.py +0 -0
  334. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/session/test_managed.py +0 -0
  335. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/session/test_op_enqueue_site.py +0 -0
  336. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/session/test_reads.py +0 -0
  337. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/session/test_session.py +0 -0
  338. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/test_database_name_alias.py +0 -0
  339. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/test_db_functions.py +0 -0
  340. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/test_model_cls_refactor.py +0 -0
  341. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/test_no_blocking_asyncpg_tls.py +0 -0
  342. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/test_one_database.py +0 -0
  343. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/test_pooler_role_guard.py +0 -0
  344. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/test_secrets_battery.py +0 -0
  345. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/test_sql_param_casts.py +0 -0
  346. {matrx_orm-3.1.78 → matrx_orm-3.1.80}/tests/test_sync_twins_guard.py +0 -0
@@ -29,6 +29,7 @@ Every table that becomes a Model needs row identity: a single-column primary key
29
29
  - **Read-only models** (`_read_only = True`): a model that can be queried but NEVER written through the ORM — every write path raises `ReadOnlyModelError` (enforced at `QueryExecutor.insert/bulk_insert/upsert/update/delete`, so it covers all of `create`/`save`/`update`/`delete`/`update_where`/`delete_where`/`upsert`/`bulk_*`). Used for DB-managed tables the app must not mutate. The canonical **`auth.users`** model is baked into the schema generator (`get_string_user_model`) as the full, curated Supabase auth schema (safe columns only — no password/token fields), `_read_only`, bound per-database. Read a user's profile/metadata there; mutate users only through Supabase Auth (GoTrue).
30
30
  - **Connection-level type codecs** (`async_db_manager._init_connection_default`): every pooled connection decodes **uuid → `str`**, **vector → `list[float]`**, and **json/jsonb → `dict`/`list`** at the driver boundary — the TYPE decides the decode, never the call path. So `call_function` scalar/rows, raw fetches, and field hydration all agree; a caller must **never `json.loads` a fetched json/jsonb value** (it's already decoded; the 2026-07-13 `build_agent_context` crash was a jsonb RPC fetched pre-codec as a raw string). Encoding: a pre-serialised JSON `str` passes through untouched; a native dict/list is dumped (`_encode_json`). Pinned by `tests/level1/test_connection_codecs.py`.
31
31
  - **Connection pools are event-loop-owned.** `AsyncDatabaseManager.get_pool()` recreates a pool when a caller arrives on a different loop. If the former owner loop is already closed, stale-pool termination and cleanup discard that pool without trying to schedule callbacks onto the dead loop; unrelated runtime errors still raise. Pinned by `tests/level1/test_pool_loop_recreation.py`.
32
+ - **Exception diagnostics never read source files.** `ORMException` captures compact caller metadata by walking live frames; `traceback.extract_stack()` is forbidden because its `linecache` filesystem checks can stall an async server. Contract: [`matrx_orm/FEATURE.md`](matrx_orm/FEATURE.md).
32
33
  - **A zero-connection pool is dead, not busy.** After bounded SQL-safe acquire retries, `AsyncDatabaseManager.get_connection()` atomically replaces a pool whose physical size is still zero, then tries once against the replacement. It never replaces a busy `size > 0` pool. Contract: [`matrx_orm/core/FEATURE.md`](matrx_orm/core/FEATURE.md).
33
34
  - **Query layer**: `QueryBuilder`, typed predicates/projections (`F`, `Q`,
34
35
  `Predicate`, `Projection`), window functions, QueryBuilder-backed CTEs, and
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: matrx-orm
3
- Version: 3.1.78
3
+ Version: 3.1.80
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
@@ -0,0 +1,19 @@
1
+ # Matrx ORM runtime contracts
2
+
3
+ ## Exception diagnostics stay event-loop safe
4
+
5
+ - **Caller capture is metadata-only.** `_capture_caller_frames()` walks live Python frames and reads only code filename, function name, and line number.
6
+ - **Source-file reads are forbidden during exception construction.** Never use `traceback.extract_stack()` here: it enters `linecache.checkcache()` and synchronously stats source files, which can freeze every request in an async server.
7
+ - **Diagnostic value is preserved.** `ORMException._caller_frames` retains up to three external application frames in oldest-to-newest order while excluding ORM, site-package, frozen-import, and runpy frames.
8
+ - **The regression guard is direct.** `tests/level1/test_exceptions.py::TestORMExceptionBase::test_caller_capture_never_reads_source_files` makes traceback extraction fail and proves exception construction still captures the application call site.
9
+
10
+ ## Entry points
11
+
12
+ - Runtime hierarchy and formatting: `exceptions.py`
13
+ - Focused unit coverage: `tests/level1/test_exceptions.py`
14
+
15
+ ## Change Log
16
+
17
+ - 2026-08-18 — Replaced traceback extraction with live-frame metadata walking so ordinary ORM exceptions cannot synchronously stat source files on the event loop.
18
+
19
+ Verified against `exceptions.py` and `tests/level1/test_exceptions.py` on 2026-08-18.
@@ -43,7 +43,7 @@ from .platform_db import (
43
43
  platform_connection_url,
44
44
  register_platform_db,
45
45
  )
46
- from .local_sqlite import SQLiteValue, read_local_sqlite_rows
46
+ from .local_sqlite import SQLiteValue, check_local_sqlite_integrity, read_local_sqlite_rows
47
47
  from .core.diagnostics import collect_pool_diagnostics, log_pool_diagnostics
48
48
  from .core.introspection import introspect_columns, introspect_rls_policies, schema_exists
49
49
  from .core.one_database import OneDatabaseReport, PoolIdentity, audit_one_database
@@ -639,6 +639,7 @@ __all__ = [
639
639
  "open_listener_connection",
640
640
  # ── Read-only local SQLite artifacts ──────────────────────────────────────
641
641
  "SQLiteValue",
642
+ "check_local_sqlite_integrity",
642
643
  "read_local_sqlite_rows",
643
644
  # ── Cache ──────────────────────────────────────────────────────────────────
644
645
  "CachePolicy",
@@ -36,7 +36,12 @@ labels (`unique`, `foreign_key`, `not_null`, `check`, `exclusion`, and
36
36
  It is diagnostic and intentionally verbose, so a known PostgreSQL error must
37
37
  never reach that fallback.
38
38
 
39
- Verified against code: 2026-08-09.
39
+ `QueryTimeoutError` diagnoses by SQL verb. A fresh `INSERT` points first to a
40
+ relation/DDL lock convoy, FK parent lock, or trigger work—not same-row fan-out.
41
+ `UPDATE`/`DELETE` retain the hot-row diagnosis while also naming a database-wide
42
+ DDL convoy when unrelated relations fail in the same window.
43
+
44
+ Verified against code: 2026-08-17.
40
45
 
41
46
  ## Pool recovery
42
47
 
@@ -66,12 +71,19 @@ census, so retry/persistence casualties preserve the diagnosis.
66
71
  shielded task with a 10-second asyncpg release timeout. A holder remains visible
67
72
  as `releasing` until reset really finishes. If reset exceeds the bound, asyncpg
68
73
  discards the physical connection and the failure is logged loudly rather than
69
- leaving an invisible checkout forever.
74
+ leaving an invisible checkout forever. **A release timeout never replaces an
75
+ already-completed query result** with a request failure; the discarded connection
76
+ is the isolation boundary.
77
+
78
+ **Pool growth never blocks reuse.** Only physical connection setup is serialized.
79
+ Waiters behind a slow TLS/database handshake continue checking for healthy holders
80
+ released by normal traffic, so an available connection cannot sit idle until the
81
+ outer acquire deadline expires.
70
82
 
71
83
  Pinned by `tests/level1/test_pool_holder_census.py` and
72
84
  `tests/level1/test_pool_maintenance.py`.
73
85
 
74
- Verified against code: 2026-08-15.
86
+ Verified against code: 2026-08-18.
75
87
 
76
88
  ## Transaction-pooled role scope
77
89
 
@@ -107,6 +119,12 @@ Verified against code: 2026-08-17.
107
119
 
108
120
  ## Change log
109
121
 
122
+ - 2026-08-18: Preserved successful query results when asyncpg times out while
123
+ resetting and discarding the released connection.
124
+ - 2026-08-18: Let waiters reuse healthy connections released behind a slow
125
+ serialized-growth handshake.
126
+ - 2026-08-17: Corrected INSERT timeout guidance to identify relation/DDL lock
127
+ convoys before application row contention.
110
128
  - 2026-08-17: Required direct before/after role evidence before recording
111
129
  contamination; concurrent empty-to-nonempty reads reconcile without a false
112
130
  repair-queue row, and session-authorization contamination is reset too.
@@ -120,6 +120,7 @@ READ_TIMEOUT_RETRY_BACKOFF = 0.25 # seconds before the read retry
120
120
  POOL_MAINTENANCE_SWEEP_SECONDS = 45.0
121
121
  POOL_MAINTENANCE_MIN_INTERVAL = 0.25
122
122
  POOL_MAINTENANCE_MAX_BACKOFF = 30.0
123
+ POOL_GROWTH_GATE_POLL_SECONDS = 0.05
123
124
 
124
125
 
125
126
  try:
@@ -708,21 +709,45 @@ class AsyncDatabaseManager:
708
709
  try:
709
710
  ready = await acquire_connected_holder(pool)
710
711
  except UnsupportedPoolInternalsError:
711
- ready = None
712
+ lock = cls._growth_locks.setdefault(config_name, asyncio.Lock())
713
+ async with lock:
714
+ return await pool.acquire()
712
715
  if ready is not None:
713
716
  return ready
714
717
 
715
718
  lock = cls._growth_locks.setdefault(config_name, asyncio.Lock())
716
- async with lock:
717
- # Maintenance or the previous waiter may have established/released
718
- # a ready holder while this caller waited for the growth gate.
719
+ while True:
719
720
  try:
720
- ready = await acquire_connected_holder(pool)
721
- except UnsupportedPoolInternalsError:
722
- ready = None
723
- if ready is not None:
724
- return ready
725
- return await pool.acquire()
721
+ await asyncio.wait_for(
722
+ lock.acquire(),
723
+ timeout=POOL_GROWTH_GATE_POLL_SECONDS,
724
+ )
725
+ except TimeoutError:
726
+ # A slow TLS/database handshake owns the growth gate. Traffic
727
+ # must still consume healthy connections released meanwhile;
728
+ # otherwise those slots sit idle while every waiter times out
729
+ # behind work that has no bearing on them.
730
+ try:
731
+ ready = await acquire_connected_holder(pool)
732
+ except UnsupportedPoolInternalsError:
733
+ async with lock:
734
+ return await pool.acquire()
735
+ if ready is not None:
736
+ return ready
737
+ continue
738
+
739
+ try:
740
+ # Maintenance or the previous waiter may have established or
741
+ # released a ready holder while this caller waited briefly.
742
+ try:
743
+ ready = await acquire_connected_holder(pool)
744
+ except UnsupportedPoolInternalsError:
745
+ return await pool.acquire()
746
+ if ready is not None:
747
+ return ready
748
+ return await pool.acquire()
749
+ finally:
750
+ lock.release()
726
751
 
727
752
  @classmethod
728
753
  @asynccontextmanager
@@ -1019,13 +1044,30 @@ class AsyncDatabaseManager:
1019
1044
  except Exception:
1020
1045
  pass
1021
1046
 
1022
- release_task.add_done_callback(_release_done)
1023
1047
  try:
1024
1048
  await asyncio.shield(release_task)
1025
1049
  except asyncio.CancelledError:
1026
1050
  # The dedicated release task keeps running to completion and its
1027
1051
  # body/callback own census cleanup and recovery-latch reset.
1052
+ release_task.add_done_callback(_release_done)
1028
1053
  raise
1054
+ except TimeoutError as release_exc:
1055
+ # asyncpg terminates a connection whose reset exceeds the
1056
+ # release timeout. The query already completed successfully, so
1057
+ # this cleanup failure must not replace its result with a false
1058
+ # request crash. Keep the pool-health signal loud.
1059
+ logger.error(
1060
+ "[matrx_orm] Connection release timed out for '%s'; "
1061
+ "asyncpg discarded the connection: %s",
1062
+ config_name,
1063
+ release_exc,
1064
+ )
1065
+ else:
1066
+ try:
1067
+ if pool.get_idle_size() > 0:
1068
+ note_pool_recovered(config_name)
1069
+ except Exception:
1070
+ pass
1029
1071
 
1030
1072
  @classmethod
1031
1073
  async def execute_query(
@@ -1102,8 +1144,9 @@ class AsyncDatabaseManager:
1102
1144
  # asyncpg's command_timeout fired — query held a connection
1103
1145
  # but didn't return in time. NOT a pool-acquire timeout
1104
1146
  # (that's PoolAcquireTimeoutError, raised in get_connection
1105
- # before the connection is held). Most common cause:
1106
- # row-lock contention on a hot row.
1147
+ # before the connection is held). Diagnose by query verb:
1148
+ # INSERT commonly means relation/DDL/FK/trigger waits, while
1149
+ # UPDATE/DELETE commonly means a hot-row lock.
1107
1150
  raise QueryTimeoutError(
1108
1151
  operation="execute_query",
1109
1152
  query=query,
@@ -1225,8 +1268,8 @@ class AsyncDatabaseManager:
1225
1268
  ) from None
1226
1269
  except TimeoutError as e:
1227
1270
  # asyncpg's command_timeout fired — query held a
1228
- # connection but didn't return in time. See twin comment
1229
- # in the tx_conn branch above for the full rationale.
1271
+ # connection but didn't return in time. See the twin
1272
+ # query-verb diagnosis in the tx_conn branch above.
1230
1273
  # A READ is side-effect-free → retry once on a fresh
1231
1274
  # connection before surfacing the timeout (self-heal for
1232
1275
  # starvation blips). Writes fall straight through to raise.
@@ -2,6 +2,7 @@ import asyncio
2
2
  import re
3
3
  import socket
4
4
  import ssl
5
+ import sys
5
6
  import traceback as _tb
6
7
  from collections.abc import Mapping
7
8
  from dataclasses import dataclass
@@ -304,12 +305,17 @@ def _capture_caller_frames() -> list[str]:
304
305
  section only contains application code written by the user.
305
306
  """
306
307
  frames: list[str] = []
307
- for fi in reversed(_tb.extract_stack()[:-1]):
308
- if any(marker in fi.filename for marker in _EXCLUDED_FRAME_MARKERS):
309
- continue
310
- frames.append(f' File "{fi.filename}", line {fi.lineno}, in {fi.name}')
311
- if len(frames) >= 3:
312
- break
308
+ frame = sys._getframe(1)
309
+ try:
310
+ while frame is not None and len(frames) < 3:
311
+ filename = frame.f_code.co_filename
312
+ if not any(marker in filename for marker in _EXCLUDED_FRAME_MARKERS):
313
+ frames.append(
314
+ f' File "{filename}", line {frame.f_lineno}, in {frame.f_code.co_name}'
315
+ )
316
+ frame = frame.f_back
317
+ finally:
318
+ del frame
313
319
  frames.reverse()
314
320
  return frames
315
321
 
@@ -1008,10 +1014,11 @@ class QueryTimeoutError(DatabaseError):
1008
1014
  timeout = self.details.get("timeout")
1009
1015
  query = self._str("query")
1010
1016
  args = self.details.get("args", [])
1011
- # First-token heuristic: SELECT timeouts and UPDATE/INSERT/DELETE
1012
- # timeouts have totally different root-cause distributions, so
1013
- # the hint changes accordingly. Reads almost never row-lock under
1014
- # MVCC; writes almost always do under fan-out concurrency.
1017
+ # First-token heuristic: SELECT, INSERT, and UPDATE/DELETE timeouts
1018
+ # have different root-cause distributions, so the hint changes
1019
+ # accordingly. In particular, a fresh INSERT cannot be contending on
1020
+ # the row it is creating; relation/DDL locks and FK/trigger work must
1021
+ # be investigated before blaming application fan-out.
1015
1022
  query_kind = _classify_query_shape(query)
1016
1023
  lines = ["\n" + "-" * 80]
1017
1024
  lines.append("Matrx ORM | QueryTimeoutError")
@@ -1054,25 +1061,40 @@ class QueryTimeoutError(DatabaseError):
1054
1061
  lines.append(" - Safe to retry ONCE for cold-cache / transient cases. If it times out")
1055
1062
  lines.append(" twice in a row, fix the index — retrying won't help.")
1056
1063
  elif query_kind == "write":
1057
- # Writes are where row-lock contention actually bites.
1058
- lines.append(
1059
- " - Write query ({op}). Most common cause: row-lock contention.".format(
1060
- op=query_kind.upper()
1061
- if query_kind != "write"
1062
- else (query.lstrip().split(None, 1)[0].upper() if query else "WRITE")
1064
+ write_verb = query.lstrip().split(None, 1)[0].upper() if query else "WRITE"
1065
+ lines.append(f" - Write query ({write_verb}).")
1066
+ if write_verb == "INSERT":
1067
+ lines.append(
1068
+ " - A fresh INSERT is not waiting on the row it is creating. Check, in"
1063
1069
  )
1064
- )
1065
- lines.append(" If N async tasks UPDATE the same row in a tight loop they serialize")
1066
- lines.append(" on the row lock and the late ones blow past command_timeout. Either")
1067
- lines.append(" (a) batch the writes, (b) replace read-modify-write with an atomic")
1068
- lines.append(" SQL UPDATE (e.g. ``col = col || jsonb_build_object(...)``),")
1069
- lines.append(" or (c) gate the fan-out via RequestLane ``db_write`` bucket caps.")
1070
- lines.append(" - Less common: missing index on a WHERE clause (forces a slow scan to")
1071
- lines.append(" locate the row to update), server-side overload, statement_timeout.")
1070
+ lines.append(
1071
+ " order: a queued live DDL/relation lock; FK parent-row locks; trigger"
1072
+ )
1073
+ lines.append(
1074
+ " work; then database-wide overload. Correlate other unrelated query"
1075
+ )
1076
+ lines.append(
1077
+ " timeouts in the same window before changing application concurrency."
1078
+ )
1079
+ else:
1080
+ lines.append(
1081
+ " - UPDATE/DELETE commonly wait on a hot row. If N tasks change the same"
1082
+ )
1083
+ lines.append(
1084
+ " row, batch them, use one atomic SQL update, or gate that write lane."
1085
+ )
1086
+ lines.append(
1087
+ " - Also check for a queued live DDL/relation lock when unrelated tables"
1088
+ )
1089
+ lines.append(
1090
+ " time out in the same window; that is a database-wide lock convoy."
1091
+ )
1092
+ lines.append(" - A missing WHERE-clause index, trigger work, or sustained server load")
1093
+ lines.append(" can also exceed command_timeout.")
1072
1094
  lines.append(
1073
- " - NOT safe to retry blindly — if it's row-lock contention, a retry just"
1095
+ " - NOT safe to retry blindly — a write may have ambiguous completion, and"
1074
1096
  )
1075
- lines.append(" rejoins the back of the lock queue and times out again.")
1097
+ lines.append(" an unchanged lock convoy will time out again.")
1076
1098
  else:
1077
1099
  # DDL / CALL / unknown — keep both hints concise.
1078
1100
  lines.append(" - Couldn't classify the query shape — see the Query line above. Common")
@@ -1204,11 +1226,15 @@ class IntegrityError(DatabaseError):
1204
1226
  lines.append(' `null value in column "<name>"` and the rest of the message.')
1205
1227
  lines.append(" - Pass the field explicitly (or set a default on the model).")
1206
1228
  elif constraint == "check":
1207
- lines.append(" - A CHECK constraint rejected values that violate a database invariant.")
1229
+ lines.append(
1230
+ " - A CHECK constraint rejected values that violate a database invariant."
1231
+ )
1208
1232
  lines.append(" - Read the DB error above for the invariant and offending row.")
1209
1233
  lines.append(" - This is not transient; correct the input or the write ordering.")
1210
1234
  elif constraint == "exclusion":
1211
- lines.append(" - This row conflicts with an existing row under an exclusion constraint.")
1235
+ lines.append(
1236
+ " - This row conflicts with an existing row under an exclusion constraint."
1237
+ )
1212
1238
  lines.append(" - Read the DB error above for the conflicting key or range.")
1213
1239
  lines.append(" - This is not safe to blindly retry with unchanged input.")
1214
1240
  elif constraint == "restrict":
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import sqlite3
5
+ from collections.abc import Sequence
6
+ from pathlib import Path
7
+ from collections.abc import Mapping
8
+
9
+ type SQLiteValue = str | int | float | bytes | None
10
+
11
+ _IDENTIFIER = re.compile(r"[A-Za-z_][A-Za-z0-9_]*\Z")
12
+
13
+
14
+ def _identifier(value: str) -> str:
15
+ if not _IDENTIFIER.fullmatch(value):
16
+ raise ValueError(f"invalid SQLite identifier: {value!r}")
17
+ return f'"{value}"'
18
+
19
+
20
+ def read_local_sqlite_rows(
21
+ database: str | Path,
22
+ *,
23
+ table: str,
24
+ columns: Sequence[str],
25
+ order_by: Sequence[str] = (),
26
+ where_not_null: Sequence[str] = (),
27
+ min_lengths: Mapping[str, int] | None = None,
28
+ limit: int | None = None,
29
+ ) -> list[tuple[SQLiteValue, ...]]:
30
+ """Read a projected table from a local SQLite artifact in read-only mode."""
31
+ if not columns:
32
+ raise ValueError("columns must contain at least one identifier")
33
+
34
+ projection = ", ".join(_identifier(column) for column in columns)
35
+ statement = f"SELECT {projection} FROM {_identifier(table)}"
36
+ predicates: list[str] = []
37
+ parameters: list[int] = []
38
+ for column in where_not_null:
39
+ predicates.append(f"{_identifier(column)} IS NOT NULL")
40
+ for column, minimum in (min_lengths or {}).items():
41
+ if minimum < 0:
42
+ raise ValueError("minimum lengths must be non-negative")
43
+ predicates.append(f"length({_identifier(column)}) >= ?")
44
+ parameters.append(minimum)
45
+ if predicates:
46
+ statement += " WHERE " + " AND ".join(predicates)
47
+ if order_by:
48
+ statement += " ORDER BY " + ", ".join(_identifier(column) for column in order_by)
49
+ if limit is not None:
50
+ if limit <= 0:
51
+ raise ValueError("limit must be positive")
52
+ statement += " LIMIT ?"
53
+ parameters.append(limit)
54
+
55
+ path = Path(database).expanduser().resolve()
56
+ connection = sqlite3.connect(f"{path.as_uri()}?mode=ro", uri=True)
57
+ try:
58
+ return [tuple(row) for row in connection.execute(statement, parameters)]
59
+ finally:
60
+ connection.close()
61
+
62
+
63
+ def check_local_sqlite_integrity(database: str | Path) -> str:
64
+ """Return SQLite's read-only quick-check result for a local artifact."""
65
+ path = Path(database).expanduser().resolve()
66
+ connection = sqlite3.connect(f"{path.as_uri()}?mode=ro", uri=True)
67
+ try:
68
+ row = connection.execute("PRAGMA quick_check").fetchone()
69
+ return str(row[0]) if row else ""
70
+ finally:
71
+ connection.close()
@@ -3,6 +3,18 @@
3
3
  `QueryBuilder` is the typed terminal-read and write layer behind every model.
4
4
  Application packages compose queries here; they never bypass it with SQL.
5
5
 
6
+ ## Async query compilation
7
+
8
+ **Every async terminal compiles SQL in a worker thread.** `all`, `first`,
9
+ `get`, `update`, `delete`, `count`, `exists`, `aggregate`, `values`,
10
+ `values_list`, `fetch_via`, and async iteration must reach their first database
11
+ await without running `QueryBuilder._build_query` or `QueryExecutor._build_query`
12
+ on the event-loop thread. Query shapes are usually small, but concurrent or
13
+ expression-heavy builders are synchronous Python work and cannot freeze every
14
+ request in the process.
15
+
16
+ `compile()` remains deliberately synchronous for synchronous callers.
17
+
6
18
  ## Large scans and the identity cache
7
19
 
8
20
  `.all()` populates `StateManager` by default so ordinary repeated identity reads
@@ -34,5 +46,6 @@ without forcing callers to add a redundant projection.
34
46
 
35
47
  ## Change log
36
48
 
49
+ - 2026-08-18 — Moved async terminal SQL compilation off the event loop.
37
50
  - 2026-08-15 — Moved bulk hydration off the event loop, made complete-primary-key
38
51
  misses constant-time, and documented cache bypass for request-local scans.
@@ -805,6 +805,9 @@ class QueryBuilder(Generic[ModelT]):
805
805
  def _get_executor(self) -> QueryExecutor:
806
806
  return QueryExecutor(self._build_query())
807
807
 
808
+ async def _get_executor_async(self) -> QueryExecutor:
809
+ return await asyncio.to_thread(self._get_executor)
810
+
808
811
  # ------------------------------------------------------------------
809
812
  # Terminal async methods
810
813
  # ------------------------------------------------------------------
@@ -822,7 +825,7 @@ class QueryBuilder(Generic[ModelT]):
822
825
  See matrx_orm.session.reads.merge_pending_with_db.
823
826
  """
824
827
  try:
825
- executor = self._get_executor()
828
+ executor = await self._get_executor_async()
826
829
  results = await executor.all()
827
830
  if use_cache:
828
831
  await StateManager.cache_bulk(self.model, results)
@@ -851,7 +854,7 @@ class QueryBuilder(Generic[ModelT]):
851
854
 
852
855
  Session-aware via the same merge pathway as :meth:`all`."""
853
856
  try:
854
- executor = self._get_executor()
857
+ executor = await self._get_executor_async()
855
858
  results = await executor.all()
856
859
  merged = _merge_pending_into_models(
857
860
  self.model,
@@ -878,7 +881,7 @@ class QueryBuilder(Generic[ModelT]):
878
881
  """
879
882
  filters = self._merge_filters_excludes()
880
883
  try:
881
- executor = self._get_executor()
884
+ executor = await self._get_executor_async()
882
885
  results = await executor.all()
883
886
  results = _merge_pending_into_models(self.model, results, filters)
884
887
  if not results:
@@ -908,7 +911,8 @@ class QueryBuilder(Generic[ModelT]):
908
911
  try:
909
912
  if not kwargs:
910
913
  raise ValidationError(model=self.model, reason="No update data provided")
911
- result = await self._get_executor().update(**kwargs)
914
+ executor = await self._get_executor_async()
915
+ result = await executor.update(**kwargs)
912
916
  await StateManager.refresh_from_rows(self.model, result.updated_rows)
913
917
  return result
914
918
  except ValidationError:
@@ -927,7 +931,8 @@ class QueryBuilder(Generic[ModelT]):
927
931
  free, serving a deleted row for 10 minutes is not.
928
932
  """
929
933
  try:
930
- deleted = await self._get_executor().delete()
934
+ executor = await self._get_executor_async()
935
+ deleted = await executor.delete()
931
936
  if deleted:
932
937
  self._invalidate_cache_for_filters()
933
938
  return deleted
@@ -958,7 +963,8 @@ class QueryBuilder(Generic[ModelT]):
958
963
  try:
959
964
  if self._stack_has_pending():
960
965
  return len(await self.all())
961
- return await self._get_executor().count()
966
+ executor = await self._get_executor_async()
967
+ return await executor.count()
962
968
  except ORMException as e:
963
969
  e.enrich(model=self.model, operation="count", filters=self._merge_filters_excludes())
964
970
  raise
@@ -970,7 +976,8 @@ class QueryBuilder(Generic[ModelT]):
970
976
  try:
971
977
  if self._stack_has_pending():
972
978
  return len(await self.all()) > 0
973
- return await self._get_executor().exists()
979
+ executor = await self._get_executor_async()
980
+ return await executor.exists()
974
981
  except ORMException as e:
975
982
  e.enrich(model=self.model, operation="exists", filters=self._merge_filters_excludes())
976
983
  raise
@@ -991,7 +998,8 @@ class QueryBuilder(Generic[ModelT]):
991
998
  for key, value in kwargs.items():
992
999
  target.aggregations.append({"name": key, "function": value})
993
1000
  try:
994
- return await target._get_executor().aggregate()
1001
+ executor = await target._get_executor_async()
1002
+ return await executor.aggregate()
995
1003
  except ORMException as e:
996
1004
  e.enrich(
997
1005
  model=self.model, operation="aggregate", filters=self._merge_filters_excludes()
@@ -1002,7 +1010,8 @@ class QueryBuilder(Generic[ModelT]):
1002
1010
  target = self._clone() if fields else self
1003
1011
  if fields:
1004
1012
  target.select_fields = list(fields)
1005
- return await target._get_executor().values()
1013
+ executor = await target._get_executor_async()
1014
+ return await executor.values()
1006
1015
 
1007
1016
  def compile(self, *fields: str) -> tuple[str, list[Any]]:
1008
1017
  """Build the SELECT SQL + bound-parameter list WITHOUT executing.
@@ -1057,7 +1066,7 @@ class QueryBuilder(Generic[ModelT]):
1057
1066
  .fetch_via(conn, "id", set_local={"hnsw.iterative_scan": "relaxed_order"})
1058
1067
  )
1059
1068
  """
1060
- sql, params = self.compile(*fields)
1069
+ sql, params = await asyncio.to_thread(self.compile, *fields)
1061
1070
  if set_local:
1062
1071
  async with conn.transaction():
1063
1072
  for guc_name, guc_value in set_local.items():
@@ -1075,7 +1084,8 @@ class QueryBuilder(Generic[ModelT]):
1075
1084
  target = self._clone() if fields else self
1076
1085
  if fields:
1077
1086
  target.select_fields = list(fields)
1078
- return await target._get_executor().values_list(flat=flat)
1087
+ executor = await target._get_executor_async()
1088
+ return await executor.values_list(flat=flat)
1079
1089
 
1080
1090
  # ------------------------------------------------------------------
1081
1091
  # Synchronous wrappers
@@ -1144,7 +1154,7 @@ class QueryBuilder(Generic[ModelT]):
1144
1154
  # ------------------------------------------------------------------
1145
1155
 
1146
1156
  async def __aiter__(self) -> Any:
1147
- executor = self._get_executor()
1157
+ executor = await self._get_executor_async()
1148
1158
  async for item in executor:
1149
1159
  yield item
1150
1160
 
@@ -1540,7 +1540,7 @@ class QueryExecutor:
1540
1540
  async def first(self) -> Any:
1541
1541
  """Fetch the first matching row as a model instance."""
1542
1542
  self._full_query_dict["limit"] = 1
1543
- self.query, self.params = self._build_query()
1543
+ self.query, self.params = await asyncio.to_thread(self._build_query)
1544
1544
  results = await self._execute()
1545
1545
  if not results:
1546
1546
  return None
@@ -1578,7 +1578,7 @@ class QueryExecutor:
1578
1578
  async def exists(self) -> bool:
1579
1579
  """EXISTS check for the current WHERE clause."""
1580
1580
  self._full_query_dict["limit"] = 1
1581
- self.query, self.params = self._build_query()
1581
+ self.query, self.params = await asyncio.to_thread(self._build_query)
1582
1582
  results = await self._execute()
1583
1583
  return len(results) > 0
1584
1584
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "matrx-orm"
3
- version = "3.1.78"
3
+ version = "3.1.80"
4
4
  description = "Async-first PostgreSQL ORM with bidirectional migrations, schema introspection, many-to-many relationships, and built-in state caching"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -33,6 +33,16 @@ class _FakeConn:
33
33
  self._in_tx = False
34
34
  return "OK"
35
35
 
36
+ async def fetch(
37
+ self,
38
+ sql: str,
39
+ *args: object,
40
+ timeout: float | None = None, # noqa: ASYNC109 - mirrors asyncpg
41
+ ) -> list[dict[str, int]]:
42
+ del args, timeout
43
+ self.calls.append(sql)
44
+ return [{"value": 1}]
45
+
36
46
  def terminate(self) -> None:
37
47
  self.calls.append("TERMINATE")
38
48
  self._in_tx = False
@@ -73,6 +83,16 @@ class _FakePool:
73
83
  return 1
74
84
 
75
85
 
86
+ class _ReleaseTimeoutPool(_FakePool):
87
+ async def release(
88
+ self,
89
+ conn: _FakeConn,
90
+ timeout: float | None = None, # noqa: ASYNC109 - mirrors asyncpg
91
+ ) -> None:
92
+ del conn, timeout
93
+ raise TimeoutError("reset exceeded release budget")
94
+
95
+
76
96
  def _patch_pool(monkeypatch, pool: _FakePool) -> None:
77
97
  async def fake_get_pool(cls, config_name): # noqa: ANN001
78
98
  return pool
@@ -116,3 +136,14 @@ async def test_unhealable_connection_is_terminated_not_reused(monkeypatch):
116
136
  pass
117
137
  assert "TERMINATE" in conn.calls
118
138
  assert pool.released == [conn] # slot freed, conn discarded
139
+
140
+
141
+ async def test_release_timeout_does_not_replace_successful_query_result(monkeypatch, caplog):
142
+ conn = _FakeConn(in_tx=False)
143
+ pool = _ReleaseTimeoutPool(conn)
144
+ _patch_pool(monkeypatch, pool)
145
+
146
+ result = await AsyncDatabaseManager.execute_query("x", "SELECT 1 AS value")
147
+
148
+ assert result == [{"value": 1}]
149
+ assert "Connection release timed out for 'x'" in caplog.text