ferro-orm 0.16.0__tar.gz → 0.16.2__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 (365) hide show
  1. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/CHANGELOG.md +22 -0
  2. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/CONTEXT.md +8 -0
  3. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/Cargo.lock +13 -13
  4. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/Cargo.toml +1 -1
  5. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/PKG-INFO +1 -1
  6. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/crates/ferro-migrate/src/emit.rs +13 -37
  7. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/crates/ferro-migrate/src/lib.rs +2 -0
  8. ferro_orm-0.16.2/crates/ferro-migrate/src/order.rs +56 -0
  9. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/crates/ferro-migrate/src/tests.rs +33 -0
  10. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/concepts/performance.md +2 -2
  11. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/guide/mutations.md +3 -1
  12. ferro_orm-0.16.2/docs/solutions/issues/self-fk-evicts-component-from-create-order.md +59 -0
  13. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/pyproject.toml +1 -1
  14. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/backend.rs +13 -1
  15. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/models.py +6 -0
  16. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/query/builder.py +56 -319
  17. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/query/nodes.py +2 -2
  18. ferro_orm-0.16.2/src/ferro/query/wire.py +518 -0
  19. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/operations.rs +106 -19
  20. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/schema.rs +34 -39
  21. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_auto_migrate.py +42 -0
  22. ferro_orm-0.16.2/tests/test_bulk_create_chunking.py +167 -0
  23. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_include.py +5 -2
  24. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_mutation_pagination_guard.py +4 -2
  25. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_query_builder.py +15 -17
  26. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_query_column_validation.py +5 -2
  27. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_query_immutability.py +7 -3
  28. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_query_joins.py +7 -5
  29. ferro_orm-0.16.2/tests/test_query_wire_vectors.py +288 -0
  30. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_static_contracts.py +21 -8
  31. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  32. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  33. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/.github/PERMISSIONS.md +0 -0
  34. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/.github/PYPI_CHECKLIST.md +0 -0
  35. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/.github/PYPI_SETUP.md +0 -0
  36. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/.github/generated/wheels.generated.yml +0 -0
  37. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/.github/pull_request_template.md +0 -0
  38. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/.github/workflows/ci.yml +0 -0
  39. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/.github/workflows/packaging-smoke.yml +0 -0
  40. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/.github/workflows/publish-docs.yml +0 -0
  41. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/.github/workflows/publish.yml +0 -0
  42. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/.github/workflows/release.yml +0 -0
  43. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/.gitignore +0 -0
  44. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/.pre-commit-config.yaml +0 -0
  45. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/.python-version +0 -0
  46. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/AGENTS.md +0 -0
  47. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/CONTRIBUTING.md +0 -0
  48. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/LICENSE +0 -0
  49. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/README.md +0 -0
  50. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/benchmarks/README.md +0 -0
  51. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/benchmarks/__init__.py +0 -0
  52. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/benchmarks/__main__.py +0 -0
  53. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/benchmarks/backends.py +0 -0
  54. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/benchmarks/baselines/postgres.json +0 -0
  55. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/benchmarks/baselines/sqlite.json +0 -0
  56. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/benchmarks/compare.py +0 -0
  57. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/benchmarks/harness.py +0 -0
  58. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/benchmarks/model.py +0 -0
  59. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/benchmarks/run.py +0 -0
  60. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/crates/ferro-ddl-lowering/Cargo.toml +0 -0
  61. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/crates/ferro-ddl-lowering/src/lib.rs +0 -0
  62. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/crates/ferro-migrate/Cargo.toml +0 -0
  63. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/crates/ferro-schema-ir/Cargo.toml +0 -0
  64. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/crates/ferro-schema-ir/src/lib.rs +0 -0
  65. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/adr/0001-resolved-epoch-registration.md +0 -0
  66. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/adr/0002-unified-column-fact-derivation.md +0 -0
  67. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/adr/0003-path-blind-db-type-validation.md +0 -0
  68. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/adr/0004-jsonb-postgres-only-canonical.md +0 -0
  69. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/adr/0005-jsonb-default-on-postgres.md +0 -0
  70. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/adr/0006-relation-traversal-inner-joins.md +0 -0
  71. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/adr/0007-materialization-plan-complete-instances.md +0 -0
  72. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/adr/0008-populated-relations-include.md +0 -0
  73. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/adr/0009-aggregate-projections-derived-grouping.md +0 -0
  74. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/agents/domain.md +0 -0
  75. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/agents/issue-tracker.md +0 -0
  76. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/agents/triage-labels.md +0 -0
  77. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/architecture-review-2026-07-08.html +0 -0
  78. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-04-29-named-connections-role-routing-requirements.md +0 -0
  79. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-05-08-typed-query-predicates-requirements.md +0 -0
  80. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-05-13-configurable-column-storage-types-requirements.md +0 -0
  81. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-05-14-autogenerate-support-for-db-type-requirements.md +0 -0
  82. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-05-25-annotated-strenum-cold-hydration-requirements.md +0 -0
  83. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-24-ir-p8-119-wire-automigrate-requirements.md +0 -0
  84. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-25-ir-p8-120-parity-gate-requirements.md +0 -0
  85. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-26-ir-p8.5-140-shared-lowering-requirements.md +0 -0
  86. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-26-ir-p8.5-143-db-type-drop-requirements.md +0 -0
  87. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-26-ir-p8.5-144-reconcile-indexes-requirements.md +0 -0
  88. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-28-ir-p8.5-141-single-schema-ir-producer-requirements.md +0 -0
  89. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-29-ir-p8.6-146-dialect-enum-unification-requirements.md +0 -0
  90. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-29-ir-p8.6-153-create-path-ir-requirements.md +0 -0
  91. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-30-ir-p8.6-155-deferred-annotations-requirements.md +0 -0
  92. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-30-ir-p8.6-158-check-renderer-requirements.md +0 -0
  93. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-07-01-ir-p8.6-154-datetime-tz-coarseness-requirements.md +0 -0
  94. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-07-01-ir-p8.6-162-typed-save-bind-requirements.md +0 -0
  95. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-07-01-ir-p8.6-165-blob-introspection-false-positive-requirements.md +0 -0
  96. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-07-02-ff-c1-codec-plan-design.md +0 -0
  97. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/brainstorms/2026-07-06-upgrade-guide-consolidation-requirements.md +0 -0
  98. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/aggregations.py +0 -0
  99. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/aggregations_annotated.py +0 -0
  100. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/multiple_databases.py +0 -0
  101. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/mutations.py +0 -0
  102. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/pagination.py +0 -0
  103. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/partial_selects.py +0 -0
  104. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/partial_selects_annotated.py +0 -0
  105. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/populated_relations.py +0 -0
  106. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/populated_relations_annotated.py +0 -0
  107. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/predicates.py +0 -0
  108. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/predicates_annotated.py +0 -0
  109. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/quickstart.py +0 -0
  110. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/quickstart_annotated.py +0 -0
  111. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/raw_sql.py +0 -0
  112. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/relationships.py +0 -0
  113. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/relationships_annotated.py +0 -0
  114. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/soft_deletes.py +0 -0
  115. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/soft_deletes_annotated.py +0 -0
  116. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/testing_conftest.py +0 -0
  117. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/timestamps.py +0 -0
  118. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/timestamps_annotated.py +0 -0
  119. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/transactions.py +0 -0
  120. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/traversal.py +0 -0
  121. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/examples/traversal_annotated.py +0 -0
  122. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/api/connection.md +0 -0
  123. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/api/exceptions.md +0 -0
  124. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/api/fields.md +0 -0
  125. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/api/migrations.md +0 -0
  126. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/api/model.md +0 -0
  127. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/api/queries.md +0 -0
  128. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/api/raw-sql.md +0 -0
  129. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/api/relationships.md +0 -0
  130. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/api/transactions.md +0 -0
  131. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/changelog.md +0 -0
  132. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/concepts/architecture.md +0 -0
  133. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/concepts/backends.md +0 -0
  134. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/concepts/identity-map.md +0 -0
  135. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/concepts/query-typing.md +0 -0
  136. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/concepts/type-safety.md +0 -0
  137. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/contributing.md +0 -0
  138. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/faq.md +0 -0
  139. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/getting-started/installation.md +0 -0
  140. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/getting-started/next-steps.md +0 -0
  141. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/getting-started/quickstart.md +0 -0
  142. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/guide/aggregations.md +0 -0
  143. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/guide/connections.md +0 -0
  144. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/guide/migrations.md +0 -0
  145. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/guide/models-and-fields.md +0 -0
  146. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/guide/queries.md +0 -0
  147. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/guide/raw-sql.md +0 -0
  148. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/guide/relationships.md +0 -0
  149. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/guide/transactions.md +0 -0
  150. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/howto/migrate-from-sqlalchemy.md +0 -0
  151. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/howto/multiple-databases.md +0 -0
  152. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/howto/pagination.md +0 -0
  153. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/howto/soft-deletes.md +0 -0
  154. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/howto/testing.md +0 -0
  155. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/howto/timestamps.md +0 -0
  156. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/howto/upgrade-guide.md +0 -0
  157. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/index.md +0 -0
  158. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/roadmap.md +0 -0
  159. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/stylesheets/extra.css +0 -0
  160. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/pages/why-ferro.md +0 -0
  161. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-04-24-001-refactor-multi-db-backend-architecture-plan.md +0 -0
  162. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-04-29-001-typed-null-binds-plan.md +0 -0
  163. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-04-29-002-feat-named-connections-plan.md +0 -0
  164. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-05-07-001-refactor-generic-model-connection-plan.md +0 -0
  165. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-05-08-001-feat-typed-query-predicates-plan.md +0 -0
  166. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-05-13-001-feat-configurable-column-storage-types-plan.md +0 -0
  167. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-05-25-001-fix-annotated-strenum-cold-hydration-plan.md +0 -0
  168. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-06-19-001-ir-first-roadmap.md +0 -0
  169. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-06-24-001-feat-ir-p8-119-wire-automigrate-plan.md +0 -0
  170. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-06-25-001-feat-ir-p8-120-parity-gate-plan.md +0 -0
  171. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-06-26-001-feat-ir-p8.5-140-shared-lowering-plan.md +0 -0
  172. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-06-26-002-feat-ir-p8.5-143-db-type-drop-plan.md +0 -0
  173. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-06-26-003-feat-ir-p8.5-144-reconcile-indexes-plan.md +0 -0
  174. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-06-28-001-feat-ir-p8.5-141-single-schema-ir-producer-plan.md +0 -0
  175. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-06-29-001-refactor-ir-p8.6-153-create-path-unification-plan.md +0 -0
  176. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-06-29-002-refactor-ir-p8.6-146-dialect-enum-unification-plan.md +0 -0
  177. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-06-30-001-refactor-ir-p8.6-158-check-renderer-plan.md +0 -0
  178. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-06-30-002-fix-ir-p8.6-155-deferred-annotations-plan.md +0 -0
  179. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-01-001-feat-ir-p8.6-162-typed-save-bind-plan.md +0 -0
  180. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-01-002-fix-ir-p8.6-154-datetime-tz-coarseness-plan.md +0 -0
  181. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-01-003-fix-ir-p8.6-165-blob-introspection-plan.md +0 -0
  182. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-02-001-fable-fixes-roadmap.md +0 -0
  183. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-02-002-feat-ff-a-172-typed-exceptions-plan.md +0 -0
  184. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-02-003-ff-c-native-decode-design.md +0 -0
  185. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-02-004-ff-c-c2-catalog-cache-design.md +0 -0
  186. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-02-005-ff-d-identity-routing-design.md +0 -0
  187. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-02-006-ff-d-identity-routing-plan.md +0 -0
  188. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-03-007-ff-e-registry-identity-design.md +0 -0
  189. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-03-008-ff-e-registry-identity-plan.md +0 -0
  190. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-06-009-ff-f-query-builder-1.0-design.md +0 -0
  191. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-06-010-ff-f-query-builder-1.0-plan.md +0 -0
  192. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-06-011-ff-g-hardening-design.md +0 -0
  193. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-06-012-ff-g-hardening-plan.md +0 -0
  194. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-06-013-ff-g2-operations-dedup-design.md +0 -0
  195. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-06-014-ff-g2-operations-dedup-plan.md +0 -0
  196. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/2026-07-06-015-docs-upgrade-guide-consolidation-plan.md +0 -0
  197. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/ir-first-migration-guide.md +0 -0
  198. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/plans/ir-first-release-checklist.md +0 -0
  199. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/rfc/ir-contracts-v1.md +0 -0
  200. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/README.md +0 -0
  201. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/architecture-patterns/ir-first-lowering-consolidation-audit.md +0 -0
  202. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/architecture-patterns/ir-first-merge-readiness-review.md +0 -0
  203. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/issues/pydantic-slots-missing-after-ferro-hydration.md +0 -0
  204. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/issues/python-3.14-deferred-annotation-typeerror-swallow.md +0 -0
  205. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/issues/sa-pk-column-nullable-divergence.md +0 -0
  206. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/issues/sa-vs-rust-unique-constraint-shape.md +0 -0
  207. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/issues/sqlite-integer-decimal-hydrates-as-none.md +0 -0
  208. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/issues/sqlite-null-hydrates-as-int-zero.md +0 -0
  209. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/issues/typed-where-null-panics-is-null.md +0 -0
  210. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/patterns/configurable-column-storage-types.md +0 -0
  211. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/patterns/cross-emitter-ddl-parity.md +0 -0
  212. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/patterns/ddl-on-live-engine.md +0 -0
  213. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/patterns/derived-type-and-naming-decision-table.md +0 -0
  214. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/patterns/foreign-key-index.md +0 -0
  215. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/patterns/index-unique-redundancy.md +0 -0
  216. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/patterns/ir-invariants.md +0 -0
  217. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/patterns/persistence-state.md +0 -0
  218. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/patterns/shadow-fk-columns.md +0 -0
  219. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/patterns/sqlite-alembic-reconnect-hydration-tests.md +0 -0
  220. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/docs/solutions/patterns/typed-null-binds.md +0 -0
  221. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/justfile +0 -0
  222. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/scripts/demo_queries.py +0 -0
  223. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/scripts/demo_queries_pre_v012.py +0 -0
  224. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/codec.rs +0 -0
  225. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/codec_plan.rs +0 -0
  226. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/connection.rs +0 -0
  227. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/errors.rs +0 -0
  228. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/__init__.py +0 -0
  229. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/_annotation_utils.py +0 -0
  230. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/_bind_payload.py +0 -0
  231. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/_core.pyi +0 -0
  232. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/_deprecations.py +0 -0
  233. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/_shadow_fk_types.py +0 -0
  234. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/base.py +0 -0
  235. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/columns.py +0 -0
  236. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/composite_indexes.py +0 -0
  237. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/composite_uniques.py +0 -0
  238. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/exceptions.py +0 -0
  239. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/fields.py +0 -0
  240. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/ir/__init__.py +0 -0
  241. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/ir/compiler.py +0 -0
  242. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/metaclass.py +0 -0
  243. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/migrations/__init__.py +0 -0
  244. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/migrations/alembic.py +0 -0
  245. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/py.typed +0 -0
  246. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/query/__init__.py +0 -0
  247. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/query/rows.py +0 -0
  248. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/raw.py +0 -0
  249. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/relations/__init__.py +0 -0
  250. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/relations/descriptors.py +0 -0
  251. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/session.py +0 -0
  252. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/ferro/state.py +0 -0
  253. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/hydration.rs +0 -0
  254. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/introspect.rs +0 -0
  255. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/lib.rs +0 -0
  256. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/migrate.rs +0 -0
  257. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/naming_ffi.rs +0 -0
  258. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/query.rs +0 -0
  259. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/schema_bind.rs +0 -0
  260. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/src/state.rs +0 -0
  261. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/__init__.py +0 -0
  262. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/conftest.py +0 -0
  263. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/db_backends.py +0 -0
  264. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/README.md +0 -0
  265. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/codec_registry_core_v1.json +0 -0
  266. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/query_transaction_aggregate_v5.json +0 -0
  267. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/query_transaction_global_aggregate_v5.json +0 -0
  268. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/query_transaction_include_v5.json +0 -0
  269. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/query_transaction_left_join_v5.json +0 -0
  270. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/query_transaction_record_v5.json +0 -0
  271. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/query_transaction_traversal_v5.json +0 -0
  272. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/query_transaction_traversed_record_v5.json +0 -0
  273. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/query_user_compound_v5.json +0 -0
  274. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/schema_invoice_baseline_v1.json +0 -0
  275. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/schema_phase1_fixture_models_v1.json +0 -0
  276. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/schema_raw_str_pk_autoincrement_v1.json +0 -0
  277. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/static_fixtures/bad_includes.py +0 -0
  278. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/static_fixtures/bad_predicates.py +0 -0
  279. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/static_fixtures/bad_projections.py +0 -0
  280. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/static_fixtures/good_includes.py +0 -0
  281. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/static_fixtures/good_predicates.py +0 -0
  282. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/static_fixtures/good_projections.py +0 -0
  283. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_aggregation.py +0 -0
  284. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_alembic_autogenerate.py +0 -0
  285. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_alembic_bridge.py +0 -0
  286. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_alembic_db_type.py +0 -0
  287. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_alembic_nullability.py +0 -0
  288. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_alembic_type_mapping.py +0 -0
  289. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_annotation_utils.py +0 -0
  290. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_bulk_install.py +0 -0
  291. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_bulk_update.py +0 -0
  292. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_catalog_cache.py +0 -0
  293. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_codec_plan.py +0 -0
  294. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_column_specs.py +0 -0
  295. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_composite_index.py +0 -0
  296. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_composite_unique.py +0 -0
  297. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_connection.py +0 -0
  298. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_connection_redaction.py +0 -0
  299. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_constraints.py +0 -0
  300. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_cross_emitter_parity.py +0 -0
  301. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_crud.py +0 -0
  302. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_db_backends.py +0 -0
  303. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_db_type_cross_emitter_parity.py +0 -0
  304. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_db_type_integration.py +0 -0
  305. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_db_type_typing.py +0 -0
  306. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_db_type_validation.py +0 -0
  307. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_deletion.py +0 -0
  308. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_deprecations.py +0 -0
  309. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_docs_examples.py +0 -0
  310. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_documentation_features.py +0 -0
  311. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_enum_cold_hydration.py +0 -0
  312. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_exception_mapping.py +0 -0
  313. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_exceptions.py +0 -0
  314. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_field_wrapper.py +0 -0
  315. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_fk_target_pk.py +0 -0
  316. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_generation_counter_dirty_tracking.py +0 -0
  317. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_global_aggregates.py +0 -0
  318. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_grouped_aggregates.py +0 -0
  319. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_helpers.py +0 -0
  320. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_hydration.py +0 -0
  321. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_hydration_equivalence.py +0 -0
  322. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_identity_memory.py +0 -0
  323. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_identity_refresh.py +0 -0
  324. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_identity_scoped_invalidation.py +0 -0
  325. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_identity_weakref.py +0 -0
  326. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_import_budget.py +0 -0
  327. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_ir_vectors_contract.py +0 -0
  328. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_metaclass_internals.py +0 -0
  329. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_metadata.py +0 -0
  330. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_migrate_plan.py +0 -0
  331. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_model_identity.py +0 -0
  332. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_models.py +0 -0
  333. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_named_connections_integration.py +0 -0
  334. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_naming_single_source.py +0 -0
  335. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_one_to_one.py +0 -0
  336. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_operation_seam_sync.py +0 -0
  337. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_partial_selects.py +0 -0
  338. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_provisional_import.py +0 -0
  339. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_query_typing.py +0 -0
  340. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_raw_sql.py +0 -0
  341. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_refresh.py +0 -0
  342. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_registry_entrypoints.py +0 -0
  343. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_relation_specs.py +0 -0
  344. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_relationship_engine.py +0 -0
  345. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_relationship_resolution_errors.py +0 -0
  346. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_route_single_site.py +0 -0
  347. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_routing_errors.py +0 -0
  348. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_save_pk_edges.py +0 -0
  349. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_save_semantics.py +0 -0
  350. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_schema.py +0 -0
  351. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_schema_constraints.py +0 -0
  352. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_schema_db_type_metadata.py +0 -0
  353. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_schema_enum_annotations.py +0 -0
  354. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_session.py +0 -0
  355. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_shadow_fk_types.py +0 -0
  356. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_sqlite_alembic_reconnect_hydration.py +0 -0
  357. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_string_search.py +0 -0
  358. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_structural_types.py +0 -0
  359. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_temporal_types.py +0 -0
  360. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_transactions.py +0 -0
  361. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_traversed_projection.py +0 -0
  362. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_typed_null_binds.py +0 -0
  363. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/tests/test_typed_save_bind.py +0 -0
  364. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/uv.lock +0 -0
  365. {ferro_orm-0.16.0 → ferro_orm-0.16.2}/zensical.toml +0 -0
