matrx-orm 3.1.145__tar.gz → 3.1.147__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 (382) hide show
  1. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/.gitignore +9 -0
  2. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/BASE_CLASS_METHODS.md +13 -1
  3. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/PKG-INFO +2 -2
  4. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/adapters/async_postgresql.py +7 -1
  5. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/adapters/base_adapter.py +6 -0
  6. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/adapters/postgrest_client_adapter.py +1 -0
  7. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/adapters/supabase_adapter.py +7 -1
  8. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/FEATURE.md +75 -0
  9. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/async_db_manager.py +111 -91
  10. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/base.py +33 -3
  11. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/db_function.py +31 -4
  12. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/expressions.py +1 -0
  13. matrx_orm-3.1.147/matrx_orm/core/write_retry.py +297 -0
  14. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/exceptions.py +34 -15
  15. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/create.py +11 -5
  16. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/update.py +26 -3
  17. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/query/FEATURE.md +23 -0
  18. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/query/builder.py +36 -0
  19. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/query/executor.py +110 -2
  20. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/secrets_battery/FEATURE.md +16 -0
  21. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/secrets_battery/org_service.py +13 -1
  22. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/pyproject.toml +2 -2
  23. matrx_orm-3.1.147/tests/level1/test_deadlock_retry.py +487 -0
  24. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_lock_contention_retry.py +23 -4
  25. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_optimistic_concurrency.py +60 -0
  26. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_query_timeout.py +80 -1
  27. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_write_retry_semantics.py +7 -1
  28. matrx_orm-3.1.147/tests/test_user_visible_lookup.py +52 -0
  29. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/AGENTS.md +0 -0
  30. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/CLAUDE.md +0 -0
  31. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/MODEL_API.md +0 -0
  32. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/README.md +0 -0
  33. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/database/orm/extended/managers/ai_model_base.py +0 -0
  34. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/docs/migrations.md +0 -0
  35. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/FEATURE.md +0 -0
  36. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/MODULE_README.md +0 -0
  37. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/__init__.py +0 -0
  38. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/adapters/MODULE_README.md +0 -0
  39. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/adapters/__init__.py +0 -0
  40. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/adapters/postgresql.py +0 -0
  41. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/admin/FEATURE.md +0 -0
  42. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/admin/__init__.py +0 -0
  43. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/admin/router.py +0 -0
  44. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/api/__init__.py +0 -0
  45. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/api/auth.py +0 -0
  46. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/api/config.py +0 -0
  47. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/api/handlers.py +0 -0
  48. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/api/protocol.py +0 -0
  49. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/api/server.py +0 -0
  50. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/associations.py +0 -0
  51. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/cache_debug.py +0 -0
  52. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/catalog.py +0 -0
  53. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/catalog_sql.py +0 -0
  54. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/client/MODULE_README.md +0 -0
  55. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/client/__init__.py +0 -0
  56. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/client/postgres_connection.py +0 -0
  57. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/client/postgrest.py +0 -0
  58. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/client/supabase_auth.py +0 -0
  59. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/client/supabase_config.py +0 -0
  60. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/client/supabase_manager.py +0 -0
  61. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/EXTENDED-TASK.md +0 -0
  62. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/MODULE_README.md +0 -0
  63. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
  64. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/TASKS.md +0 -0
  65. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/__init__.py +0 -0
  66. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/config.py +0 -0
  67. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/connections.py +0 -0
  68. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/diagnostics.py +0 -0
  69. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/extended.py +0 -0
  70. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/fields.py +0 -0
  71. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/introspection.py +0 -0
  72. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/listen.py +0 -0
  73. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/loop_filters.py +0 -0
  74. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/model_dto.py +0 -0
  75. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/model_view.py +0 -0
  76. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/one_database.py +0 -0
  77. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/paginator.py +0 -0
  78. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/pool_maintenance.py +0 -0
  79. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/pool_watch.py +0 -0
  80. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/pooler_role.py +0 -0
  81. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/pydantic_bridge.py +0 -0
  82. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/registry.py +0 -0
  83. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/relations.py +0 -0
  84. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/resilience.py +0 -0
  85. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/rls_session.py +0 -0
  86. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/session_context.py +0 -0
  87. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/signals.py +0 -0
  88. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/tls.py +0 -0
  89. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/transaction.py +0 -0
  90. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/types.py +0 -0
  91. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/core/write_queue.py +0 -0
  92. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/entity.py +0 -0
  93. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/error_handling.py +0 -0
  94. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/extended/__init__.py +0 -0
  95. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/extended/app_error_handler.py +0 -0
  96. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/identity_rule.py +0 -0
  97. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/local_sqlite.py +0 -0
  98. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/middleware/__init__.py +0 -0
  99. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/middleware/base.py +0 -0
  100. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/migrations/MODULE_README.md +0 -0
  101. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/migrations/__init__.py +0 -0
  102. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/migrations/cli.py +0 -0
  103. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/migrations/ddl.py +0 -0
  104. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/migrations/diff.py +0 -0
  105. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/migrations/executor.py +0 -0
  106. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/migrations/integration.py +0 -0
  107. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/migrations/loader.py +0 -0
  108. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/migrations/operations.py +0 -0
  109. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/migrations/state.py +0 -0
  110. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/migrations/table_filter.py +0 -0
  111. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/no_org_assignment.py +0 -0
  112. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/FEATURE.md +0 -0
  113. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/MODULE_README.md +0 -0
  114. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/__init__.py +0 -0
  115. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/bulk_update_values.py +0 -0
  116. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/catalog_select.py +0 -0
  117. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/conflict.py +0 -0
  118. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/conflict_writes.py +0 -0
  119. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/db_functions.py +0 -0
  120. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/delete.py +0 -0
  121. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/dynamic_admin.py +0 -0
  122. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/dynamic_crud.py +0 -0
  123. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/graph_walk.py +0 -0
  124. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/latest_rows.py +0 -0
  125. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/queue_claim.py +0 -0
  126. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/read.py +0 -0
  127. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/relational_backfill.py +0 -0
  128. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/operations/staging_load.py +0 -0
  129. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/platform_access.py +0 -0
  130. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/platform_db.py +0 -0
  131. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/py.typed +0 -0
  132. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/pytest_plugin.py +0 -0
  133. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/python_sql/MODULE_README.md +0 -0
  134. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/python_sql/__init__.py +0 -0
  135. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/python_sql/db_objects.py +0 -0
  136. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
  137. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
  138. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/query/MODULE_README.md +0 -0
  139. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/query/__init__.py +0 -0
  140. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/resources/__init__.py +0 -0
  141. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/resources/additional-assigners-2026-09-12.json +0 -0
  142. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/resources/org_assignment_trigger_baseline.json +0 -0
  143. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/FEATURE.md +0 -0
  144. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/MODULE_README.md +0 -0
  145. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/__init__.py +0 -0
  146. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/code_handler.py +0 -0
  147. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/codegen_writer.py +0 -0
  148. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/columns.py +0 -0
  149. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/common.py +0 -0
  150. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/diff_preview.py +0 -0
  151. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
  152. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/generator.py +0 -0
  153. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
  154. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
  155. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
  156. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
  157. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/package_wiring.py +0 -0
  158. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/relationships.py +0 -0
  159. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/runner.py +0 -0
  160. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/schema.py +0 -0
  161. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/schema_manager.py +0 -0
  162. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/tables.py +0 -0
  163. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/schema_builder/views.py +0 -0
  164. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/secrets_battery/__init__.py +0 -0
  165. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/secrets_battery/attachments.py +0 -0
  166. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/secrets_battery/crypto.py +0 -0
  167. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/secrets_battery/host_authorization.py +0 -0
  168. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/secrets_battery/items.py +0 -0
  169. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/secrets_battery/model.py +0 -0
  170. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/secrets_battery/mutation_receipts.py +0 -0
  171. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/secrets_battery/service.py +0 -0
  172. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/session/README.md +0 -0
  173. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/session/__init__.py +0 -0
  174. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/session/coalesce.py +0 -0
  175. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/session/dag.py +0 -0
  176. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/session/errors.py +0 -0
  177. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/session/fallback.py +0 -0
  178. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/session/flush.py +0 -0
  179. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/session/lifecycle.py +0 -0
  180. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/session/managed.py +0 -0
  181. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/session/op.py +0 -0
  182. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/session/reads.py +0 -0
  183. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/session/session.py +0 -0
  184. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/session/telemetry.py +0 -0
  185. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/sql_executor/MODULE_README.md +0 -0
  186. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/sql_executor/__init__.py +0 -0
  187. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/sql_executor/executor.py +0 -0
  188. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/sql_executor/queries.py +0 -0
  189. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/sql_executor/registry.py +0 -0
  190. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/sql_executor/types.py +0 -0
  191. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/sql_executor/utils.py +0 -0
  192. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/state.py +0 -0
  193. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/utils/__init__.py +0 -0
  194. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/utils/sql_utils.py +0 -0
  195. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/matrx_orm/utils/type_converters.py +0 -0
  196. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/scripts/git-branches.sh +0 -0
  197. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/scripts/publish.sh +0 -0
  198. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/scripts/release.sh +0 -0
  199. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/MODULE_README.md +0 -0
  200. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/__init__.py +0 -0
  201. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/conftest.py +0 -0
  202. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/MODULE_README.md +0 -0
  203. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/__init__.py +0 -0
  204. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_adapter_registry.py +0 -0
  205. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_admin_db_columns.py +0 -0
  206. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_admin_response_rendering.py +0 -0
  207. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_admin_search_negation.py +0 -0
  208. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_admin_search_sentinels.py +0 -0
  209. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_advisory_lock.py +0 -0
  210. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_agent_message.py +0 -0
  211. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_api_auth.py +0 -0
  212. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_api_config.py +0 -0
  213. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_api_handlers.py +0 -0
  214. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_api_protocol.py +0 -0
  215. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_array_agg_order_by.py +0 -0
  216. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_associations.py +0 -0
  217. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_associations_wiring.py +0 -0
  218. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_bulk_hydration_offload.py +0 -0
  219. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_bulk_update_by_pk.py +0 -0
  220. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
  221. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_cache_debug.py +0 -0
  222. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_call_function.py +0 -0
  223. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_case_when.py +0 -0
  224. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_catalog.py +0 -0
  225. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_committed_row_guard.py +0 -0
  226. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_composite_pk_filter.py +0 -0
  227. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_composite_pk_write_paths.py +0 -0
  228. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_config.py +0 -0
  229. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_conflict_target.py +0 -0
  230. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_conflict_writes.py +0 -0
  231. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_connection_codecs.py +0 -0
  232. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_connection_poison_guard.py +0 -0
  233. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_connection_primitives.py +0 -0
  234. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_count_composite_distinct.py +0 -0
  235. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_count_limit_param_leak.py +0 -0
  236. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_db_function_field.py +0 -0
  237. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_ddl_generator.py +0 -0
  238. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_derived_table.py +0 -0
  239. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_dirty_tracking.py +0 -0
  240. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_dynamic_crud.py +0 -0
  241. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_exceptions.py +0 -0
  242. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
  243. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_expression_primitives.py +0 -0
  244. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_fields.py +0 -0
  245. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
  246. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_from_alias.py +0 -0
  247. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_fts.py +0 -0
  248. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_func_expression_args.py +0 -0
  249. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_graph_walk.py +0 -0
  250. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_having_annotate_alias.py +0 -0
  251. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
  252. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_introspect_rls_policies.py +0 -0
  253. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_join_and_aggregate.py +0 -0
  254. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_latest_rows.py +0 -0
  255. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_listen_notify.py +0 -0
  256. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_local_sqlite.py +0 -0
  257. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_managed_write_guard.py +0 -0
  258. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_matrx_entity.py +0 -0
  259. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_migration_diff_types.py +0 -0
  260. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_migration_loader.py +0 -0
  261. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_model_instance.py +0 -0
  262. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_model_meta.py +0 -0
  263. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_order_by_expression.py +0 -0
  264. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_org_assignment_inventory.py +0 -0
  265. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_parameter_name_hydration.py +0 -0
  266. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_patch_jsonb_path.py +0 -0
  267. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_persisted_count.py +0 -0
  268. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_pool_holder_census.py +0 -0
  269. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_pool_loop_recreation.py +0 -0
  270. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_pool_maintenance.py +0 -0
  271. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_postgrest_filters.py +0 -0
  272. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_pydantic_bridge.py +0 -0
  273. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_query_builder.py +0 -0
  274. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_query_error_detail.py +0 -0
  275. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_query_executor_sql.py +0 -0
  276. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
  277. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_queue_claim.py +0 -0
  278. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_read_connection_retry.py +0 -0
  279. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_registry.py +0 -0
  280. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_registry_multi_database.py +0 -0
  281. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_registry_multi_schema.py +0 -0
  282. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_relational_backfill.py +0 -0
  283. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_relations.py +0 -0
  284. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_resilience.py +0 -0
  285. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_rls_session_scope.py +0 -0
  286. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_schema_exists.py +0 -0
  287. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_session_advisory_lock.py +0 -0
  288. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_standalone_write_session_context.py +0 -0
  289. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_state_cache.py +0 -0
  290. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_subquery_filter_raw.py +0 -0
  291. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_subquery_in_filter.py +0 -0
  292. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_subquery_nested_expressions.py +0 -0
  293. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_supabase_auth.py +0 -0
  294. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_supabase_config.py +0 -0
  295. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_tls.py +0 -0
  296. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_transaction_database_identity.py +0 -0
  297. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_typed_join_projection.py +0 -0
  298. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_typed_predicates.py +0 -0
  299. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_update_case_expression.py +0 -0
  300. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_update_subquery.py +0 -0
  301. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_update_with_rebase.py +0 -0
  302. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_upsert_default_leak.py +0 -0
  303. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_upsert_with_conflict.py +0 -0
  304. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_values_jsonb_decode.py +0 -0
  305. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_watched_lifecycle.py +0 -0
  306. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level1/test_write_queue_deadline.py +0 -0
  307. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level2/MODULE_README.md +0 -0
  308. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level2/__init__.py +0 -0
  309. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level2/conftest.py +0 -0
  310. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level2/test_bulk_ops.py +0 -0
  311. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level2/test_cache_integration.py +0 -0
  312. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level2/test_crud.py +0 -0
  313. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level2/test_foreign_keys.py +0 -0
  314. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level2/test_m2m.py +0 -0
  315. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level2/test_manager.py +0 -0
  316. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level2/test_migrations_live.py +0 -0
  317. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level2/test_query_execution.py +0 -0
  318. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level2/test_schema_diff.py +0 -0
  319. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/level2/verify_committed_row_guard_pg17.py +0 -0
  320. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/live_pooler_role.py +0 -0
  321. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/sample_project/.env.example +0 -0
  322. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/sample_project/README.md +0 -0
  323. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/sample_project/__init__.py +0 -0
  324. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/sample_project/generate.py +0 -0
  325. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/sample_project/generated/.gitkeep +0 -0
  326. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/sample_project/matrx_orm.yaml +0 -0
  327. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/sample_project/test_schema_generation.py +0 -0
  328. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/sample_project_desktop/.env.example +0 -0
  329. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/sample_project_desktop/README.md +0 -0
  330. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/sample_project_desktop/__init__.py +0 -0
  331. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/sample_project_desktop/client_example.py +0 -0
  332. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/sample_project_desktop/client_example.ts +0 -0
  333. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/sample_project_desktop/client_supabase_example.py +0 -0
  334. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/sample_project_desktop/server.py +0 -0
  335. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema/entity_tests.py +0 -0
  336. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema/test_base_generation.py +0 -0
  337. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema/test_composite_pk_fk_generation.py +0 -0
  338. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema/test_composite_unique_generation.py +0 -0
  339. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema/test_dto_identity_generation.py +0 -0
  340. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema/test_entity_capability_generation.py +0 -0
  341. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema/test_enum_ordering.py +0 -0
  342. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema/test_generate_schema.py +0 -0
  343. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema/test_junction_analysis.py +0 -0
  344. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema/test_multi_schema_output.py +0 -0
  345. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema/test_schema_analysis.py +0 -0
  346. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema/test_sql_expression_defaults.py +0 -0
  347. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema/test_table_relationships_query.py +0 -0
  348. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema/test_view_generation.py +0 -0
  349. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema_builder/test_array_default_parsing.py +0 -0
  350. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema_builder/test_focused_generation_plan.py +0 -0
  351. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/schema_builder/test_host_models_aggregator.py +0 -0
  352. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/session/__init__.py +0 -0
  353. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/session/conftest.py +0 -0
  354. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/session/test_cache_no_downgrade.py +0 -0
  355. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/session/test_capture_poison_proof.py +0 -0
  356. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/session/test_coalesce.py +0 -0
  357. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/session/test_dag.py +0 -0
  358. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/session/test_disk_spill_fallback.py +0 -0
  359. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/session/test_flush_individual_fallback.py +0 -0
  360. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/session/test_governed_write.py +0 -0
  361. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/session/test_managed.py +0 -0
  362. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/session/test_op_enqueue_site.py +0 -0
  363. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/session/test_reads.py +0 -0
  364. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/session/test_record_error_bounded_insert.py +0 -0
  365. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/session/test_record_error_storm.py +0 -0
  366. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/session/test_session.py +0 -0
  367. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/session/test_spill_guard_plugin.py +0 -0
  368. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/test_array_arg_element_type.py +0 -0
  369. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/test_database_name_alias.py +0 -0
  370. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/test_db_functions.py +0 -0
  371. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/test_dynamic_select_unregistered.py +0 -0
  372. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/test_immutable_insert_or_exact_verify.py +0 -0
  373. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/test_keyed_digest.py +0 -0
  374. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/test_model_cls_refactor.py +0 -0
  375. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/test_no_blocking_asyncpg_tls.py +0 -0
  376. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/test_one_database.py +0 -0
  377. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/test_order_by_desc_nulls_last.py +0 -0
  378. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/test_pooler_role_guard.py +0 -0
  379. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/test_secrets_battery.py +0 -0
  380. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/test_secrets_minting_keys.py +0 -0
  381. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/test_sql_param_casts.py +0 -0
  382. {matrx_orm-3.1.145 → matrx_orm-3.1.147}/tests/test_sync_twins_guard.py +0 -0
