matrx-orm 3.1.243__tar.gz → 3.1.244__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 (449) hide show
  1. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/CHANGELOG.md +14 -0
  2. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/PKG-INFO +1 -1
  3. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/__init__.py +6 -0
  4. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/adapters/postgrest_client_adapter.py +9 -0
  5. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/expressions.py +76 -0
  6. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/fields.py +37 -0
  7. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/FEATURE.md +18 -0
  8. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/db_functions.py +11 -1
  9. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/update.py +36 -3
  10. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/query/executor.py +4 -1
  11. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/pyproject.toml +1 -1
  12. matrx_orm-3.1.244/tests/test_instance_update_runs_expressions.py +237 -0
  13. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/.gitignore +0 -0
  14. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/AGENTS.md +0 -0
  15. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/BASE_CLASS_METHODS.md +0 -0
  16. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/CLAUDE.md +0 -0
  17. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/MODEL_API.md +0 -0
  18. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/README.md +0 -0
  19. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/database/orm/extended/managers/ai_model_base.py +0 -0
  20. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/docs/migrations.md +0 -0
  21. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/FEATURE.md +0 -0
  22. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/MODULE_README.md +0 -0
  23. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/adapters/MODULE_README.md +0 -0
  24. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/adapters/__init__.py +0 -0
  25. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/adapters/async_postgresql.py +0 -0
  26. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/adapters/base_adapter.py +0 -0
  27. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/adapters/supabase_adapter.py +0 -0
  28. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/admin/FEATURE.md +0 -0
  29. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/admin/__init__.py +0 -0
  30. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/admin/router.py +0 -0
  31. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/api/__init__.py +0 -0
  32. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/api/auth.py +0 -0
  33. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/api/config.py +0 -0
  34. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/api/handlers.py +0 -0
  35. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/api/protocol.py +0 -0
  36. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/api/server.py +0 -0
  37. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/associations.py +0 -0
  38. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/cache_debug.py +0 -0
  39. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/catalog.py +0 -0
  40. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/catalog_sql.py +0 -0
  41. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/client/MODULE_README.md +0 -0
  42. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/client/__init__.py +0 -0
  43. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/client/postgres_connection.py +0 -0
  44. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/client/postgrest.py +0 -0
  45. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/client/supabase_auth.py +0 -0
  46. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/client/supabase_config.py +0 -0
  47. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/client/supabase_manager.py +0 -0
  48. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/EXTENDED-TASK.md +0 -0
  49. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/FEATURE.md +0 -0
  50. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/MODULE_README.md +0 -0
  51. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
  52. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/TASKS.md +0 -0
  53. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/__init__.py +0 -0
  54. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/admin_lane.py +0 -0
  55. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/async_db_manager.py +0 -0
  56. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/base.py +0 -0
  57. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/config.py +0 -0
  58. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/connections.py +0 -0
  59. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/db_function.py +0 -0
  60. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/diagnostics.py +0 -0
  61. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/extended.py +0 -0
  62. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/foreign_session_state.py +0 -0
  63. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/introspection.py +0 -0
  64. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/listen.py +0 -0
  65. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/loop_filters.py +0 -0
  66. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/model_dto.py +0 -0
  67. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/model_view.py +0 -0
  68. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/one_database.py +0 -0
  69. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/paginator.py +0 -0
  70. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/pool_maintenance.py +0 -0
  71. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/pool_watch.py +0 -0
  72. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/pooler_role.py +0 -0
  73. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/pydantic_bridge.py +0 -0
  74. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/read_write.py +0 -0
  75. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/registry.py +0 -0
  76. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/relations.py +0 -0
  77. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/resilience.py +0 -0
  78. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/rls_session.py +0 -0
  79. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/session_context.py +0 -0
  80. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/signals.py +0 -0
  81. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/statement_ceiling.py +0 -0
  82. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/tls.py +0 -0
  83. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/transaction.py +0 -0
  84. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/types.py +0 -0
  85. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/write_queue.py +0 -0
  86. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/core/write_retry.py +0 -0
  87. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/entity.py +0 -0
  88. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/error_handling.py +0 -0
  89. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/exceptions.py +0 -0
  90. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/extended/__init__.py +0 -0
  91. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/extended/app_error_handler.py +0 -0
  92. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/identity_rule.py +0 -0
  93. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/local_sqlite.py +0 -0
  94. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/middleware/__init__.py +0 -0
  95. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/middleware/base.py +0 -0
  96. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/migrations/MODULE_README.md +0 -0
  97. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/migrations/__init__.py +0 -0
  98. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/migrations/cli.py +0 -0
  99. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/migrations/ddl.py +0 -0
  100. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/migrations/diff.py +0 -0
  101. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/migrations/executor.py +0 -0
  102. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/migrations/integration.py +0 -0
  103. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/migrations/loader.py +0 -0
  104. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/migrations/operations.py +0 -0
  105. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/migrations/state.py +0 -0
  106. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/migrations/table_filter.py +0 -0
  107. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/no_org_assignment.py +0 -0
  108. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/MODULE_README.md +0 -0
  109. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/__init__.py +0 -0
  110. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/bulk_update_values.py +0 -0
  111. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/catalog_select.py +0 -0
  112. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/conflict.py +0 -0
  113. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/conflict_writes.py +0 -0
  114. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/create.py +0 -0
  115. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/delete.py +0 -0
  116. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/dynamic_admin.py +0 -0
  117. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/dynamic_crud.py +0 -0
  118. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/graph_walk.py +0 -0
  119. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/latest_rows.py +0 -0
  120. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/queue_claim.py +0 -0
  121. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/read.py +0 -0
  122. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/relational_backfill.py +0 -0
  123. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/operations/staging_load.py +0 -0
  124. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/platform_access.py +0 -0
  125. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/platform_db.py +0 -0
  126. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/py.typed +0 -0
  127. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/pytest_lock_guard.py +0 -0
  128. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/pytest_plugin.py +0 -0
  129. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/python_sql/MODULE_README.md +0 -0
  130. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/python_sql/__init__.py +0 -0
  131. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/python_sql/db_objects.py +0 -0
  132. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
  133. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
  134. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/query/FEATURE.md +0 -0
  135. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/query/MODULE_README.md +0 -0
  136. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/query/__init__.py +0 -0
  137. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/query/builder.py +0 -0
  138. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/resources/__init__.py +0 -0
  139. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/resources/additional-assigners-2026-09-12.json +0 -0
  140. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/resources/org_assignment_trigger_baseline.json +0 -0
  141. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/FEATURE.md +0 -0
  142. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/MODULE_README.md +0 -0
  143. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/__init__.py +0 -0
  144. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/code_handler.py +0 -0
  145. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/codegen_writer.py +0 -0
  146. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/columns.py +0 -0
  147. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/common.py +0 -0
  148. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/diff_preview.py +0 -0
  149. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
  150. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/excluded_targets.py +0 -0
  151. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/generator.py +0 -0
  152. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
  153. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
  154. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
  155. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
  156. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/package_wiring.py +0 -0
  157. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/relationships.py +0 -0
  158. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/runner.py +0 -0
  159. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/schema.py +0 -0
  160. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/schema_manager.py +0 -0
  161. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/tables.py +0 -0
  162. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/schema_builder/views.py +0 -0
  163. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/FEATURE.md +0 -0
  164. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/__init__.py +0 -0
  165. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/attachments.py +0 -0
  166. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/backup_restore.py +0 -0
  167. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/crypto.py +0 -0
  168. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/host_authorization.py +0 -0
  169. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/items.py +0 -0
  170. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/model.py +0 -0
  171. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/mutation_receipts.py +0 -0
  172. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/native_enable.py +0 -0
  173. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/native_lifecycle.py +0 -0
  174. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/native_passkeys.py +0 -0
  175. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/native_source.py +0 -0
  176. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/org_service.py +0 -0
  177. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/org_vault_gate.py +0 -0
  178. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/service.py +0 -0
  179. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/secrets_battery/vault_history.py +0 -0
  180. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/session/README.md +0 -0
  181. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/session/__init__.py +0 -0
  182. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/session/coalesce.py +0 -0
  183. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/session/dag.py +0 -0
  184. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/session/errors.py +0 -0
  185. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/session/fallback.py +0 -0
  186. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/session/flush.py +0 -0
  187. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/session/lifecycle.py +0 -0
  188. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/session/managed.py +0 -0
  189. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/session/op.py +0 -0
  190. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/session/reads.py +0 -0
  191. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/session/session.py +0 -0
  192. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/session/telemetry.py +0 -0
  193. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/sql_executor/MODULE_README.md +0 -0
  194. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/sql_executor/__init__.py +0 -0
  195. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/sql_executor/executor.py +0 -0
  196. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/sql_executor/queries.py +0 -0
  197. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/sql_executor/registry.py +0 -0
  198. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/sql_executor/types.py +0 -0
  199. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/sql_executor/utils.py +0 -0
  200. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/state.py +0 -0
  201. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/utils/__init__.py +0 -0
  202. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/utils/sql_utils.py +0 -0
  203. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/utils/type_converters.py +0 -0
  204. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/matrx_orm/write_actor.py +0 -0
  205. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/scripts/git-branches.sh +0 -0
  206. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/scripts/publish.sh +0 -0
  207. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/scripts/release.sh +0 -0
  208. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/MODULE_README.md +0 -0
  209. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/__init__.py +0 -0
  210. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/conftest.py +0 -0
  211. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/MODULE_README.md +0 -0
  212. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/__init__.py +0 -0
  213. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_adapter_registry.py +0 -0
  214. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_admin_db_columns.py +0 -0
  215. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_admin_response_rendering.py +0 -0
  216. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_admin_search_negation.py +0 -0
  217. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_admin_search_sentinels.py +0 -0
  218. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_advisory_lock.py +0 -0
  219. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_agent_message.py +0 -0
  220. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_api_auth.py +0 -0
  221. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_api_config.py +0 -0
  222. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_api_handlers.py +0 -0
  223. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_api_protocol.py +0 -0
  224. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_array_agg_order_by.py +0 -0
  225. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_association_trigger_actor_naming.py +0 -0
  226. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_associations.py +0 -0
  227. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_associations_wiring.py +0 -0
  228. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_borrowed_role_never_reaches_server_calls.py +0 -0
  229. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_bulk_hydration_offload.py +0 -0
  230. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_bulk_update_by_pk.py +0 -0
  231. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
  232. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_cache_debug.py +0 -0
  233. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_call_function.py +0 -0
  234. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_case_when.py +0 -0
  235. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_catalog.py +0 -0
  236. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_column_scoped_writes.py +0 -0
  237. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_committed_row_guard.py +0 -0
  238. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_composite_pk_filter.py +0 -0
  239. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_composite_pk_write_paths.py +0 -0
  240. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_config.py +0 -0
  241. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_conflict_target.py +0 -0
  242. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_conflict_writes.py +0 -0
  243. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_connection_codecs.py +0 -0
  244. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_connection_poison_guard.py +0 -0
  245. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_connection_primitives.py +0 -0
  246. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_count_composite_distinct.py +0 -0
  247. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_count_limit_param_leak.py +0 -0
  248. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_db_function_field.py +0 -0
  249. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_ddl_generator.py +0 -0
  250. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_deadlock_retry.py +0 -0
  251. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_derived_table.py +0 -0
  252. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_dirty_tracking.py +0 -0
  253. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_dynamic_crud.py +0 -0
  254. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_exceptions.py +0 -0
  255. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
  256. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_expression_primitives.py +0 -0
  257. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_fetch_on_init_holds_whole_table.py +0 -0
  258. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_fields.py +0 -0
  259. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
  260. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_fk_cycle_resolution.py +0 -0
  261. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_from_alias.py +0 -0
  262. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_fts.py +0 -0
  263. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_func_expression_args.py +0 -0
  264. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_graph_walk.py +0 -0
  265. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_guarded_read_never_reruns_empty.py +0 -0
  266. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_having_annotate_alias.py +0 -0
  267. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
  268. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_introspect_rls_policies.py +0 -0
  269. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_join_and_aggregate.py +0 -0
  270. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_latest_rows.py +0 -0
  271. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_listen_notify.py +0 -0
  272. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_local_sqlite.py +0 -0
  273. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_lock_contention_retry.py +0 -0
  274. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_managed_write_guard.py +0 -0
  275. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_matrx_entity.py +0 -0
  276. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_migration_diff_types.py +0 -0
  277. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_migration_loader.py +0 -0
  278. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_model_instance.py +0 -0
  279. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_model_meta.py +0 -0
  280. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_optimistic_concurrency.py +0 -0
  281. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_order_by_expression.py +0 -0
  282. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_org_assignment_inventory.py +0 -0
  283. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_orm_banners_are_client_safe.py +0 -0
  284. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_parameter_name_hydration.py +0 -0
  285. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_patch_jsonb_path.py +0 -0
  286. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_percentile_cont.py +0 -0
  287. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_persisted_count.py +0 -0
  288. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_person_scoped_session_bypasses_shared_cache.py +0 -0
  289. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_pool_create_never_wedges.py +0 -0
  290. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_pool_holder_census.py +0 -0
  291. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_pool_loop_recreation.py +0 -0
  292. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_pool_maintenance.py +0 -0
  293. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_postgrest_filters.py +0 -0
  294. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_pydantic_bridge.py +0 -0
  295. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_query_builder.py +0 -0
  296. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_query_error_detail.py +0 -0
  297. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_query_executor_sql.py +0 -0
  298. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_query_timeout.py +0 -0
  299. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
  300. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_queue_claim.py +0 -0
  301. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_read_connection_retry.py +0 -0
  302. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_read_write_declared.py +0 -0
  303. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_registry.py +0 -0
  304. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_registry_multi_database.py +0 -0
  305. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_registry_multi_schema.py +0 -0
  306. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_relational_backfill.py +0 -0
  307. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_relations.py +0 -0
  308. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_resilience.py +0 -0
  309. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_rls_session_scope.py +0 -0
  310. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_schema_exists.py +0 -0
  311. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_session_advisory_lock.py +0 -0
  312. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_standalone_write_session_context.py +0 -0
  313. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_state_cache.py +0 -0
  314. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_subquery_filter_raw.py +0 -0
  315. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_subquery_in_filter.py +0 -0
  316. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_subquery_nested_expressions.py +0 -0
  317. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_supabase_auth.py +0 -0
  318. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_supabase_config.py +0 -0
  319. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_sync_pool_declares_read_write.py +0 -0
  320. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_tls.py +0 -0
  321. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_transaction_database_identity.py +0 -0
  322. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_transaction_gucs_one_round_trip.py +0 -0
  323. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_transaction_pool_release_sends_nothing.py +0 -0
  324. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_typed_join_projection.py +0 -0
  325. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_typed_predicates.py +0 -0
  326. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_update_case_expression.py +0 -0
  327. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_update_subquery.py +0 -0
  328. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_update_with_rebase.py +0 -0
  329. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_upsert_default_leak.py +0 -0
  330. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_upsert_with_conflict.py +0 -0
  331. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_values_jsonb_decode.py +0 -0
  332. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_watched_lifecycle.py +0 -0
  333. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_write_queue_deadline.py +0 -0
  334. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level1/test_write_retry_semantics.py +0 -0
  335. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level2/MODULE_README.md +0 -0
  336. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level2/__init__.py +0 -0
  337. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level2/conftest.py +0 -0
  338. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level2/test_bulk_ops.py +0 -0
  339. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level2/test_cache_integration.py +0 -0
  340. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level2/test_crud.py +0 -0
  341. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level2/test_foreign_keys.py +0 -0
  342. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level2/test_m2m.py +0 -0
  343. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level2/test_manager.py +0 -0
  344. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level2/test_migrations_live.py +0 -0
  345. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level2/test_query_execution.py +0 -0
  346. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level2/test_schema_diff.py +0 -0
  347. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/level2/verify_committed_row_guard_pg17.py +0 -0
  348. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/live_pooler_role.py +0 -0
  349. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/native_source_cases.py +0 -0
  350. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/native_source_oracle.rs +0 -0
  351. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/sample_project/.env.example +0 -0
  352. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/sample_project/README.md +0 -0
  353. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/sample_project/__init__.py +0 -0
  354. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/sample_project/generate.py +0 -0
  355. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/sample_project/generated/.gitkeep +0 -0
  356. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/sample_project/matrx_orm.yaml +0 -0
  357. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/sample_project/test_schema_generation.py +0 -0
  358. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/sample_project_desktop/.env.example +0 -0
  359. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/sample_project_desktop/README.md +0 -0
  360. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/sample_project_desktop/__init__.py +0 -0
  361. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/sample_project_desktop/client_example.py +0 -0
  362. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/sample_project_desktop/client_example.ts +0 -0
  363. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/sample_project_desktop/client_supabase_example.py +0 -0
  364. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/sample_project_desktop/server.py +0 -0
  365. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema/entity_tests.py +0 -0
  366. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema/test_base_generation.py +0 -0
  367. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema/test_composite_pk_fk_generation.py +0 -0
  368. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema/test_composite_unique_generation.py +0 -0
  369. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema/test_dto_identity_generation.py +0 -0
  370. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema/test_entity_capability_generation.py +0 -0
  371. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema/test_enum_ordering.py +0 -0
  372. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema/test_excluded_fk_targets.py +0 -0
  373. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema/test_generate_schema.py +0 -0
  374. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema/test_junction_analysis.py +0 -0
  375. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema/test_multi_schema_output.py +0 -0
  376. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema/test_schema_analysis.py +0 -0
  377. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema/test_sql_expression_defaults.py +0 -0
  378. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema/test_table_relationships_query.py +0 -0
  379. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema/test_view_generation.py +0 -0
  380. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema_builder/test_array_default_parsing.py +0 -0
  381. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema_builder/test_enum_array_column.py +0 -0
  382. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema_builder/test_focused_generation_plan.py +0 -0
  383. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema_builder/test_host_models_aggregator.py +0 -0
  384. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/schema_builder/test_never_filter_columns.py +0 -0
  385. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/__init__.py +0 -0
  386. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/conftest.py +0 -0
  387. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_a_failed_capture_still_names_the_failure.py +0 -0
  388. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_a_grouped_update_writes_only_each_rows_own_fields.py +0 -0
  389. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_cache_no_downgrade.py +0 -0
  390. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_capture_poison_proof.py +0 -0
  391. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_coalesce.py +0 -0
  392. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_dag.py +0 -0
  393. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_disk_spill_fallback.py +0 -0
  394. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_flush_individual_fallback.py +0 -0
  395. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_governed_write.py +0 -0
  396. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_managed.py +0 -0
  397. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_op_enqueue_site.py +0 -0
  398. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_pending_insert_honors_operator_filters.py +0 -0
  399. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_reads.py +0 -0
  400. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_record_error_bounded_insert.py +0 -0
  401. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_record_error_storm.py +0 -0
  402. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_session.py +0 -0
  403. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_spill_guard_plugin.py +0 -0
  404. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/session/test_tests_never_write_live_system_error.py +0 -0
  405. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_a_backend_handed_over_mid_transaction_never_takes_our_writes.py +0 -0
  406. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_a_bulk_cache_never_stalls_the_loop.py +0 -0
  407. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_a_keyword_column_refuses_generation.py +0 -0
  408. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_a_list_of_uuids_binds_as_uuid_array.py +0 -0
  409. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_a_read_only_backend_never_refuses_our_writes.py +0 -0
  410. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_a_sealed_field_shows_no_character_of_its_value.py +0 -0
  411. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_array_arg_element_type.py +0 -0
  412. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_attachment_mutation_serialization.py +0 -0
  413. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_backup_alias_conflict.py +0 -0
  414. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_backup_history_restore.py +0 -0
  415. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_backup_restore_live.py +0 -0
  416. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_call_function_as.py +0 -0
  417. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_call_function_none_mode.py +0 -0
  418. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_composite_fk_is_not_a_column_fk.py +0 -0
  419. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_database_name_alias.py +0 -0
  420. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_db_functions.py +0 -0
  421. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_dsn_password_masking.py +0 -0
  422. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_dynamic_select_unregistered.py +0 -0
  423. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_first_asks_the_database_for_one_row.py +0 -0
  424. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_foreign_session_state.py +0 -0
  425. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_immutable_insert_or_exact_verify.py +0 -0
  426. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_keyed_digest.py +0 -0
  427. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_model_cls_refactor.py +0 -0
  428. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_native_enable.py +0 -0
  429. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_native_lifecycle.py +0 -0
  430. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_native_lifecycle_fences.py +0 -0
  431. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_native_passkey_receipts.py +0 -0
  432. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_native_source.py +0 -0
  433. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_no_blocking_asyncpg_tls.py +0 -0
  434. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_one_database.py +0 -0
  435. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_order_by_desc_nulls_last.py +0 -0
  436. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_org_vault_gate.py +0 -0
  437. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_package_wiring_views.py +0 -0
  438. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_pooler_role_guard.py +0 -0
  439. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_secret_audit_names_its_organization.py +0 -0
  440. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_secrets_battery.py +0 -0
  441. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_secrets_minting_keys.py +0 -0
  442. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_shared_lock_guard.py +0 -0
  443. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_sql_param_casts.py +0 -0
  444. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_state_cache_sweeps_stale.py +0 -0
  445. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_statement_ceiling_warning.py +0 -0
  446. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_sync_twins_guard.py +0 -0
  447. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_update_result_truthiness.py +0 -0
  448. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_user_visible_lookup.py +0 -0
  449. {matrx_orm-3.1.243 → matrx_orm-3.1.244}/tests/test_vault_aggregate_recovery_stateful.py +0 -0
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - **Fix: an instance `update()` / `save()` runs a SQL expression instead of storing it.**
6
+ `row.update(metadata=JsonbMerge("metadata", {...}))` sent the expression through
7
+ `JSONField.get_db_prep_value`, and `json.dumps` wrote its attribute dict over the
8
+ column (1,328 live rows lost their metadata, 2026-10-22); a text column stored
9
+ `str(expression)`. `update_instance` and `bulk_update` now hand every expression to
10
+ the executor's SET compiler exactly as `update_where` does, and the instance ends
11
+ holding the computed value (re-read when RETURNING did not carry it). `F("col")` is
12
+ now a valid SET value. Every serialising path refuses an expression loudly with the
13
+ new `ExpressionAsDataError` (a `TypeError`): all `Field.get_db_prep_value`
14
+ implementations (wrapped per subclass), the json encoders (nested expressions), json
15
+ function arguments, and PostgREST adapter writes. New export `is_sql_expression`.
16
+ Guard: `tests/test_instance_update_runs_expressions.py` (9 live round trips; all 9
17
+ red on the prior code).
18
+
5
19
  - **Feature: entity doors in the associations engine.** A registry token may resolve to a
