ferro-orm 0.16.1__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.
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/CHANGELOG.md +14 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/CONTEXT.md +8 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/Cargo.lock +13 -13
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/Cargo.toml +1 -1
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/PKG-INFO +1 -1
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/crates/ferro-migrate/src/emit.rs +13 -37
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/crates/ferro-migrate/src/lib.rs +2 -0
- ferro_orm-0.16.2/crates/ferro-migrate/src/order.rs +56 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/crates/ferro-migrate/src/tests.rs +33 -0
- ferro_orm-0.16.2/docs/solutions/issues/self-fk-evicts-component-from-create-order.md +59 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/pyproject.toml +1 -1
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/query/builder.py +56 -319
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/query/nodes.py +2 -2
- ferro_orm-0.16.2/src/ferro/query/wire.py +518 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/schema.rs +34 -39
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_auto_migrate.py +42 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_include.py +5 -2
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_mutation_pagination_guard.py +4 -2
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_query_builder.py +15 -17
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_query_column_validation.py +5 -2
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_query_immutability.py +7 -3
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_query_joins.py +7 -5
- ferro_orm-0.16.2/tests/test_query_wire_vectors.py +288 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_static_contracts.py +21 -8
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/.github/PERMISSIONS.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/.github/PYPI_CHECKLIST.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/.github/PYPI_SETUP.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/.github/generated/wheels.generated.yml +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/.github/pull_request_template.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/.github/workflows/ci.yml +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/.github/workflows/packaging-smoke.yml +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/.github/workflows/publish-docs.yml +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/.github/workflows/publish.yml +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/.github/workflows/release.yml +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/.gitignore +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/.pre-commit-config.yaml +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/.python-version +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/AGENTS.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/CONTRIBUTING.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/LICENSE +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/README.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/benchmarks/README.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/benchmarks/__init__.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/benchmarks/__main__.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/benchmarks/backends.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/benchmarks/baselines/postgres.json +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/benchmarks/baselines/sqlite.json +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/benchmarks/compare.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/benchmarks/harness.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/benchmarks/model.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/benchmarks/run.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/crates/ferro-ddl-lowering/Cargo.toml +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/crates/ferro-ddl-lowering/src/lib.rs +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/crates/ferro-migrate/Cargo.toml +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/crates/ferro-schema-ir/Cargo.toml +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/crates/ferro-schema-ir/src/lib.rs +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/adr/0001-resolved-epoch-registration.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/adr/0002-unified-column-fact-derivation.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/adr/0003-path-blind-db-type-validation.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/adr/0004-jsonb-postgres-only-canonical.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/adr/0005-jsonb-default-on-postgres.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/adr/0006-relation-traversal-inner-joins.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/adr/0007-materialization-plan-complete-instances.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/adr/0008-populated-relations-include.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/adr/0009-aggregate-projections-derived-grouping.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/agents/domain.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/agents/issue-tracker.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/agents/triage-labels.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/architecture-review-2026-07-08.html +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-04-29-named-connections-role-routing-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-05-08-typed-query-predicates-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-05-13-configurable-column-storage-types-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-05-14-autogenerate-support-for-db-type-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-05-25-annotated-strenum-cold-hydration-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-24-ir-p8-119-wire-automigrate-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-25-ir-p8-120-parity-gate-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-26-ir-p8.5-140-shared-lowering-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-26-ir-p8.5-143-db-type-drop-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-26-ir-p8.5-144-reconcile-indexes-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-28-ir-p8.5-141-single-schema-ir-producer-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-29-ir-p8.6-146-dialect-enum-unification-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-29-ir-p8.6-153-create-path-ir-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-30-ir-p8.6-155-deferred-annotations-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-06-30-ir-p8.6-158-check-renderer-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-07-01-ir-p8.6-154-datetime-tz-coarseness-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-07-01-ir-p8.6-162-typed-save-bind-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-07-01-ir-p8.6-165-blob-introspection-false-positive-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-07-02-ff-c1-codec-plan-design.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/brainstorms/2026-07-06-upgrade-guide-consolidation-requirements.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/aggregations.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/aggregations_annotated.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/multiple_databases.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/mutations.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/pagination.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/partial_selects.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/partial_selects_annotated.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/populated_relations.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/populated_relations_annotated.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/predicates.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/predicates_annotated.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/quickstart.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/quickstart_annotated.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/raw_sql.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/relationships.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/relationships_annotated.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/soft_deletes.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/soft_deletes_annotated.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/testing_conftest.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/timestamps.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/timestamps_annotated.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/transactions.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/traversal.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/examples/traversal_annotated.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/api/connection.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/api/exceptions.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/api/fields.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/api/migrations.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/api/model.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/api/queries.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/api/raw-sql.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/api/relationships.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/api/transactions.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/changelog.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/concepts/architecture.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/concepts/backends.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/concepts/identity-map.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/concepts/performance.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/concepts/query-typing.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/concepts/type-safety.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/contributing.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/faq.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/getting-started/installation.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/getting-started/next-steps.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/getting-started/quickstart.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/guide/aggregations.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/guide/connections.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/guide/migrations.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/guide/models-and-fields.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/guide/mutations.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/guide/queries.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/guide/raw-sql.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/guide/relationships.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/guide/transactions.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/howto/migrate-from-sqlalchemy.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/howto/multiple-databases.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/howto/pagination.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/howto/soft-deletes.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/howto/testing.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/howto/timestamps.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/howto/upgrade-guide.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/index.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/roadmap.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/stylesheets/extra.css +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/pages/why-ferro.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-04-24-001-refactor-multi-db-backend-architecture-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-04-29-001-typed-null-binds-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-04-29-002-feat-named-connections-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-05-07-001-refactor-generic-model-connection-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-05-08-001-feat-typed-query-predicates-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-05-13-001-feat-configurable-column-storage-types-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-05-25-001-fix-annotated-strenum-cold-hydration-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-06-19-001-ir-first-roadmap.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-06-24-001-feat-ir-p8-119-wire-automigrate-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-06-25-001-feat-ir-p8-120-parity-gate-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-06-26-001-feat-ir-p8.5-140-shared-lowering-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-06-26-002-feat-ir-p8.5-143-db-type-drop-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-06-26-003-feat-ir-p8.5-144-reconcile-indexes-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-06-28-001-feat-ir-p8.5-141-single-schema-ir-producer-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-06-29-001-refactor-ir-p8.6-153-create-path-unification-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-06-29-002-refactor-ir-p8.6-146-dialect-enum-unification-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-06-30-001-refactor-ir-p8.6-158-check-renderer-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-06-30-002-fix-ir-p8.6-155-deferred-annotations-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-01-001-feat-ir-p8.6-162-typed-save-bind-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-01-002-fix-ir-p8.6-154-datetime-tz-coarseness-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-01-003-fix-ir-p8.6-165-blob-introspection-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-02-001-fable-fixes-roadmap.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-02-002-feat-ff-a-172-typed-exceptions-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-02-003-ff-c-native-decode-design.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-02-004-ff-c-c2-catalog-cache-design.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-02-005-ff-d-identity-routing-design.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-02-006-ff-d-identity-routing-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-03-007-ff-e-registry-identity-design.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-03-008-ff-e-registry-identity-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-06-009-ff-f-query-builder-1.0-design.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-06-010-ff-f-query-builder-1.0-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-06-011-ff-g-hardening-design.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-06-012-ff-g-hardening-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-06-013-ff-g2-operations-dedup-design.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-06-014-ff-g2-operations-dedup-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/2026-07-06-015-docs-upgrade-guide-consolidation-plan.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/ir-first-migration-guide.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/plans/ir-first-release-checklist.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/rfc/ir-contracts-v1.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/README.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/architecture-patterns/ir-first-lowering-consolidation-audit.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/architecture-patterns/ir-first-merge-readiness-review.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/issues/pydantic-slots-missing-after-ferro-hydration.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/issues/python-3.14-deferred-annotation-typeerror-swallow.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/issues/sa-pk-column-nullable-divergence.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/issues/sa-vs-rust-unique-constraint-shape.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/issues/sqlite-integer-decimal-hydrates-as-none.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/issues/sqlite-null-hydrates-as-int-zero.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/issues/typed-where-null-panics-is-null.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/patterns/configurable-column-storage-types.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/patterns/cross-emitter-ddl-parity.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/patterns/ddl-on-live-engine.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/patterns/derived-type-and-naming-decision-table.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/patterns/foreign-key-index.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/patterns/index-unique-redundancy.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/patterns/ir-invariants.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/patterns/persistence-state.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/patterns/shadow-fk-columns.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/patterns/sqlite-alembic-reconnect-hydration-tests.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/docs/solutions/patterns/typed-null-binds.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/justfile +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/scripts/demo_queries.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/scripts/demo_queries_pre_v012.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/backend.rs +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/codec.rs +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/codec_plan.rs +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/connection.rs +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/errors.rs +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/__init__.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/_annotation_utils.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/_bind_payload.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/_core.pyi +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/_deprecations.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/_shadow_fk_types.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/base.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/columns.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/composite_indexes.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/composite_uniques.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/exceptions.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/fields.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/ir/__init__.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/ir/compiler.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/metaclass.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/migrations/__init__.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/migrations/alembic.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/models.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/py.typed +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/query/__init__.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/query/rows.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/raw.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/relations/__init__.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/relations/descriptors.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/session.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/ferro/state.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/hydration.rs +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/introspect.rs +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/lib.rs +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/migrate.rs +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/naming_ffi.rs +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/operations.rs +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/query.rs +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/schema_bind.rs +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/src/state.rs +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/__init__.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/conftest.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/db_backends.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/README.md +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/codec_registry_core_v1.json +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/query_transaction_aggregate_v5.json +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/query_transaction_global_aggregate_v5.json +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/query_transaction_include_v5.json +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/query_transaction_left_join_v5.json +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/query_transaction_record_v5.json +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/query_transaction_traversal_v5.json +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/query_transaction_traversed_record_v5.json +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/query_user_compound_v5.json +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/schema_invoice_baseline_v1.json +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/schema_phase1_fixture_models_v1.json +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/fixtures/ir_vectors/schema_raw_str_pk_autoincrement_v1.json +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/static_fixtures/bad_includes.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/static_fixtures/bad_predicates.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/static_fixtures/bad_projections.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/static_fixtures/good_includes.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/static_fixtures/good_predicates.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/static_fixtures/good_projections.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_aggregation.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_alembic_autogenerate.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_alembic_bridge.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_alembic_db_type.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_alembic_nullability.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_alembic_type_mapping.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_annotation_utils.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_bulk_create_chunking.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_bulk_install.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_bulk_update.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_catalog_cache.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_codec_plan.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_column_specs.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_composite_index.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_composite_unique.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_connection.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_connection_redaction.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_constraints.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_cross_emitter_parity.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_crud.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_db_backends.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_db_type_cross_emitter_parity.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_db_type_integration.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_db_type_typing.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_db_type_validation.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_deletion.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_deprecations.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_docs_examples.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_documentation_features.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_enum_cold_hydration.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_exception_mapping.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_exceptions.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_field_wrapper.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_fk_target_pk.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_generation_counter_dirty_tracking.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_global_aggregates.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_grouped_aggregates.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_helpers.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_hydration.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_hydration_equivalence.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_identity_memory.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_identity_refresh.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_identity_scoped_invalidation.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_identity_weakref.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_import_budget.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_ir_vectors_contract.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_metaclass_internals.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_metadata.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_migrate_plan.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_model_identity.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_models.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_named_connections_integration.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_naming_single_source.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_one_to_one.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_operation_seam_sync.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_partial_selects.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_provisional_import.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_query_typing.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_raw_sql.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_refresh.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_registry_entrypoints.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_relation_specs.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_relationship_engine.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_relationship_resolution_errors.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_route_single_site.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_routing_errors.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_save_pk_edges.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_save_semantics.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_schema.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_schema_constraints.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_schema_db_type_metadata.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_schema_enum_annotations.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_session.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_shadow_fk_types.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_sqlite_alembic_reconnect_hydration.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_string_search.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_structural_types.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_temporal_types.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_transactions.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_traversed_projection.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_typed_null_binds.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/tests/test_typed_save_bind.py +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/uv.lock +0 -0
- {ferro_orm-0.16.1 → ferro_orm-0.16.2}/zensical.toml +0 -0
|
@@ -1,6 +1,20 @@
|
|
|
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
|
+
|
|
4
18
|
## v0.16.1 (2026-07-13)
|
|
5
19
|
|
|
6
20
|
### Bug Fixes
|
|
@@ -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.
|
|
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.
|
|
893
|
+
version = "1.2.2"
|
|
894
894
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
895
|
-
checksum = "
|
|
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.
|
|
1252
|
+
version = "0.23.42"
|
|
1253
1253
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1254
|
-
checksum = "
|
|
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.
|
|
1447
|
+
version = "0.6.5"
|
|
1448
1448
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1449
|
-
checksum = "
|
|
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.
|
|
1457
|
+
version = "0.9.9"
|
|
1458
1458
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1459
|
-
checksum = "
|
|
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.
|
|
1701
|
+
version = "2.0.119"
|
|
1702
1702
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1703
|
-
checksum = "
|
|
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.
|
|
2332
|
+
version = "1.0.23"
|
|
2333
2333
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2334
|
-
checksum = "
|
|
2334
|
+
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
|
@@ -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
|
-
///
|
|
259
|
-
///
|
|
260
|
-
///
|
|
261
|
-
///
|
|
262
|
-
///
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
282
|
-
|
|
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
|
+
}
|
|
@@ -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.
|