@@ -304,3 +304,12 @@ _kind_marker_law_scratch*/
304
304
 
305
305
  # packed npm tarballs are build output, never source (doctrine: typescript-package-standard)
306
306
  *.tgz
307
+
308
+ # Peers' kit scratch witnesses. A `.kit-<name>-witness.XXXXXX/` directory is a
309
+ # full COPY of kit/src planted inside the packages tree — three of them held a
310
+ # duplicate `format.ts` on 2026-09-12 — and it sits in the `check:package-twins`
311
+ # scan ROOT. `git ls-files` only reaches TRACKED files, so the guard never saw
312
+ # them; the risk was a broad `git add` committing three formatter twins in one
313
+ # stroke, which is the exact class that guard exists to stop. Ignored, never
314
+ # deleted: they are another agent's evidence.
315
+ apps/shared/.kit-*-witness.*/
@@ -59,9 +59,21 @@ The following methods are available directly from all manager classes:
59
59
  - `upsert_item(data, conflict_fields, update_fields=None)`
60
60
  - `upsert_items(items_data, conflict_fields, update_fields=None)`
61
61
  - `count(**filters)`
62
- - `update_where(filters, *, expected_version=None, **updates)`
62
+ - `update_where(filters, *, expected_version=None, lock_rows_in_pk_order=False, **updates)`
63
63
  - `delete_where(*, expected_version=None, **filters)`