6
20
  *door* (`__entity_door__ = True`, `async load_by_ids(ids, *, actor_id) -> {id: row}`) instead of
7
21
  a Model, for nouns whose rows live behind the host's read doors (a record store). The engine
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: matrx-orm
3
- Version: 3.1.243
3
+ Version: 3.1.244
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
@@ -129,7 +129,10 @@ from .core.expressions import (
129
129
  Like,
130
130
  Regex,
131
131
  JsonbConcat,
132
+ ExpressionAsDataError,
132
133
  JsonbMerge,
134
+ JsonbRemoveKeys,
135
+ is_sql_expression,
133
136
  JsonbObjectAgg,
134
137
  Lag,
135
138
  LastValue,
@@ -645,7 +648,10 @@ __all__ = [
645
648
  "ArrayIndex",
646
649
  "JsonText",
647
650
  "JsonbConcat",
651
+ "ExpressionAsDataError",
648
652
  "JsonbMerge",
653
+ "JsonbRemoveKeys",
654
+ "is_sql_expression",
649
655
  "JsonbObjectAgg",
650
656
  "Filter",
651
657
  "Case",
@@ -225,6 +225,15 @@ class PostgRESTClientAdapter(BaseAdapter):
225
225
  filters = self._translate_filters(query_dict.get("filters", {}))
226
226
  data: dict | list = query_dict.get("data", {})
227
227
 
228
+ # PostgREST takes JSON rows, not SQL: an expression object here would be
229
+ # serialised as data. Refuse before anything is sent.
230
+ from matrx_orm.core.expressions import ExpressionAsDataError, is_sql_expression
231
+
232
+ for row in data if isinstance(data, list) else [data]:
233
+ for column, value in (row or {}).items():
234
+ if is_sql_expression(value):
235
+ raise ExpressionAsDataError(column, value)
236
+
228
237
  logger.debug("[PostgRESTClientAdapter] %s %s", operation.upper(), table)
229
238
 
230
239
  try:
@@ -1575,6 +1575,34 @@ class JsonbMerge(Func):
1575
1575
  return f"JsonbMerge({self.args[0]!r}, {self.value!r})"
1576
1576
 
1577
1577
 
1578
+ class JsonbRemoveKeys(Func):
1579
+ """``COALESCE(field, '{}'::jsonb) - $keys::text[]`` — atomically drop top-level
1580
+ keys from a JSONB column (the removal sibling of :class:`JsonbMerge`), for use as
1581
+ an ``.update()`` / ``update_where()`` SET value. Every other key is untouched;
1582
+ a key that is absent is a no-op.
1583
+
1584
+ Usage::
1585
+
1586
+ await SiteKeywordValue.update_where(
1587
+ {"id__in": ids}, metadata=JsonbRemoveKeys("metadata", ["serp_cluster"])
1588
+ )
1589
+ """
1590
+
1591
+ function = "JSONB_REMOVE_KEYS"
1592
+
1593
+ def __init__(self, field: str, keys: list[str] | tuple[str, ...]) -> None:
1594
+ super().__init__(field)
1595
+ self.keys = [str(k) for k in keys]
1596
+
1597
+ def as_sql(self, params: list[Any]) -> str:
1598
+ inner = _render_func_arg(self.args[0], params)
1599
+ params.append(self.keys)
1600
+ return f"COALESCE({inner}, '{{}}'::jsonb) - ${len(params)}::text[]"
1601
+
1602
+ def __repr__(self) -> str:
1603
+ return f"JsonbRemoveKeys({self.args[0]!r}, {self.keys!r})"
1604
+
1605
+
1578
1606
  class JsonbConcat(Func):
1579
1607
  """``a || b`` — jsonb concat of two RENDERABLE expressions (columns,
1580
1608
  ``F()``/``Excluded()`` references, or nested ``Func``s), top-level keys
@@ -2566,3 +2594,51 @@ class TsMatch:
2566
2594
  f"TsMatch({self.column!r}, config={self.config!r}, "
2567
2595
  f"column_is_tsvector={self.column_is_tsvector})"
2568
2596
  )
2597
+
2598
+
2599
+ # ---------------------------------------------------------------------------
2600
+ # Expression-as-data guard
2601
+ # ---------------------------------------------------------------------------
2602
+
2603
+ # Expression types that carry no ``as_sql`` of their own (they are rendered by
2604
+ # their consumers) — listed so the predicate below still recognises them.
2605
+ _EXPRESSION_TYPES_WITHOUT_AS_SQL: tuple[type, ...] = (F, Q, OuterRef)
2606
+
2607
+
2608
+ def is_sql_expression(value: Any) -> bool:
2609
+ """True when *value* is a SQL expression object, never a storable value.
2610
+
2611
+ Every class in this module is SQL: either it renders itself through
2612
+ ``as_sql`` (``Func`` and all its subclasses — ``JsonbMerge``, ``Now``,
2613
+ ``Coalesce``…, ``Expression``/``BinaryExpr`` increments, ``Case``,
2614
+ ``Subquery``, ``Value``, ``Excluded``, predicates, ``Window``…) or it is
2615
+ one of the few renderer-side references (``F``, ``Q``, ``OuterRef``).
2616
+ ``as_sql`` is looked up on the CLASS, so a test double whose instance
2617
+ answers every attribute (``MagicMock``) is not mistaken for SQL.
2618
+ """
2619
+ if value is None or isinstance(value, type):
2620
+ return False
2621
+ if isinstance(value, _EXPRESSION_TYPES_WITHOUT_AS_SQL):
2622
+ return True
2623
+ return any("as_sql" in vars(klass) for klass in type(value).__mro__)
2624
+
2625
+
2626
+ class ExpressionAsDataError(TypeError):
2627
+ """A SQL expression object reached a path that would store it as data.
2628
+
2629
+ Raised instead of serialising the object (a jsonb column used to receive
2630
+ ``{"args": [...], "value": ...}`` — the 2026-10-22 metadata loss on 1,328
2631
+ rows). Expressions are compiled to SQL by ``Model.update_where`` /
2632
+ ``QueryBuilder.update`` and by instance ``update()`` / ``save()``; every
2633
+ other write path (INSERT, upsert data, filters) refuses them here.
2634
+ """
2635
+
2636
+ def __init__(self, field_name: str | None, value: Any) -> None:
2637
+ self.field_name = field_name
2638
+ self.value = value
2639
+ super().__init__(
2640
+ f"{type(value).__name__} is a SQL expression, not a value — refusing to "
2641
+ f"serialise {value!r} into field {field_name!r}. Expressions run as SQL only "
2642
+ f"in an UPDATE SET: use Model.update_where(filters, {field_name}=...) or "
2643
+ f"instance.update({field_name}=...)."
2644
+ )
@@ -44,7 +44,35 @@ def _strip_nul_bytes(value: Any) -> Any:
44
44
  return value
45
45
 
46
46
 
47
+ def _refuse_expression_as_data(prep: Any) -> Any:
48
+ """Wrap a ``get_db_prep_value`` so a SQL expression object can never be
49
+ serialised as a stored value (``json.dumps`` / ``str()`` of a ``JsonbMerge``
50
+ wrote its attribute dict into live jsonb columns). Every Field subclass is
51
+ wrapped at class creation, so a new field type cannot forget the check."""
52
+ import functools
53
+
54
+ if getattr(prep, "_refuses_expressions", False):
55
+ return prep
56
+
57
+ @functools.wraps(prep)
58
+ def guarded(self: Any, value: Any, *args: Any, **kwargs: Any) -> Any:
59
+ from matrx_orm.core.expressions import ExpressionAsDataError, is_sql_expression
60
+
61
+ if is_sql_expression(value):
62
+ raise ExpressionAsDataError(getattr(self, "name", None), value)
63
+ return prep(self, value, *args, **kwargs)
64
+
65
+ guarded._refuses_expressions = True # type: ignore[attr-defined]
66
+ return guarded
67
+
68
+
47
69
  class Field:
70
+ def __init_subclass__(cls, **kwargs: Any) -> None:
71
+ super().__init_subclass__(**kwargs)
72
+ own = cls.__dict__.get("get_db_prep_value")
73
+ if own is not None:
74
+ cls.get_db_prep_value = _refuse_expression_as_data(own) # type: ignore[method-assign]
75
+
48
76
  if TYPE_CHECKING:
49
77
 
50
78
  @overload
@@ -147,6 +175,8 @@ class Field:
147
175
  return None
148
176
  return value
149
177
 
178
+ get_db_prep_value = _refuse_expression_as_data(get_db_prep_value)
179
+
150
180
  def desc(self):
151
181
  """Mark this field for descending order."""
152
182
  self._order_direction = "DESC"
@@ -1166,6 +1196,13 @@ class JSONBField(Field, Generic[_JT]):
1166
1196
  import uuid as _uuid
1167
1197
  from decimal import Decimal as _Decimal
1168
1198
 
1199
+ from matrx_orm.core.expressions import ExpressionAsDataError, is_sql_expression
1200
+
1201
+ # A SQL expression nested inside a json value is never data — degrading
1202
+ # it to its attribute dict is exactly how JsonbMerge objects were stored.
1203
+ if is_sql_expression(obj):
1204
+ raise ExpressionAsDataError(None, obj)
1205
+
1169
1206
  # Pydantic v2 — preferred for the JSON-mode dump that handles nested
1170
1207
  # exotic types correctly.
1171
1208
  model_dump = getattr(obj, "model_dump", None)
@@ -34,6 +34,21 @@ READ depends on the database, not on the deploy. `catalog_select`:
34
34
  Guard: `tests/test_dynamic_select_unregistered.py` (failing before the fallback
35
35
  existed), plus `tests/level1/test_dynamic_crud.py` for the write-side refusals.
36
36
 
37
+ ## SQL expressions are compiled, never stored
38
+
39
+ An expression object (`JsonbMerge`, `F(...) + 1`, `Coalesce`, `Now()`, `Case`,
40
+ `Subquery`, `Value`, `Excluded`, any `Func` or class with `as_sql`, plus `F`/`Q`/
41
+ `OuterRef`) is SQL, never data. `update_where`, `QueryBuilder.update`, instance
42
+ `update()`/`save()` (`update_instance`) and `bulk_update` all hand it to the
43
+ executor's SET compiler; the instance ends holding the database-computed value.
44
+ Every other path that would serialise one — INSERT data, upsert rows, a json
45
+ value nesting one, a json function argument, a PostgREST write — raises
46
+ `ExpressionAsDataError` before anything is written (`Field.get_db_prep_value` is
47
+ wrapped for every Field subclass, and the json encoders refuse nested ones).
48
+ Predicate: `matrx_orm.is_sql_expression`. Guard:
49
+ `tests/test_instance_update_runs_expressions.py` (live round trip on a disposable
50
+ loopback Postgres, `ISOLATED_PG17_DSN`; red on the prior code).
51
+
37
52
  ## Event-loop safety
38
53
 
39
54
  - **Bulk row coercion never runs on the event loop.** `bulk_insert_ignore` and
@@ -51,6 +66,9 @@ Verified against `conflict_writes.py` and
51
66
 
52
67
  ## Change Log
53
68
 
69
+ - 2026-10-22 — Instance `update()`/`save()`/`bulk_update` compile SQL expressions;
70
+ every serialising path refuses them (`ExpressionAsDataError`).
71
+
54
72
  - 2026-09-26 — Added the typed `update_where` guard to `upsert_with_conflict`.
55
73
 
56
74
  - 2026-09-11 — Added the live-catalog read fallback so an unregistered view or a
@@ -34,6 +34,16 @@ if TYPE_CHECKING: # pragma: no cover
34
34
  _FUNCTION_NAME_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?$")
35
35
 
36
36
 
37
+ def _json_arg_default(obj: Any) -> str:
38
+ """``default=str`` for json-bound function args — except a SQL expression,
39
+ which must never be stringified into data."""
40
+ from matrx_orm.core.expressions import ExpressionAsDataError, is_sql_expression
41
+
42
+ if is_sql_expression(obj):
43
+ raise ExpressionAsDataError(None, obj)
44
+ return str(obj)
45
+
46
+
37
47
  def _render_call(function_name: str, args: tuple[Any, ...]) -> tuple[str, list[Any]]:
38
48
  if not _FUNCTION_NAME_RE.match(function_name):
39
49
  raise ValueError(f"call_db_function: invalid function name {function_name!r}")
@@ -47,7 +57,7 @@ def _render_call(function_name: str, args: tuple[Any, ...]) -> tuple[str, list[A
47
57
  values.append(arg)
48
58
  elif isinstance(arg, dict | list):
49
59
  placeholders.append(f"${index}::jsonb")
50
- values.append(json.dumps(arg, ensure_ascii=False, default=str))
60
+ values.append(json.dumps(arg, ensure_ascii=False, default=_json_arg_default))
51
61
  else:
52
62
  placeholders.append(f"${index}")
53
63
  values.append(arg)
@@ -9,7 +9,7 @@ from matrx_orm.core.types import UpdateResult
9
9
  from matrx_orm.exceptions import describe_db_exception
10
10
  from matrx_orm.state import StateManager
11
11
 
12
- from ..core.expressions import F
12
+ from ..core.expressions import F, is_sql_expression
13
13
  from ..query.builder import QueryBuilder
14
14
 
15
15
  if TYPE_CHECKING:
@@ -286,7 +286,9 @@ async def bulk_update(
286
286
  if field_name in model_cls._fields and hasattr(obj, field_name):
287
287
  field = model_cls._fields[field_name]
288
288
  value = getattr(obj, field_name)
289
- if value is not None:
289
+ if is_sql_expression(value):
290
+ update_data[field_name] = value # compiled by the executor
291
+ elif value is not None:
290
292
  update_data[field_name] = field.get_db_prep_value(value)
291
293
 
292
294
  row_filter = dict(pk_filter)
@@ -310,6 +312,14 @@ async def bulk_update(
310
312
  )
311
313
  if result.rows_affected > 0:
312
314
  rows_affected += 1
315
+ # An expression field holds the database-computed value
316
+ # afterwards, never the expression (a later save would
317
+ # re-run it).
318
+ if result.updated_rows:
319
+ returned = result.updated_rows[0]
320
+ for field_name, sent in update_data.items():
321
+ if is_sql_expression(sent) and field_name in returned:
322
+ setattr(obj, field_name, returned[field_name])
313
323
  # Do NOT cache `obj` here. It is the CALLER's instance and is
314
324
  # routinely a partial {pk + payload} stub (see
315
325
  # session/flush.py), so writing it into the identity cache
@@ -461,6 +471,7 @@ async def update_instance(
461
471
  current_version = expected_version
462
472
 
463
473
  update_data: dict[str, Any] = {}
474
+ expression_fields: set[str] = set()
464
475
  field_names: Iterable[str] = (
465
476
  fields if fields is not None else [f for f in model_cls._fields if f not in pk_filter]
466
477
  )
@@ -473,7 +484,14 @@ async def update_instance(
473
484
  continue # handled below — never depends on being in `fields`
474
485
  field = model_cls._fields[field_name]
475
486
  value = getattr(instance, field_name, None)
476
- if value is not None:
487
+ if is_sql_expression(value):
488
+ # SQL, not data: hand it to the executor untouched so it compiles
489
+ # into the SET clause exactly as update_where() does. Running it
490
+ # through get_db_prep_value is what json-dumped JsonbMerge objects
491
+ # into live jsonb columns.
492
+ update_data[field_name] = value
493
+ expression_fields.add(field_name)
494
+ elif value is not None:
477
495
  update_data[field_name] = field.get_db_prep_value(value)
478
496
  elif field.nullable:
479
497
  update_data[field_name] = None
@@ -517,5 +535,20 @@ async def update_instance(
517
535
  elif version_field_name is not None and version_field_name in update_data:
518
536
  setattr(instance, version_field_name, update_data[version_field_name])
519
537
 
538
+ # An expression field must end holding the value the database computed —
539
+ # never the expression object, which a later save() would re-run (a second
540
+ # increment) or a reader would mistake for data. RETURNING normally covers
541
+ # it; re-read whatever it did not.
542
+ unresolved = [f for f in expression_fields if is_sql_expression(getattr(instance, f, None))]
543
+ if unresolved:
544
+ fresh = await QueryBuilder(model_cls).filter(**pk_filter).first()
545
+ if fresh is None:
546
+ raise ValueError(
547
+ f"{model_cls.__name__} {pk_filter}: updated, but the row could not be "
548
+ f"re-read to resolve expression fields {sorted(unresolved)}"
549
+ )
550
+ for f in unresolved:
551
+ setattr(instance, f, getattr(fresh, f, None))
552
+
520
553
  await post_save.send(model_cls, instance=instance, created=False)
521
554
  return instance
@@ -1564,7 +1564,7 @@ class QueryExecutor:
1564
1564
  invalid_fields: list[str] = []
1565
1565
  skipped_fields: list[str] = []
1566
1566
 
1567
- from matrx_orm.core.expressions import Expression, Func, Subquery
1567
+ from matrx_orm.core.expressions import F, Expression, Func, Subquery
1568
1568
 
1569
1569
  for k, v in kwargs.items():
1570
1570
  if k in self.model._fields:
@@ -1600,6 +1600,9 @@ class QueryExecutor:
1600
1600
  expr_sql = value.as_sql(params)
1601
1601
  set_clause.append(f"{field_name} = {expr_sql}")
1602
1602
  param_index = len(params) + 1
1603
+ elif isinstance(value, F):
1604
+ # SET col = other_col (F validates the identifier).
1605
+ set_clause.append(f"{field_name} = {value.field_name}")
1603
1606
  elif isinstance(value, Expression):
1604
1607
  expr_sql = _render_update_expression(value, params)
1605
1608
  set_clause.append(f"{field_name} = {expr_sql}")
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "matrx-orm"
3
- version = "3.1.243"
3
+ version = "3.1.244"
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" }
@@ -0,0 +1,237 @@
1
+ """An instance ``update()`` / ``save()`` RUNS a SQL expression — it never stores one.
2
+
3
+ THE INCIDENT (2026-10-22). ``row.update(metadata=JsonbMerge("metadata", {...}))`` sent
4
+ the JsonbMerge object through ``JSONField.get_db_prep_value``; ``json.dumps`` degraded
5
+ it to its attribute dict and wrote ``{"args": [...], "value": ...}`` over the metadata
6
+ of 1,328 live rows. ``Model.update_where`` compiled the same object to SQL correctly;
7
+ the instance path did not. A text column had the same hole (``str(expression)``).
8
+
9
+ THE GUARD. Instance ``update()``/``save()`` and ``bulk_update`` hand expressions to the
10
+ executor, which compiles them into the SET clause exactly as ``update_where`` does, and
11
+ the instance ends holding the value the database computed. Every other path that would
12
+ serialise an expression (INSERT data, a json value that nests one) raises
13
+ ``ExpressionAsDataError`` before anything is written.
14
+
15
+ Every test does a real round trip and re-reads the row with raw SQL, so a stored
16
+ expression cannot hide behind the in-memory instance. Needs a DISPOSABLE loopback
17
+ Postgres (``ISOLATED_PG17_DSN``); it creates and drops its own tables.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import os
24
+ import uuid
25
+ from ipaddress import ip_address
26
+ from urllib.parse import unquote, urlparse
27
+
28
+ import pytest
29
+
30
+ from matrx_orm import (
31
+ Coalesce,
32
+ ExpressionAsDataError,
33
+ F,
34
+ JsonbMerge,
35
+ Upper,
36
+ )
37
+ from matrx_orm.core.async_db_manager import AsyncDatabaseManager
38
+ from matrx_orm.core.base import Model
39
+ from matrx_orm.core.config import DatabaseProjectConfig, register_database, registry
40
+ from matrx_orm.core.fields import CharField, IntegerField, JSONBField, TextField, UUIDField
41
+ from matrx_orm.operations.update import bulk_update
42
+ from matrx_orm.state import StateManager
43
+
44
+ pytestmark = pytest.mark.asyncio
45
+
46
+ _DB = "expr_guard_db"
47
+ _SUFFIX = uuid.uuid4().hex[:8]
48
+ _TABLE = f"expr_guard_{_SUFFIX}"
49
+
50
+
51
+ class ExprRow(Model):
52
+ id = UUIDField(primary_key=True)
53
+ label = CharField(max_length=100, null=True)
54
+ note = TextField(null=True)
55
+ counter = IntegerField(null=True)
56
+ metadata = JSONBField(null=True)
57
+ _database = _DB
58
+ _table_name = _TABLE
59
+
60
+
61
+ def _loopback_dsn() -> dict[str, str]:
62
+ dsn = os.environ.get("ISOLATED_PG17_DSN")
63
+ if not dsn:
64
+ pytest.skip("ISOLATED_PG17_DSN (a disposable loopback Postgres) is required")
65
+ u = urlparse(dsn)
66
+ host = (u.hostname or "").strip("[]")
67
+ try:
68
+ loopback = ip_address(host).is_loopback
69
+ except ValueError:
70
+ loopback = host == "localhost"
71
+ if not loopback:
72
+ raise RuntimeError(f"REFUSED: {host!r} is not loopback — this test creates tables.")
73
+ return {
74
+ "host": host,
75
+ "port": str(u.port or 5432),
76
+ "user": unquote(u.username or "postgres"),
77
+ "password": unquote(u.password or ""),
78
+ "database_name": (u.path or "/postgres").lstrip("/") or "postgres",
79
+ }
80
+
81
+
82
+ @pytest.fixture
83
+ async def db():
84
+ target = _loopback_dsn()
85
+ # Other suites clear the process-wide state registry; re-register this model.
86
+ StateManager.register_model(ExprRow)
87
+ registry._configs.pop(_DB, None)
88
+ if f"{_DB}_alias" in registry._used_aliases:
89
+ registry._used_aliases.remove(f"{_DB}_alias")
90
+ register_database(
91
+ DatabaseProjectConfig(
92
+ name=_DB,
93
+ alias=f"{_DB}_alias",
94
+ host=target["host"],
95
+ port=target["port"],
96
+ database_name=target["database_name"],
97
+ user=target["user"],
98
+ password=target["password"],
99
+ pool_min=1,
100
+ pool_max=2,
101
+ ssl=False,
102
+ write_queue_enabled=False,
103
+ )
104
+ )
105
+ async with AsyncDatabaseManager.get_connection(_DB) as conn:
106
+ await conn.execute(f"DROP TABLE IF EXISTS public.{_TABLE}")
107
+ await conn.execute(
108
+ f"CREATE TABLE public.{_TABLE} (id uuid PRIMARY KEY, label varchar(100), "
109
+ f"note text, counter integer, metadata jsonb)"
110
+ )
111
+ try:
112
+ yield
113
+ finally:
114
+ async with AsyncDatabaseManager.get_connection(_DB) as conn:
115
+ await conn.execute(f"DROP TABLE IF EXISTS public.{_TABLE}")
116
+ pool = AsyncDatabaseManager._pools.pop(_DB, None)
117
+ if pool is not None:
118
+ await pool.close()
119
+ AsyncDatabaseManager._pool_loops.pop(_DB, None)
120
+ AsyncDatabaseManager._locks.pop(_DB, None)
121
+ AsyncDatabaseManager._growth_locks.pop(_DB, None)
122
+ task = AsyncDatabaseManager._keepalive_tasks.pop(_DB, None)
123
+ if task is not None:
124
+ task.cancel()
125
+ registry._configs.pop(_DB, None)
126
+ if f"{_DB}_alias" in registry._used_aliases:
127
+ registry._used_aliases.remove(f"{_DB}_alias")
128
+
129
+
130
+ async def _raw(row_id: str) -> dict | None:
131
+ async with AsyncDatabaseManager.get_connection(_DB) as conn:
132
+ rec = await conn.fetchrow(
133
+ f"SELECT label, note, counter, metadata::text AS metadata FROM public.{_TABLE} "
134
+ f"WHERE id = $1::uuid",
135
+ row_id,
136
+ )
137
+ if rec is None:
138
+ return None
139
+ out = dict(rec)
140
+ out["metadata"] = json.loads(out["metadata"]) if out["metadata"] is not None else None
141
+ return out
142
+
143
+
144
+ async def _all_stored_text() -> list[str]:
145
+ async with AsyncDatabaseManager.get_connection(_DB) as conn:
146
+ rows = await conn.fetch(
147
+ f"SELECT coalesce(label,'') || '|' || coalesce(note,'') || '|' || "
148
+ f"coalesce(metadata::text,'') AS t FROM public.{_TABLE}"
149
+ )
150
+ return [r["t"] for r in rows]
151
+
152
+
153
+ def _serialised_expression(text: str) -> bool:
154
+ return any(m in text for m in ('"args"', "JsonbMerge", "Expression(", "Func", "Upper("))
155
+
156
+
157
+ async def _new(**values) -> ExprRow:
158
+ return await ExprRow.create(id=str(uuid.uuid4()), **values)
159
+
160
+
161
+ async def test_instance_update_with_jsonb_merge_merges(db) -> None:
162
+ row = await _new(metadata={"views": 100, "keep": True})
163
+ await row.update(metadata=JsonbMerge("metadata", {"placement": {"slot": 3}}))
164
+
165
+ stored = (await _raw(row.id))["metadata"]
166
+ assert stored == {"views": 100, "keep": True, "placement": {"slot": 3}}
167
+ assert row.metadata == stored # the computed value, not the expression
168
+
169
+
170
+ async def test_instance_update_with_jsonb_merge_on_a_null_column(db) -> None:
171
+ row = await _new(metadata=None)
172
+ await row.update(metadata=JsonbMerge("metadata", {"classification": "a"}))
173
+ assert (await _raw(row.id))["metadata"] == {"classification": "a"}
174
+
175
+
176
+ async def test_save_with_a_jsonb_merge_attribute_merges(db) -> None:
177
+ row = await _new(metadata={"views": 1})
178
+ row.metadata = JsonbMerge("metadata", {"extra": 2})
179
+ await row.save()
180
+ stored = (await _raw(row.id))["metadata"]
181
+ assert stored == {"views": 1, "extra": 2}
182
+ assert row.metadata == stored
183
+
184
+
185
+ async def test_increments_run_once_and_a_later_save_does_not_rerun_them(db) -> None:
186
+ row = await _new(counter=None)
187
+ await row.update(counter=Coalesce(F("counter"), 0) + 7) # BinaryExpr
188
+ assert row.counter == 7
189
+ await row.update(counter=F("counter") * 2) # Expression
190
+ assert row.counter == 14
191
+ await row.save() # nothing changed: must not re-apply anything
192
+ assert (await _raw(row.id))["counter"] == 14
193
+
194
+
195
+ async def test_text_functions_and_column_references_run_as_sql(db) -> None:
196
+ row = await _new(label="quiet", note="n")
197
+ await row.update(label=Upper(F("label")))
198
+ assert (await _raw(row.id))["label"] == "QUIET"
199
+ await row.update(note=F("label"))
200
+ assert (await _raw(row.id))["note"] == "QUIET"
201
+ assert row.note == "QUIET"
202
+
203
+
204
+ async def test_bulk_update_runs_expressions(db) -> None:
205
+ row = await _new(metadata={"a": 1})
206
+ row.metadata = JsonbMerge("metadata", {"b": 2})
207
+ assert await bulk_update(ExprRow, [row], ["metadata"]) == 1
208
+ stored = (await _raw(row.id))["metadata"]
209
+ assert stored == {"a": 1, "b": 2}
210
+ assert row.metadata == stored
211
+
212
+
213
+ async def test_insert_with_an_expression_refuses_and_writes_nothing(db) -> None:
214
+ row_id = str(uuid.uuid4())
215
+ with pytest.raises(ExpressionAsDataError):
216
+ await ExprRow.create(id=row_id, metadata=JsonbMerge("metadata", {"a": 1}))
217
+ with pytest.raises(ExpressionAsDataError):
218
+ await ExprRow.create(id=row_id, label=Upper(F("label")))
219
+ assert await _raw(row_id) is None
220
+
221
+
222
+ async def test_an_expression_nested_inside_json_refuses(db) -> None:
223
+ row = await _new(metadata={"a": 1})
224
+ with pytest.raises(ExpressionAsDataError):
225
+ await row.update(metadata={"placement": JsonbMerge("metadata", {"x": 1})})
226
+ assert (await _raw(row.id))["metadata"] == {"a": 1}
227
+
228
+
229
+ async def test_no_stored_value_is_ever_a_serialised_expression(db) -> None:
230
+ """Drive every write shape at one table, then scan what Postgres holds."""
231
+ a = await _new(label="x", note="y", counter=1, metadata={"k": 1})
232
+ await a.update(metadata=JsonbMerge("metadata", {"m": 1}), label=Upper(F("label")))
233
+ a.note = F("label")
234
+ a.counter = F("counter") + 1
235
+ await a.save()
236
+ for text in await _all_stored_text():
237
+ assert not _serialised_expression(text), text
File without changes
File without changes
File without changes
File without changes
File without changes