@@ -1,6 +1,28 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v0.16.2 (2026-07-15)
5
+
6
+ ### Bug Fixes
7
+
8
+ - Self-referential FKs no longer evict their component from CREATE TABLE order
9
+ ([#303](https://github.com/syn54x/ferro-orm/pull/303),
10
+ [`d1a55f6`](https://github.com/syn54x/ferro-orm/commit/d1a55f6ae241101d98ee659e6651fa33abaf4ec5))
11
+
12
+ ### Refactoring
13
+
14
+ - Compile QueryIR payloads at a single wire choke point
15
+ ([`851b3f4`](https://github.com/syn54x/ferro-orm/commit/851b3f4e1f077d70481ebca643ff0ffff81645d8))
16
+
17
+
18
+ ## v0.16.1 (2026-07-13)
19
+
20
+ ### Bug Fixes
21
+
22
+ - Chunk bulk_create under backend bind-parameter limits
23
+ ([`38da62d`](https://github.com/syn54x/ferro-orm/commit/38da62d9ab9fab6b36a9eae0722c1d741b976d15))
24
+
25
+
4
26
  ## v0.16.0 (2026-07-13)
5
27
 
6
28
  ### Chores
@@ -80,6 +80,14 @@ _Avoid_: Eager-loaded field, select_related, prefetched attribute, joined attrib
80
80
  A query's declaration of what its result columns become: complete root instances (every query today), a projected record of named fields, or — in the future — a populated instance graph. Every query carries exactly one plan; the plan travels with the query rather than being inferred from its column list.
81
81
  _Avoid_: Select list, projection spec, hydration mode flag
82
82
 
83
+ **QueryIR payload**:
84
+ The single typed wire artifact a query ships to the Rust runtime — model identity, predicates, ordering, paging, joins, and exactly one materialization plan, inside a versioned envelope. Compiled only by `compile_query`; no other code assembles query wire shape.
85
+ _Avoid_: Query dict, query def, payload dict
86
+
87
+ **Golden vector**:
88
+ A hand-authored JSON fixture pinning one wire shape — the independent authority both the Python emitter and the Rust decoder assert against, never regenerated from either side's output. Updating one by hand is the contract-review moment for a wire change.
89
+ _Avoid_: Snapshot, fixture dump, example payload
90
+
83
91
  **Aggregate projection**:
84
92
  A projection containing at least one aggregate field. Each group collapses to exactly one projected record: every non-aggregate field is a group key, so grouping is derived from the projection and never declared separately. With no non-aggregate fields, the whole result collapses to a single record. Grouping collapses rows — bucketing complete instances by a key ("partitioning") is a different, client-side operation and is not grouping.
85
93
  _Avoid_: Group-by query, summary query, rollup, partition
@@ -327,7 +327,7 @@ dependencies = [
327
327
 
328
328
  [[package]]
329
329
  name = "ferro"
330
- version = "0.16.0"
330
+ version = "0.16.2"
331
331
  dependencies = [
332
332
  "chrono",
333
333
  "dashmap",
@@ -890,9 +890,9 @@ dependencies = [
890
890
 
891
891
  [[package]]
892
892
  name = "mio"
893
- version = "1.2.1"
893
+ version = "1.2.2"
894
894
  source = "registry+https://github.com/rust-lang/crates.io-index"
895
- checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
895
+ checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
896
896
  dependencies = [
897
897
  "libc",
898
898
  "wasi",
@@ -1249,9 +1249,9 @@ dependencies = [
1249
1249
 
1250
1250
  [[package]]
1251
1251
  name = "rustls"
1252
- version = "0.23.41"
1252
+ version = "0.23.42"
1253
1253
  source = "registry+https://github.com/rust-lang/crates.io-index"
1254
- checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f"
1254
+ checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138"
1255
1255
  dependencies = [
1256
1256
  "once_cell",
1257
1257
  "ring",
@@ -1444,9 +1444,9 @@ dependencies = [
1444
1444
 
1445
1445
  [[package]]
1446
1446
  name = "socket2"
1447
- version = "0.6.4"
1447
+ version = "0.6.5"
1448
1448
  source = "registry+https://github.com/rust-lang/crates.io-index"
1449
- checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
1449
+ checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
1450
1450
  dependencies = [
1451
1451
  "libc",
1452
1452
  "windows-sys 0.61.2",
@@ -1454,9 +1454,9 @@ dependencies = [
1454
1454
 
1455
1455
  [[package]]
1456
1456
  name = "spin"
1457
- version = "0.9.8"
1457
+ version = "0.9.9"
1458
1458
  source = "registry+https://github.com/rust-lang/crates.io-index"
1459
- checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
1459
+ checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
1460
1460
  dependencies = [
1461
1461
  "lock_api",
1462
1462
  ]
@@ -1698,9 +1698,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
1698
1698
 
1699
1699
  [[package]]
1700
1700
  name = "syn"
1701
- version = "2.0.118"
1701
+ version = "2.0.119"
1702
1702
  source = "registry+https://github.com/rust-lang/crates.io-index"
1703
- checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
1703
+ checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
1704
1704
  dependencies = [
1705
1705
  "proc-macro2",
1706
1706
  "quote",
@@ -2329,6 +2329,6 @@ dependencies = [
2329
2329
 
2330
2330
  [[package]]
2331
2331
  name = "zmij"
2332
- version = "1.0.22"
2332
+ version = "1.0.23"
2333
2333
  source = "registry+https://github.com/rust-lang/crates.io-index"
2334
- checksum = "bd2f034a4bebf216c9e4b7083603e024cf930873fd67830cfb083c9fa33129d9"
2334
+ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
@@ -3,7 +3,7 @@ members = ["crates/ferro-schema-ir", "crates/ferro-ddl-lowering", "crates/ferro-
3
3
 
4
4
  [package]
5
5
  name = "ferro"
6
- version = "0.16.0"
6
+ version = "0.16.2"
7
7
  edition = "2024"
8
8
  readme = "README.md"
9
9
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ferro-orm
3
- Version: 0.16.0
3
+ Version: 0.16.2
4
4
  Requires-Dist: pydantic>=2.0
5
5
  Requires-Dist: alembic>=1.18.1 ; extra == 'alembic'
6
6
  Requires-Dist: sqlalchemy>=2.0.46 ; extra == 'alembic'
@@ -255,47 +255,23 @@ fn post_create_artifacts(
255
255
 
256
256
  /// Order `AddTable` models so each table's FK targets are created before it.
257
257
  ///
258
- /// A FK dependency on a table that is *not* part of this add set (already
259
- /// exists in the live DB) does not constrain ordering. Cycles fall through in
260
- /// arbitrary order — the inline FKs are anonymous, so a cyclic create still
261
- /// runs (SQLite is lenient; Postgres `CREATE TABLE` with a forward inline FK
262
- /// reference is the pre-existing behavior preserved here).
258
+ /// Delegates to [`crate::order_by_dependencies`] for the dependency
259
+ /// semantics: a self-referential FK and a FK to a table outside this add set
260
+ /// do not constrain ordering, and genuine cross-table cycles fall through in
261
+ /// input order (SQLite tolerates the forward references; Postgres rejects
262
+ /// them — #302 follow-up).
263
263
  pub fn order_models_for_create<'a>(models: &[&'a SchemaModel]) -> Vec<&'a SchemaModel> {
264
- let mut remaining: Vec<&SchemaModel> = models.to_vec();
265
- let mut ordered = Vec::new();
266
- let mut created = std::collections::HashSet::new();
267
-
268
- while !remaining.is_empty() {
269
- let available: std::collections::HashSet<String> = remaining
270
- .iter()
271
- .map(|m| m.table_name.clone())
272
- .collect();
273
- let mut progress = false;
274
- let mut index = 0;
275
- while index < remaining.len() {
276
- let deps: Vec<String> = remaining[index]
264
+ crate::order_by_dependencies(
265
+ models.to_vec(),
266
+ |model| model.table_name.clone(),
267
+ |model| {
268
+ model
277
269
  .foreign_keys
278
270
  .iter()
279
271
  .map(|fk| fk.to_table.clone())
280
- .collect();
281
- if deps
282
- .iter()
283
- .all(|dep| created.contains(dep) || !available.contains(dep))
284
- {
285
- let model = remaining.remove(index);
286
- created.insert(model.table_name.clone());
287
- ordered.push(model);
288
- progress = true;
289
- } else {
290
- index += 1;
291
- }
292
- }
293
- if !progress {
294
- ordered.append(&mut remaining);
295
- break;
296
- }
297
- }
298
- ordered
272
+ .collect()
273
+ },
274
+ )
299
275
  }
300
276
 
301
277
  fn emit_add_table_passes(
@@ -4,12 +4,14 @@
4
4
  //! [`emit_sql_with_ir`] lowers structural ops to executable backend-specific DDL.
5
5
 
6
6
  mod emit;
7
+ mod order;
7
8
 
8
9
  use ferro_ddl_lowering::schema_columns_storage_drift;
9
10
  use ferro_schema_ir::{IrEnvelope, SchemaIrPayload, SchemaModel};
10
11
  use std::collections::{BTreeMap, BTreeSet};
11
12
 
12
13
  pub use emit::{emit_sql_with_ir, order_models_for_create, render_create_table, CreateTableEmission};
14
+ pub use order::order_by_dependencies;
13
15
  pub use ferro_ddl_lowering::Dialect;
14
16
 
15
17
  /// Executable SQL plus non-fatal warnings from [`emit_sql_with_ir`].
@@ -0,0 +1,56 @@
1
+ //! Dependency ordering shared by every path that emits or reconciles tables.
2
+
3
+ use std::collections::HashSet;
4
+
5
+ /// Order `items` so every FK dependency of an item is emitted before it.
6
+ ///
7
+ /// This is the single dependency-order primitive: both the CREATE TABLE
8
+ /// emitter ([`crate::order_models_for_create`]) and the runtime's migrate
9
+ /// reconcile loop funnel through it, so the semantics live in one place:
10
+ ///
11
+ /// - A dependency on the item's **own** table (a self-referential FK) is not
12
+ /// an ordering constraint — the inline `REFERENCES` resolves against the
13
+ /// table its own `CREATE TABLE` statement is creating (#302).
14
+ /// - A dependency on a table **outside** `items` (already live in the DB)
15
+ /// does not constrain ordering.
16
+ /// - Genuine cross-table cycles cannot be dependency-ordered; the remaining
17
+ /// items are appended in input order. SQLite tolerates the resulting
18
+ /// forward references; Postgres rejects them at CREATE time (#302
19
+ /// follow-up: defer those FKs to post-create `ALTER TABLE ADD CONSTRAINT`).
20
+ ///
21
+ /// Ordering is deterministic: input order is preserved except where a
22
+ /// dependency forces an item later.
23
+ pub fn order_by_dependencies<T>(
24
+ items: Vec<T>,
25
+ table_of: impl Fn(&T) -> String,
26
+ deps_of: impl Fn(&T) -> Vec<String>,
27
+ ) -> Vec<T> {
28
+ let mut remaining = items;
29
+ let mut ordered = Vec::with_capacity(remaining.len());
30
+ let mut created: HashSet<String> = HashSet::new();
31
+
32
+ while !remaining.is_empty() {
33
+ let available: HashSet<String> = remaining.iter().map(&table_of).collect();
34
+ let mut progress = false;
35
+ let mut index = 0;
36
+ while index < remaining.len() {
37
+ let table = table_of(&remaining[index]);
38
+ let satisfied = deps_of(&remaining[index])
39
+ .iter()
40
+ .all(|dep| dep == &table || created.contains(dep) || !available.contains(dep));
41
+ if satisfied {
42
+ let item = remaining.remove(index);
43
+ created.insert(table);
44
+ ordered.push(item);
45
+ progress = true;
46
+ } else {
47
+ index += 1;
48
+ }
49
+ }
50
+ if !progress {
51
+ ordered.append(&mut remaining);
52
+ break;
53
+ }
54
+ }
55
+ ordered
56
+ }
@@ -1459,3 +1459,36 @@ fn emit_sql_with_ir_add_column_db_check_postgres_quoted_and_sqlite_warns() {
1459
1459
  lite.warnings
1460
1460
  );
1461
1461
  }
1462
+
1463
+ #[test]
1464
+ fn order_models_for_create_self_fk_is_not_an_ordering_constraint() {
1465
+ // #302: `znode` carries a self-referential FK; `areferrer` arrives first
1466
+ // (mirroring the alphabetical incoming order) and requires `znode`. The
1467
+ // self-loop is satisfied by the table's own CREATE, so it must not evict
1468
+ // the component from the dependency order.
1469
+ let znode = SchemaModel {
1470
+ foreign_keys: vec![SchemaForeignKey {
1471
+ column: "parent_id".to_string(),
1472
+ to_table: "znode".to_string(),
1473
+ to_column: "id".to_string(),
1474
+ on_delete: None,
1475
+ name: None,
1476
+ }],
1477
+ ..schema_model("znode", vec![col("id", "uuid", false)])
1478
+ };
1479
+ let areferrer = SchemaModel {
1480
+ foreign_keys: vec![SchemaForeignKey {
1481
+ column: "node_id".to_string(),
1482
+ to_table: "znode".to_string(),
1483
+ to_column: "id".to_string(),
1484
+ on_delete: None,
1485
+ name: None,
1486
+ }],
1487
+ ..schema_model("areferrer", vec![col("id", "uuid", false)])
1488
+ };
1489
+
1490
+ let models = vec![&areferrer, &znode];
1491
+ let ordered = order_models_for_create(&models);
1492
+ let tables: Vec<&str> = ordered.iter().map(|m| m.table_name.as_str()).collect();
1493
+ assert_eq!(tables, vec!["znode", "areferrer"]);
1494
+ }
@@ -6,7 +6,7 @@ Ferro moves SQL generation, parameter binding, and row hydration out of Python a
6
6
 
7
7
  The Rust core helps most where a traditional ORM spends significant CPU time in Python:
8
8
 
9
- **Bulk inserts.** `bulk_create` serializes and binds an entire batch in Rust and writes it as a single statement. The per-row Python overhead — building parameter lists, driver round-trips, object bookkeeping — largely disappears:
9
+ **Bulk inserts.** `bulk_create` serializes and binds an entire batch in Rust and writes it in as few statements as the backend's bind-parameter limit allows. The per-row Python overhead — building parameter lists, driver round-trips, object bookkeeping — largely disappears:
10
10
 
11
11
  ```python
12
12
  users = [
@@ -43,7 +43,7 @@ for i in range(1000):
43
43
  await User.bulk_create([User(username=f"user_{i}") for i in range(1000)])
44
44
  ```
45
45
 
46
- Batches in the low thousands (roughly 1,000–5,000 rows) are a good unit of work — large enough to amortize overhead, small enough to keep statements and memory reasonable. Note that `bulk_create` skips the [identity map](identity-map.md) by design.
46
+ Batch size is unbounded — Ferro splits large batches under the backend's bind-parameter limit automatically and keeps the whole call all-or-nothing — so size batches by what your application can hold in memory, not by statement limits. Note that `bulk_create` skips the [identity map](identity-map.md) by design.
47
47
 
48
48
  **Use batch update/delete instead of instance loops.** Push the work into one SQL statement:
49
49
 
@@ -14,12 +14,14 @@ Ferro's write surface is three verbs with three distinct intents — none of the
14
14
 
15
15
  ### create
16
16
 
17
- `Model.create(**fields)` validates, inserts, and returns the persisted instance in one call. For inserting many rows, `Model.bulk_create(instances)` batches them into a single statement and returns the inserted count:
17
+ `Model.create(**fields)` validates, inserts, and returns the persisted instance in one call. For inserting many rows, `Model.bulk_create(instances)` writes the whole batch in bulk and returns the inserted count:
18
18
 
19
19
  ```python
20
20
  --8<-- "docs/examples/quickstart.py:create"
21
21
  ```
22
22
 
23
+ Batch size is unbounded. Both backends cap how many parameters one statement may bind (SQLite 32,766; PostgreSQL 65,535), so Ferro splits a large batch across as many INSERT statements as the active backend requires — an implementation detail you never see. Atomicity is preserved either way: inside a [`transaction()`](transactions.md) block that transaction is the boundary, and a bare `bulk_create` call is all-or-nothing — if any row fails (say, a unique violation late in the batch), no rows from the batch are inserted.
24
+
23
25
  `create()` is a plain INSERT — it never updates an existing row. A duplicate primary key or unique value raises [`UniqueViolationError`](../api/exceptions.md), and the existing row is left untouched:
24
26
 
25
27
  ```python
@@ -0,0 +1,59 @@
1
+ ---
2
+ title: Self-referential FK evicted its whole component from CREATE TABLE order
3
+ type: issue
4
+ tags: [rust, schema, migrations, gotcha, postgres]
5
+ related_files:
6
+ - crates/ferro-migrate/src/order.rs
7
+ - crates/ferro-migrate/src/emit.rs
8
+ - src/schema.rs
9
+ related_issues: [302]
10
+ captured: 2026-07-15
11
+ ---
12
+
13
+ # Self-referential FK evicted its whole component from CREATE TABLE order
14
+
15
+ **Problem:** On a fresh Postgres schema, `auto_migrate` created a referrer
16
+ table before its FK target whenever the target carried a self-referential FK —
17
+ the inline `REFERENCES` failed with `relation "..." does not exist`.
18
+
19
+ **Takeaway:** In a dependency sort, an edge to your own table is already
20
+ satisfied by your own `CREATE TABLE`; treating it as a pending dependency makes
21
+ the table permanently ineligible, stalls everything downstream of it, and drops
22
+ the whole component into the cycle fallback (input/alphabetical order).
23
+
24
+ ## What happened
25
+
26
+ Both ordering loops (`order_models_for_create` in `ferro-migrate` and
27
+ `order_models_for_migration` in `src/schema.rs`) only scheduled a table once
28
+ every `fk.to_table` was in the `created` set. A self-FK's target can never be
29
+ in `created` before the table itself, so the self-FK table never became
30
+ eligible, its dependents stalled behind it, and the no-progress fallback
31
+ appended the remainder in incoming order. Any referrer sorting before its
32
+ target then failed on Postgres. Existing schemas survived by alphabetical
33
+ luck.
34
+
35
+ Two things let the bug survive review:
36
+
37
+ 1. The ordering loop existed in **two hand-rolled copies** with the same flaw.
38
+ Both now delegate to one primitive:
39
+ `ferro_migrate::order_by_dependencies` (`crates/ferro-migrate/src/order.rs`),
40
+ where the self-edge rule lives exactly once.
41
+ 2. A doc comment claimed cycles were harmless because "Postgres `CREATE TABLE`
42
+ with a forward inline FK reference is the pre-existing behavior" — false.
43
+ Postgres rejects forward references at CREATE time; only SQLite tolerates
44
+ them. Don't trust ordering-doesn't-matter claims without a Postgres test.
45
+
46
+ Genuine cross-table cycles (A→B→A) still fall through in input order and fail
47
+ on Postgres — a real fix needs post-create `ALTER TABLE ADD CONSTRAINT`
48
+ deferral (follow-up tracked from #302).
49
+
50
+ ## How to recognize
51
+
52
+ - `relation "X" does not exist` during `auto_migrate` on a fresh Postgres
53
+ schema, where X *is* among the registered models.
54
+ - The failure appears/disappears when a self-FK is added/removed anywhere in
55
+ the dependency component, or when table names are renamed across the
56
+ alphabetical boundary.
57
+ - SQLite passes while Postgres fails: SQLite accepts forward FK references, so
58
+ ordering bugs are invisible there — regression tests for CREATE order must
59
+ run on the Postgres matrix.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ferro-orm"
3
- version = "0.16.0"
3
+ version = "0.16.2"
4
4
  description = "A high-performance, Rust-backed ORM for Python."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -415,6 +415,19 @@ impl EngineHandle {
415
415
  self.backend
416
416
  }
417
417
 
418
+ /// Maximum bind parameters one statement may carry on this backend.
419
+ ///
420
+ /// SQLite: `SQLITE_MAX_VARIABLE_NUMBER`, 32,766 by default since 3.32.
421
+ /// Postgres: the wire protocol's `Bind` message carries an `int16`
422
+ /// parameter count, so 65,535 is a hard protocol ceiling.
423
+ #[must_use]
424
+ pub fn max_bind_params(&self) -> usize {
425
+ match self.backend {
426
+ Dialect::Sqlite => 32_766,
427
+ Dialect::Postgres => 65_535,
428
+ }
429
+ }
430
+
418
431
  /// Record one catalog-introspection round-trip (called at the single
419
432
  /// choke point that executes catalog SQL).
420
433
  pub fn record_catalog_query(&self) {
@@ -611,7 +624,6 @@ impl EngineHandle {
611
624
  }
612
625
  }
613
626
 
614
- #[allow(dead_code)]
615
627
  pub async fn begin_transaction_connection(&self) -> Result<EngineConnection, sqlx::Error> {
616
628
  match &self.pool_snapshot() {
617
629
  BackendPool::Sqlite(pool) => {
@@ -639,6 +639,12 @@ class Model(BaseModel, metaclass=ModelMetaclass):
639
639
  ) -> int:
640
640
  """Persist multiple instances in a single bulk operation
641
641
 
642
+ Batch size is unbounded: batches larger than the backend's
643
+ bind-parameter limit allows in one statement are split internally.
644
+ The call is atomic either way — inside an ambient ``transaction()``
645
+ that transaction is the atomicity boundary; a bare call is
646
+ all-or-nothing (a failure on any row inserts none).
647
+
642
648
  Args:
643
649
  instances: Model instances to persist.
644
650