64
64
 
65
+ ### A multi-row UPDATE that racing callers share — `lock_rows_in_pk_order=True`
66
+
67
+ Pass it whenever concurrent callers can run the SAME multi-row `update_where`
68
+ over an **overlapping** row set. The statement then takes its row locks
69
+ ascending by primary key (`WHERE pk IN (SELECT pk ... ORDER BY pk FOR UPDATE)`)
70
+ instead of in plan-scan order, which is what stops two copies of one statement
71
+ from deadlocking each other (SQLSTATE 40P01). Sorting the id list yourself does
72
+ NOT do this — the plan decides lock order, not the array. Needs a single-column
73
+ primary key; contradictory with `expected_version=` (that addresses ONE row, so
74
+ there is no order to fix) and refused. Full reasoning and the incident:
75
+ `matrx_orm/query/FEATURE.md` § A multi-row UPDATE can pin its lock ORDER.
76
+
65
77
  ### Optimistic concurrency — `expected_version=`
66
78
 
67
79
  **Opt-in compare-and-swap on the canonical `version` int column.** Pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: matrx-orm
3
- Version: 3.1.145
3
+ Version: 3.1.147
4
4
  Summary: Async-first PostgreSQL ORM with bidirectional migrations, schema introspection, many-to-many relationships, and built-in state caching
5
5
  Project-URL: Homepage, https://github.com/AI-Matrix-Engine/aidream-current
6
6
  Project-URL: Repository, https://github.com/AI-Matrix-Engine/aidream-current
@@ -25,7 +25,7 @@ Requires-Python: >=3.13
25
25
  Requires-Dist: asyncpg>=0.31.0
26
26
  Requires-Dist: cryptography>=43.0
27
27
  Requires-Dist: gitpython>=3.1.40
28
- Requires-Dist: matrx-utils>=2.0.39
28
+ Requires-Dist: matrx-utils>=2.0.40
29
29
  Requires-Dist: psycopg-pool>=3.2.5
30
30
  Requires-Dist: psycopg[binary]>=3.2.5
31
31
  Requires-Dist: python-dotenv>=1.0
@@ -60,11 +60,17 @@ class AsyncPostgreSQLAdapter(BaseAdapter):
60
60
  *args: Any,
61
61
  timeout: float = 10.0,
62
62
  max_retries: int = 2,
63
+ statement_timeout: float | None = None,
63
64
  ) -> list[dict[str, Any]]:
64
65
  from matrx_orm.core.async_db_manager import AsyncDatabaseManager
65
66
 
66
67
  return await AsyncDatabaseManager.execute_write(
67
- self._config_name, sql, *args, timeout=timeout, max_retries=max_retries
68
+ self._config_name,
69
+ sql,
70
+ *args,
71
+ timeout=timeout,
72
+ max_retries=max_retries,
73
+ statement_timeout=statement_timeout,
68
74
  )
69
75
 
70
76
  # ------------------------------------------------------------------
@@ -50,10 +50,16 @@ class BaseAdapter(ABC):
50
50
  *args: Any,
51
51
  timeout: float = 10.0,
52
52
  max_retries: int = 2,
53
+ statement_timeout: float | None = None,
53
54
  ) -> list[dict[str, Any]]:
54
55
  """Execute a write (INSERT/UPDATE/DELETE) query and return rows as dicts.
55
56
 
56
57
  Implementations should handle retries and write-queue routing as needed.
58
+ ``timeout`` is the connection-acquire timeout; ``statement_timeout``
59
+ (when set) overrides the pool's ``command_timeout`` for THIS statement
60
+ only — the write-side twin of ``QueryBuilder.timeout()``, for a write
61
+ whose size is known to need longer than the pool default (a workflow
62
+ checkpoint carrying a fan-out's inline payloads).
57
63
  """
58
64
 
59
65
  # ------------------------------------------------------------------
@@ -338,6 +338,7 @@ class PostgRESTClientAdapter(BaseAdapter):
338
338
  *args: Any,
339
339
  timeout: float = 10.0,
340
340
  max_retries: int = 2,
341
+ statement_timeout: float | None = None,
341
342
  ) -> list[dict[str, Any]]:
342
343
  """Raw SQL writes are not supported via PostgREST."""
343
344
  raise NotImplementedError(
@@ -122,11 +122,17 @@ class SupabaseAdapter(BaseAdapter):
122
122
  *args: Any,
123
123
  timeout: float = 10.0,
124
124
  max_retries: int = 2,
125
+ statement_timeout: float | None = None,
125
126
  ) -> list[dict[str, Any]]:
126
127
  from matrx_orm.core.async_db_manager import AsyncDatabaseManager
127
128
 
128
129
  return await AsyncDatabaseManager.execute_write(
129
- self._config_name, sql, *args, timeout=timeout, max_retries=max_retries
130
+ self._config_name,
131
+ sql,
132
+ *args,
133
+ timeout=timeout,
134
+ max_retries=max_retries,
135
+ statement_timeout=statement_timeout,
130
136
  )
131
137
 
132
138
  # ------------------------------------------------------------------
@@ -45,6 +45,67 @@ Guard: `tests/level1/test_read_connection_retry.py`.
45
45
  rejects the statement before execution, so this narrow failure is replay-safe;
46
46
  ambiguous write failures remain single-attempt.
47
47
 
48
+ **A write may set its own statement ceiling — `Model.create(statement_timeout=…)`
49
+ (seconds, keyword-only).** The read side has had `QueryBuilder.timeout()` for a
50
+ while; the write door had nothing, so the pool's `command_timeout` (10s) was the
51
+ only ceiling any INSERT could have. It threads
52
+ `Model.create` → `operations/create` → `query["statement_timeout"]` →
53
+ `QueryExecutor.insert` → `adapter.execute_write(statement_timeout=)` →
54
+ `execute_query(statement_timeout=)` → asyncpg `fetch(..., timeout=)`, so it
55
+ overrides — never widens — anything else: pool-acquire timeout and every other
56
+ statement are untouched. It raises no retry rights: an ambiguous write is still
57
+ single-attempt HERE, and a caller that re-issues one must own an idempotency key
58
+ (matrx-graph's checkpoint INSERT is keyed by checkpoint id and reads the row back).
59
+ Added 2026-09-12 after a 1.1 MB `workflow.checkpoint` row timed out at 10s under
60
+ load and killed a paid-for workflow run. Guard:
61
+ `tests/level1/test_query_timeout.py::TestInsertStatementTimeout`.
62
+
63
+ **A statement the SERVER cancelled to break a lock collision is re-issued — the
64
+ second and last replay-safe failure, and it covers READS.** SQLSTATE `40P01`
65
+ (`deadlock_detected`), `40001` (`serialization_failure`) and `55P03`
66
+ (`lock_not_available`) are the statuses where Postgres aborted the statement IN
67
+ FULL: the effects are gone with certainty, so a replay is exactly-once, not the
68
+ at-least-once hazard that makes a statement timeout or a mid-query disconnect
69
+ unretryable. Class-40 siblings stay out by name — `40003`
70
+ (`StatementCompletionUnknownError`) is literally "the server cannot say whether
71
+ this completed", and `40002` is a constraint failure wearing a class-40 code.
72
+ The victim is often innocent of any write: a multi-table READ is cancelled
73
+ whenever a concurrent DDL session wants AccessExclusiveLock on a table it already
74
+ holds a lock on, which reached a person as "listConversations: deadlock detected"
75
+ on /chat.
76
+
77
+ 🚨 **THERE IS ONE LOOP AND ONE BUDGET.** `matrx_orm/core/write_retry.py` owns the
78
+ classification (`is_confidently_aborted_lock_contention`), the knob policy, and
79
+ the loop (`run_with_lock_retry`); both doors into the database run through it —
80
+ `execute_query` for every statement and `call_function` for RPCs (an RPC reaches
81
+ Postgres on a raw connection, bypassing `execute_query` entirely, so before this a
82
+ deadlock inside any `SECURITY DEFINER` write RPC failed its caller outright).
83
+ `_execute_with_retry` retries only the nothing-was-sent classes above it and must
84
+ never grow a lock budget of its own: on 2026-09-12 two independent lock-retry
85
+ mechanisms landed within hours and nested, making the real ceiling the product of
86
+ two budgets nobody had chosen. Statement-level replay is sound only because both
87
+ doors return early when a `transaction()` is active: every attempt the loop
88
+ retries WAS its own transaction. Inside a caller's transaction the rollback took
89
+ the whole block and only that transaction's owner can redo the unit, so the
90
+ refusal is typed `LockContentionError(in_transaction=True)`.
91
+
92
+ The budget is `platform.feature_knob` feature `orm.write_retry`
93
+ (`deadlock_max_retries` 4, `deadlock_backoff_base_ms` 25,
94
+ `deadlock_backoff_max_ms` 400 — seeded by
95
+ `db/migrations/0647_orm_write_retry_knobs.sql`, re-read every 60s, not
96
+ org-overridable). Backoff is FULL jitter: the victims are N invocations that
97
+ started together, so a fixed sleep re-collides them in lockstep. Every retry and
98
+ every heal is logged `[lock-retry]`, and when the budget is spent the
99
+ `LockContentionError` NAMES the attempts — and points at the lock ORDER
100
+ (`Model.update_where(..., lock_rows_in_pk_order=True)`) rather than a bigger
101
+ retry count. A database whose knob rows cannot be read (a standalone install on a
102
+ bare Postgres) runs on the recorded starting values and logs one ERROR naming the
103
+ remedy — a raise here would turn a survivable deadlock into a hard failure on the
104
+ very path that exists to survive it. Guards:
105
+ `tests/level1/test_lock_contention_retry.py` (the loop's semantics per statement)
106
+ + `tests/level1/test_deadlock_retry.py` (the knob budget, the RPC door, the lock
107
+ order).
108
+
48
109
  `AsyncDatabaseManager.execute_query` must classify every PostgreSQL integrity
49
110
  constraint violation as `IntegrityError`. Known categories retain their useful
50
111
  labels (`unique`, `foreign_key`, `not_null`, `check`, `exclusion`, and
@@ -220,6 +281,20 @@ can never disagree about who the blocker was.
220
281
  Verified against the live database: 2026-08-21.
221
282
 
222
283
  ## Change log
284
+ - 2026-09-12 (later): ONE retry mechanism. Two independent lock-retry
285
+ implementations for the same class landed hours apart — a `LockContentionError`
286
+ + constant-budget loop inside `execute_query`, and a `TransientTransactionError`
287
+ + knob-budget loop wrapped around `_execute_with_retry` — and merged into
288
+ nesting. Reconciled to: `LockContentionError` survives (it covers reads and
289
+ 55P03 too), the loop lives once in `write_retry.run_with_lock_retry` and is used
290
+ by `execute_query` AND `call_function`, its budget is the `orm.write_retry`
291
+ knob rows (never a constant), and `TransientTransactionError` is deleted.
292
+ - 2026-09-12: A statement Postgres cancelled to break a lock collision
293
+ (40P01/40001/55P03) is re-issued on a fresh connection instead of failing the
294
+ caller. Before this it fell to the catch-all as `UnknownDatabaseError` and
295
+ killed the request: four parallel `ai.agent.start` invocations deadlocked on
296
+ `users.user_secrets` and errored a whole workflow run, and a pure `/chat`
297
+ conversation-list READ showed "deadlock detected" to a person.
223
298
  - 2026-08-30: Classified server restart/failover rejection during pool checkout
224
299
  as retryable connection failure before any caller SQL is sent.
225
300
  - 2026-08-24: Recovered read-only disconnects masked by empty-read reproof's
@@ -6,7 +6,6 @@ from dataclasses import dataclass
6
6
  import json
7
7
  import logging
8
8
  import os
9
- import random
10
9
  import traceback
11
10
  from contextlib import asynccontextmanager
12
11
  from typing import Any
@@ -44,6 +43,15 @@ from matrx_orm.exceptions import (
44
43
  StateError,
45
44
  UnknownDatabaseError,
46
45
  )
46
+ from matrx_orm.core.write_retry import (
47
+ CONTENTION_EXC as _CONTENTION_EXC,
48
+ )
49
+ from matrx_orm.core.write_retry import (
50
+ is_confidently_aborted_lock_contention as _is_confidently_aborted_lock_contention,
51
+ )
52
+ from matrx_orm.core.write_retry import (
53
+ run_with_lock_retry,
54
+ )
47
55
 
48
56
  logger = logging.getLogger("matrx_orm.async_db_manager")
49
57
 
@@ -75,24 +83,6 @@ _SCHEMA_MISMATCH_EXC = (
75
83
  asyncpg.exceptions.InvalidSchemaNameError,
76
84
  )
77
85
 
78
- # Cancelled to break a lock collision. ``TransactionRollbackError`` is an
79
- # umbrella for every SQLSTATE 40xxx, including 40003 (completion unknown) and
80
- # 40002 (transaction integrity constraint violation). Only the three explicit
81
- # statuses below establish that PostgreSQL aborted this statement in full.
82
- _CONTENTION_EXC = (
83
- asyncpg.exceptions.TransactionRollbackError,
84
- asyncpg.exceptions.LockNotAvailableError,
85
- )
86
- _CONTENTION_SQLSTATES = frozenset({"40P01", "40001", "55P03"})
87
-
88
-
89
- def _is_confidently_aborted_lock_contention(exc: BaseException) -> bool:
90
- """Whether ``exc`` proves a standalone statement can be re-issued."""
91
- return (
92
- isinstance(exc, _CONTENTION_EXC)
93
- and getattr(exc, "sqlstate", None) in _CONTENTION_SQLSTATES
94
- )
95
-
96
86
  # PostgreSQL emits these SQLSTATE class-57 errors while asyncpg is opening a
97
87
  # physical connection during a server restart/failover. They happen before a
98
88
  # checkout exists, so no caller SQL was sent and a replay-safe owner may retry.
@@ -156,11 +146,13 @@ READ_TIMEOUT_RETRY_BACKOFF = 0.25 # seconds before the read retry
156
146
  # traffic). On 2026-09-12 that reached a person as the banner
157
147
  # "listConversations: deadlock detected" on /chat, and ops.app_log shows the same
158
148
  # class crashing POST /coding-sessions/bridge with a 500. Bounded and LOUD: real
159
- # contention still surfaces, it just costs two extra attempts first.
160
- # KNOB MIRROR of platform.feature_knob "orm.write_retry" "contention_retries" — the database
161
- # manager cannot read the database to configure itself; change the row, then re-mirror here.
162
- CONTENTION_RETRIES = 2 # extra attempts for a cancelled statement; total = 1 + this
163
- CONTENTION_BACKOFF = 0.06 # seconds before the first re-issue; doubled each time
149
+ # contention still surfaces, it just costs extra attempts first.
150
+ #
151
+ # The budget is NOT a constant here: it is the ``orm.write_retry``
152
+ # ``platform.feature_knob`` rows an admin turns, read by
153
+ # ``matrx_orm.core.write_retry``, which also owns THE ONE retry loop
154
+ # (``run_with_lock_retry``) that this door and the RPC door (``call_function``)
155
+ # both run through. Never grow a second loop or a second budget.
164
156
 
165
157
  # ── idle-in-transaction ceiling (the platform-wide DDL blocker) ───────────────
166
158
  # A session that BEGINs and then goes quiet is the single most expensive thing a
@@ -1330,23 +1322,18 @@ class AsyncDatabaseManager:
1330
1322
 
1331
1323
  read_only = is_read_only_sql(query)
1332
1324
 
1333
- async with handle_orm_operation(
1334
- operation_name="execute_query",
1335
- model=None,
1336
- config_name=config_name,
1337
- query=query,
1338
- args=args,
1339
- timeout=timeout,
1340
- ):
1325
+ async def _attempt() -> list[dict[str, Any]]:
1326
+ """One full try of this statement, on its own fresh connection.
1327
+
1328
+ Re-issuing it after a lock collision is NOT decided here: a raw
1329
+ asyncpg contention error escapes unwrapped so THE ONE retry loop
1330
+ (``run_with_lock_retry``, knob-driven) owns that budget for this door
1331
+ and the RPC door alike.
1332
+ """
1341
1333
  # A timed-out READ retries ONCE on a fresh connection (self-heal for
1342
1334
  # a starvation-induced command_timeout). Each iteration re-acquires,
1343
1335
  # so a poisoned connection is never reused. Writes never loop here.
1344
- # Two independent budgets, each re-acquiring a FRESH connection: a
1345
- # timed-out read (below) and a statement Postgres cancelled to break
1346
- # a lock collision (which applies to writes too — the victim is
1347
- # rolled back in full).
1348
1336
  read_attempt = 0
1349
- contention_attempt = 0
1350
1337
  while True:
1351
1338
  async with cls.get_connection(config_name, timeout) as conn:
1352
1339
  try:
@@ -1358,14 +1345,6 @@ class AsyncDatabaseManager:
1358
1345
  read_attempt + 1,
1359
1346
  query[:120],
1360
1347
  )
1361
- if contention_attempt > 0:
1362
- logger.warning(
1363
- "[lock-retry] HEALED on attempt %d after Postgres "
1364
- "cancelled the statement to break a lock collision "
1365
- "(rolled back, safe to re-run): %s",
1366
- contention_attempt + 1,
1367
- query[:120],
1368
- )
1369
1348
  if (
1370
1349
  not results
1371
1350
  and verify_empty
@@ -1387,36 +1366,19 @@ class AsyncDatabaseManager:
1387
1366
  if true_rows is not None:
1388
1367
  return true_rows
1389
1368
  return [_normalize_record(r) for r in results]
1390
- except _CONTENTION_EXC as e:
1391
- if not _is_confidently_aborted_lock_contention(e):
1392
- raise
1393
- # Rolled back in full → the SAME statement is re-issued
1394
- # on a fresh connection, writes included. Jittered so N
1395
- # tasks cancelled by ONE collision do not collide again
1396
- # in lockstep. Announced every time: a masked contention
1397
- # problem is the thing this must never become.
1398
- if contention_attempt < CONTENTION_RETRIES:
1399
- delay = CONTENTION_BACKOFF * (2**contention_attempt)
1400
- delay += random.random() * delay * 0.5
1401
- logger.warning(
1402
- "[lock-retry] attempt %d/%d cancelled by a lock "
1403
- "collision (%s) — re-running in %.3fs: %s",
1404
- contention_attempt + 1,
1405
- 1 + CONTENTION_RETRIES,
1406
- getattr(e, "sqlstate", None) or e.__class__.__name__,
1407
- delay,
1408
- query[:120],
1409
- )
1410
- await asyncio.sleep(delay)
1411
- contention_attempt += 1
1412
- continue
1413
- raise LockContentionError(
1414
- original_error=e,
1415
- query=query,
1416
- args=args,
1417
- config_name=config_name,
1418
- attempts=1 + contention_attempt,
1419
- ) from None
1369
+ except _CONTENTION_EXC:
1370
+ # Rolled back in full (40P01/40001/55P03) → the SAME
1371
+ # statement is provably safe to re-issue, writes
1372
+ # included, and ``run_with_lock_retry`` around this
1373
+ # attempt does it on a FRESH connection with the knob
1374
+ # budget and full jitter. Raised RAW on purpose: this
1375
+ # clause exists only to keep the catch-all below from
1376
+ # wrapping it as UnknownDatabaseError, which is what made
1377
+ # the class unretryable and cost a live workflow run
1378
+ # ($0.35, Newsroom Desk, 2026-09-12). An ambiguous
1379
+ # class-40 status (40002/40003) leaves here the same way
1380
+ # and the loop declines to retry it.
1381
+ raise
1420
1382
  except asyncpg.exceptions.PostgresSyntaxError as e:
1421
1383
  raise QueryError(
1422
1384
  model=None,
@@ -1565,6 +1527,21 @@ class AsyncDatabaseManager:
1565
1527
  original_error=e,
1566
1528
  ) from e
1567
1529
 
1530
+ # handle_orm_operation stays OUTSIDE the retry loop: it wraps a non-ORM
1531
+ # exception as CacheError, which would hide the raw asyncpg contention
1532
+ # error the loop classifies on.
1533
+ async with handle_orm_operation(
1534
+ operation_name="execute_query",
1535
+ model=None,
1536
+ config_name=config_name,
1537
+ query=query,
1538
+ args=args,
1539
+ timeout=timeout,
1540
+ ):
1541
+ return await run_with_lock_retry(
1542
+ _attempt, config_name=config_name, query=query, args=args
1543
+ )
1544
+
1568
1545
  @classmethod
1569
1546
  async def execute_write(
1570
1547
  cls,
@@ -1573,6 +1550,7 @@ class AsyncDatabaseManager:
1573
1550
  *args: Any,
1574
1551
  timeout: float = 10.0,
1575
1552
  max_retries: int = 2,
1553
+ statement_timeout: float | None = None,
1576
1554
  ) -> list[dict[str, Any]]:
1577
1555
  """Execute a write query, routing through the write queue when enabled.
1578
1556
 
@@ -1604,7 +1582,13 @@ class AsyncDatabaseManager:
1604
1582
  from matrx_orm.core.transaction import get_active_connection
1605
1583
 
1606
1584
  if get_active_connection() is not None:
1607
- return await cls.execute_query(config_name, query, *args, timeout=timeout)
1585
+ return await cls.execute_query(
1586
+ config_name,
1587
+ query,
1588
+ *args,
1589
+ timeout=timeout,
1590
+ statement_timeout=statement_timeout,
1591
+ )
1608
1592
  except ImportError:
1609
1593
  pass
1610
1594
 
@@ -1633,9 +1617,17 @@ class AsyncDatabaseManager:
1633
1617
  if session_context_gucs():
1634
1618
  from matrx_orm.core.transaction import transaction
1635
1619
 
1620
+ config_name = resolve_database_name(config_name)
1621
+
1636
1622
  async def _declared_write() -> list[dict[str, Any]]:
1637
1623
  async with transaction(config_name):
1638
- return await cls.execute_query(config_name, query, *args, timeout=timeout)
1624
+ return await cls.execute_query(
1625
+ config_name,
1626
+ query,
1627
+ *args,
1628
+ timeout=timeout,
1629
+ statement_timeout=statement_timeout,
1630
+ )
1639
1631
 
1640
1632
  if wq is None:
1641
1633
  return await _declared_write()
@@ -1652,11 +1644,18 @@ class AsyncDatabaseManager:
1652
1644
  if wq is not None:
1653
1645
  return await wq.submit(
1654
1646
  lambda: cls._execute_with_retry(
1655
- config_name, query, *args, timeout=timeout, max_retries=max_retries
1647
+ config_name,
1648
+ query,
1649
+ *args,
1650
+ timeout=timeout,
1651
+ max_retries=max_retries,
1652
+ statement_timeout=statement_timeout,
1656
1653
  )
1657
1654
  )
1658
1655
 
1659
- return await cls.execute_query(config_name, query, *args, timeout=timeout)
1656
+ return await cls.execute_query(
1657
+ config_name, query, *args, timeout=timeout, statement_timeout=statement_timeout
1658
+ )
1660
1659
 
1661
1660
  @classmethod
1662
1661
  async def _execute_with_retry(
@@ -1666,12 +1665,21 @@ class AsyncDatabaseManager:
1666
1665
  *args: Any,
1667
1666
  timeout: float = 10.0,
1668
1667
  max_retries: int = 2,
1668
+ statement_timeout: float | None = None,
1669
1669
  ) -> list[dict[str, Any]]:
1670
1670
  """Execute a query with exponential-backoff retry — write-safe.
1671
1671
 
1672
- Retries only failures proven to happen before write execution:
1673
- ``PoolAcquireTimeoutError`` (no connection acquired) and
1674
- ``ReadOnlyTransactionError`` (PostgreSQL refused the statement).
1672
+ Retries exactly ONE class here — **nothing was sent**:
1673
+ ``PoolAcquireTimeoutError`` (no connection was ever acquired) and
1674
+ ``ReadOnlyTransactionError`` (PostgreSQL refused the statement before
1675
+ executing it). Both leave the database provably untouched.
1676
+
1677
+ The other provably-safe class — **the server rolled it back itself**
1678
+ (SQLSTATE 40P01 / 40001 / 55P03) — is retried ONE LAYER DOWN, inside
1679
+ ``execute_query``, by THE ONE lock-retry loop on the ``orm.write_retry``
1680
+ knob budget (:mod:`matrx_orm.core.write_retry`); it covers reads as well
1681
+ as writes and surfaces as ``LockContentionError`` when spent. This loop
1682
+ must never grow a second budget for it.
1675
1683
 
1676
1684
  Every other transient-looking error is propagated immediately:
1677
1685
 
@@ -1692,18 +1700,29 @@ class AsyncDatabaseManager:
1692
1700
  ``"timeout" in str(exc).lower() or isinstance(exc, ConnectionError)``
1693
1701
  treated all four classes above as "transient" and retried them,
1694
1702
  which silently produced cx_request_pkey / cx_message_pkey
1695
- duplicate-key errors under burst load. The class of failure is now
1696
- structurally extinct because retries can only fire when no SQL was
1697
- sent — see ``PoolAcquireTimeoutError`` for the contract.
1703
+ duplicate-key errors under burst load. That class of failure is now
1704
+ structurally extinct: a retry fires only when no SQL was sent
1705
+ (``PoolAcquireTimeoutError`` — see its docstring for the contract), or,
1706
+ a layer down, when the server states it rolled the statement back.
1707
+ Never on "the error message contains the word timeout".
1698
1708
  """
1699
- last_exc: BaseException | None = None
1700
1709
  for attempt in range(1 + max_retries):
1701
1710
  try:
1702
- return await cls.execute_query(config_name, query, *args, timeout=timeout)
1711
+ # A lock collision is NOT retried here: ``execute_query`` already
1712
+ # re-issues the statement on the ONE knob-driven loop
1713
+ # (``run_with_lock_retry``), one layer down and for reads too. A
1714
+ # second budget living here is the duplicate this was reconciled
1715
+ # out of.
1716
+ return await cls.execute_query(
1717
+ config_name,
1718
+ query,
1719
+ *args,
1720
+ timeout=timeout,
1721
+ statement_timeout=statement_timeout,
1722
+ )
1703
1723
  except (PoolAcquireTimeoutError, ReadOnlyTransactionError) as exc:
1704
1724
  if attempt >= max_retries:
1705
1725
  raise
1706
- last_exc = exc
1707
1726
  backoff = 0.5 * (2**attempt)
1708
1727
  logger.info(
1709
1728
  "[matrx_orm] Write retry %d/%d for '%s' after %s (backoff %.1fs)",
@@ -1715,9 +1734,10 @@ class AsyncDatabaseManager:
1715
1734
  )
1716
1735
  await asyncio.sleep(backoff)
1717
1736
 
1718
- if last_exc is not None:
1719
- raise last_exc
1720
- return []
1737
+ # Unreachable: the handler above re-raises on its last attempt rather than
1738
+ # falling through. Kept explicit so no future edit can turn a spent retry
1739
+ # budget into an empty result that reads as "the UPDATE matched no rows".
1740
+ raise AssertionError("write retry loop fell through without a result")
1721
1741
 
1722
1742
  @classmethod
1723
1743
  async def cleanup(cls):
@@ -658,8 +658,18 @@ class Model(RuntimeMixin, metaclass=ModelMeta):
658
658
  )
659
659
 
660
660
  @classmethod
661
- async def create(cls, **kwargs: Any) -> Self:
662
- instance = await create.create_instance(model_cls=cls, **kwargs)
661
+ async def create(cls, *, statement_timeout: float | None = None, **kwargs: Any) -> Self:
662
+ """INSERT one row.
663
+
664
+ ``statement_timeout`` (seconds, keyword-only) overrides the pool's
665
+ ``command_timeout`` for THIS insert only — the write-side twin of
666
+ ``QueryBuilder.timeout()``. Use it when the row's size is known to need
667
+ longer than the pool default; it never widens the pool-acquire timeout
668
+ and never affects another statement.
669
+ """
670
+ instance = await create.create_instance(
671
+ model_cls=cls, statement_timeout=statement_timeout, **kwargs
672
+ )
663
673
  await StateManager.cache(cls, instance)
664
674
  return cast(Self, instance)
665
675
 
@@ -980,12 +990,22 @@ class Model(RuntimeMixin, metaclass=ModelMeta):
980
990
  filters: dict[str, Any],
981
991
  *,
982
992
  expected_version: int | None = None,
993
+ lock_rows_in_pk_order: bool = False,
983
994
  **updates: Any,
984
995
  ) -> UpdateResult:
985
996
  """Bulk-update rows matching filters without fetching them first.
986
997
 
987
998
  Args:
988
999
  filters: Lookup kwargs (same syntax as filter()).
1000
+ lock_rows_in_pk_order: take the row locks ascending by primary key
1001
+ instead of in plan-scan order, by routing the WHERE through
1002
+ ``... IN (SELECT pk ... ORDER BY pk FOR UPDATE)``. Pass it on
1003
+ any multi-row UPDATE that concurrent callers can run over an
1004
+ OVERLAPPING row set — without it the lock order is whatever the
1005
+ plan scans (and heap order moves as the updates themselves
1006
+ rewrite rows), which is how two identical statements deadlock
1007
+ (SQLSTATE 40P01). Needs a single-column primary key. Details
1008
+ and the incident: ``QueryBuilder.lock_rows_in_pk_order``.
989
1009
  expected_version: opt-in optimistic concurrency — compare-and-swap
990
1010
  against the canonical ``version`` column. Requires the filters
991
1011
  to address ONE row (every primary-key column present as a plain
@@ -1006,6 +1026,13 @@ class Model(RuntimeMixin, metaclass=ModelMeta):
1006
1026
  print(result.rows_affected)
1007
1027
  """
1008
1028
  if expected_version is not None:
1029
+ if lock_rows_in_pk_order:
1030
+ raise ValueError(
1031
+ f"{cls.__name__}.update_where(expected_version=..., "
1032
+ f"lock_rows_in_pk_order=True) is contradictory: a "
1033
+ f"compare-and-swap already addresses exactly ONE row, so "
1034
+ f"there is no lock ORDER to fix."
1035
+ )
1009
1036
  missing = [pk for pk in cls._meta.primary_keys if pk not in filters]
1010
1037
  if missing:
1011
1038
  raise ValueError(
@@ -1016,7 +1043,10 @@ class Model(RuntimeMixin, metaclass=ModelMeta):
1016
1043
  return await update.guarded_update(
1017
1044
  cls, dict(filters), dict(updates), expected_version=expected_version
1018
1045
  )
1019
- return await QueryBuilder(cls).filter(**filters).update(**updates)
1046
+ qb = QueryBuilder(cls).filter(**filters)
1047
+ if lock_rows_in_pk_order:
1048
+ qb = qb.lock_rows_in_pk_order()
1049
+ return await qb.update(**updates)
1020
1050
 
1021
1051
  @classmethod
1022
1052
  async def delete_where(cls, *, expected_version: int | None = None, **filters: Any) -> int:
@@ -69,7 +69,11 @@ from typing import Any, Literal
69
69
 
70
70
  from matrx_orm.core.async_db_manager import AsyncDatabaseManager
71
71
  from matrx_orm.core.transaction import get_active_connection
72
- from matrx_orm.exceptions import QueryTimeoutError
72
+ from matrx_orm.core.write_retry import (
73
+ is_confidently_aborted_lock_contention,
74
+ run_with_lock_retry,
75
+ )
76
+ from matrx_orm.exceptions import LockContentionError, QueryTimeoutError
73
77
 
74
78
  __all__ = ["call_function", "call_function_sync", "ArrayArg"]
75
79
 
@@ -263,10 +267,33 @@ async def call_function( # noqa: ASYNC109 — maps onto asyncpg's per-call time
263
267
 
264
268
  active_conn = get_active_connection()
265
269
  if active_conn is not None:
266
- return await invoke(active_conn)
270
+ # Inside a caller's transaction a rollback took the whole block; only the
271
+ # transaction's owner can redo the unit, so no retry here — the refusal is
272
+ # typed the same way ``execute_query``'s transaction branch types it.
273
+ try:
274
+ return await invoke(active_conn)
275
+ except Exception as exc:
276
+ if not is_confidently_aborted_lock_contention(exc):
277
+ raise
278
+ raise LockContentionError(
279
+ original_error=exc,
280
+ query=sql,
281
+ args=bound_args,
282
+ config_name=database,
283
+ attempts=1,
284
+ in_transaction=True,
285
+ ) from None
286
+
287
+ async def _attempt() -> Any:
288
+ async with AsyncDatabaseManager.get_connection(database) as conn:
289
+ return await invoke(conn)
267
290
 
268
- async with AsyncDatabaseManager.get_connection(database) as conn:
269
- return await invoke(conn)
291
+ # An RPC reaches Postgres on a RAW connection, bypassing execute_query
292
+ # entirely, so it needs THE ONE loop explicitly — otherwise a deadlock inside
293
+ # any SECURITY DEFINER write RPC still costs the caller its request.
294
+ return await run_with_lock_retry(
295
+ _attempt, config_name=database, query=sql, args=bound_args
296
+ )
270
297
 
271
298
 
272
299
  def call_function_sync(