ferro-orm 0.17.0__tar.gz → 0.17.1__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.17.0 → ferro_orm-0.17.1}/CHANGELOG.md +9 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/CONTEXT.md +16 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/Cargo.lock +52 -41
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/Cargo.toml +1 -1
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/PKG-INFO +1 -1
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/crates/ferro-ddl-lowering/src/lib.rs +7 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/crates/ferro-migrate/src/emit.rs +89 -10
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/crates/ferro-migrate/src/lib.rs +103 -1
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/crates/ferro-migrate/src/tests.rs +343 -0
- ferro_orm-0.17.1/docs/adr/0010-create-pass-owns-only-missing-tables.md +39 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/concepts/architecture.md +1 -1
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/faq.md +1 -1
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/guide/migrations.md +3 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/pyproject.toml +1 -1
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/__init__.py +16 -4
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/introspect.rs +231 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/migrate.rs +51 -6
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/schema.rs +13 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_auto_migrate.py +169 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_migrate_plan.py +84 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/.github/PERMISSIONS.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/.github/PYPI_CHECKLIST.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/.github/PYPI_SETUP.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/.github/generated/wheels.generated.yml +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/.github/pull_request_template.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/.github/workflows/ci.yml +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/.github/workflows/packaging-smoke.yml +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/.github/workflows/publish-docs.yml +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/.github/workflows/publish.yml +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/.github/workflows/release.yml +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/.gitignore +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/.pre-commit-config.yaml +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/.python-version +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/AGENTS.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/CONTRIBUTING.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/LICENSE +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/README.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/benchmarks/README.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/benchmarks/__init__.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/benchmarks/__main__.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/benchmarks/backends.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/benchmarks/baselines/postgres.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/benchmarks/baselines/sqlite.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/benchmarks/compare.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/benchmarks/harness.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/benchmarks/model.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/benchmarks/run.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/crates/ferro-ddl-lowering/Cargo.toml +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/crates/ferro-migrate/Cargo.toml +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/crates/ferro-migrate/src/order.rs +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/crates/ferro-schema-ir/Cargo.toml +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/crates/ferro-schema-ir/src/lib.rs +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/adr/0001-resolved-epoch-registration.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/adr/0002-unified-column-fact-derivation.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/adr/0003-path-blind-db-type-validation.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/adr/0004-jsonb-postgres-only-canonical.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/adr/0005-jsonb-default-on-postgres.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/adr/0006-relation-traversal-inner-joins.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/adr/0007-materialization-plan-complete-instances.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/adr/0007-reverse-relations-tested-not-traversed.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/adr/0008-populated-relations-include.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/adr/0008-predicate-negation-uniform-not.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/adr/0009-aggregate-projections-derived-grouping.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/agents/domain.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/agents/issue-tracker.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/agents/triage-labels.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/architecture-review-2026-07-08.html +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-04-29-named-connections-role-routing-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-05-08-typed-query-predicates-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-05-13-configurable-column-storage-types-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-05-14-autogenerate-support-for-db-type-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-05-25-annotated-strenum-cold-hydration-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-06-24-ir-p8-119-wire-automigrate-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-06-25-ir-p8-120-parity-gate-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-06-26-ir-p8.5-140-shared-lowering-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-06-26-ir-p8.5-143-db-type-drop-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-06-26-ir-p8.5-144-reconcile-indexes-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-06-28-ir-p8.5-141-single-schema-ir-producer-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-06-29-ir-p8.6-146-dialect-enum-unification-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-06-29-ir-p8.6-153-create-path-ir-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-06-30-ir-p8.6-155-deferred-annotations-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-06-30-ir-p8.6-158-check-renderer-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-07-01-ir-p8.6-154-datetime-tz-coarseness-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-07-01-ir-p8.6-162-typed-save-bind-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-07-01-ir-p8.6-165-blob-introspection-false-positive-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-07-02-ff-c1-codec-plan-design.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/brainstorms/2026-07-06-upgrade-guide-consolidation-requirements.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/aggregations.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/aggregations_annotated.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/existence_tests.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/existence_tests_annotated.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/multiple_databases.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/mutations.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/pagination.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/partial_selects.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/partial_selects_annotated.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/populated_relations.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/populated_relations_annotated.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/predicates.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/predicates_annotated.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/quickstart.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/quickstart_annotated.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/raw_sql.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/relationships.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/relationships_annotated.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/soft_deletes.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/soft_deletes_annotated.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/testing_conftest.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/timestamps.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/timestamps_annotated.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/transactions.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/traversal.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/examples/traversal_annotated.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/api/connection.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/api/exceptions.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/api/fields.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/api/migrations.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/api/model.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/api/queries.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/api/raw-sql.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/api/relationships.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/api/transactions.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/changelog.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/concepts/backends.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/concepts/identity-map.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/concepts/performance.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/concepts/query-typing.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/concepts/type-safety.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/contributing.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/getting-started/installation.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/getting-started/next-steps.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/getting-started/quickstart.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/guide/aggregations.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/guide/connections.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/guide/models-and-fields.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/guide/mutations.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/guide/queries.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/guide/raw-sql.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/guide/relationships.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/guide/transactions.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/howto/migrate-from-sqlalchemy.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/howto/multiple-databases.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/howto/pagination.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/howto/soft-deletes.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/howto/testing.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/howto/timestamps.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/howto/upgrade-guide.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/index.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/roadmap.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/stylesheets/extra.css +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/pages/why-ferro.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-04-24-001-refactor-multi-db-backend-architecture-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-04-29-001-typed-null-binds-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-04-29-002-feat-named-connections-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-05-07-001-refactor-generic-model-connection-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-05-08-001-feat-typed-query-predicates-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-05-13-001-feat-configurable-column-storage-types-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-05-25-001-fix-annotated-strenum-cold-hydration-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-06-19-001-ir-first-roadmap.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-06-24-001-feat-ir-p8-119-wire-automigrate-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-06-25-001-feat-ir-p8-120-parity-gate-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-06-26-001-feat-ir-p8.5-140-shared-lowering-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-06-26-002-feat-ir-p8.5-143-db-type-drop-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-06-26-003-feat-ir-p8.5-144-reconcile-indexes-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-06-28-001-feat-ir-p8.5-141-single-schema-ir-producer-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-06-29-001-refactor-ir-p8.6-153-create-path-unification-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-06-29-002-refactor-ir-p8.6-146-dialect-enum-unification-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-06-30-001-refactor-ir-p8.6-158-check-renderer-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-06-30-002-fix-ir-p8.6-155-deferred-annotations-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-01-001-feat-ir-p8.6-162-typed-save-bind-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-01-002-fix-ir-p8.6-154-datetime-tz-coarseness-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-01-003-fix-ir-p8.6-165-blob-introspection-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-02-001-fable-fixes-roadmap.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-02-002-feat-ff-a-172-typed-exceptions-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-02-003-ff-c-native-decode-design.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-02-004-ff-c-c2-catalog-cache-design.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-02-005-ff-d-identity-routing-design.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-02-006-ff-d-identity-routing-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-03-007-ff-e-registry-identity-design.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-03-008-ff-e-registry-identity-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-06-009-ff-f-query-builder-1.0-design.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-06-010-ff-f-query-builder-1.0-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-06-011-ff-g-hardening-design.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-06-012-ff-g-hardening-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-06-013-ff-g2-operations-dedup-design.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-06-014-ff-g2-operations-dedup-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-06-015-docs-upgrade-guide-consolidation-plan.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/2026-07-18-016-existence-tests-design.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/ir-first-migration-guide.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/plans/ir-first-release-checklist.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/rfc/ir-contracts-v1.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/README.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/architecture-patterns/ir-first-lowering-consolidation-audit.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/architecture-patterns/ir-first-merge-readiness-review.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/issues/pydantic-slots-missing-after-ferro-hydration.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/issues/python-3.14-deferred-annotation-typeerror-swallow.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/issues/sa-pk-column-nullable-divergence.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/issues/sa-vs-rust-unique-constraint-shape.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/issues/self-fk-evicts-component-from-create-order.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/issues/sqlite-integer-decimal-hydrates-as-none.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/issues/sqlite-null-hydrates-as-int-zero.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/issues/typed-where-null-panics-is-null.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/patterns/configurable-column-storage-types.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/patterns/cross-emitter-ddl-parity.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/patterns/ddl-on-live-engine.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/patterns/derived-type-and-naming-decision-table.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/patterns/foreign-key-index.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/patterns/index-unique-redundancy.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/patterns/ir-invariants.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/patterns/persistence-state.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/patterns/shadow-fk-columns.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/patterns/sqlite-alembic-reconnect-hydration-tests.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/docs/solutions/patterns/typed-null-binds.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/justfile +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/scripts/demo_queries.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/scripts/demo_queries_pre_v012.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/backend.rs +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/codec.rs +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/codec_plan.rs +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/connection.rs +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/errors.rs +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/_annotation_utils.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/_bind_payload.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/_core.pyi +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/_deprecations.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/_shadow_fk_types.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/base.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/columns.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/composite_indexes.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/composite_uniques.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/exceptions.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/fields.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/ir/__init__.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/ir/compiler.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/metaclass.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/migrations/__init__.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/migrations/alembic.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/models.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/py.typed +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/query/__init__.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/query/builder.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/query/nodes.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/query/rows.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/query/wire.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/raw.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/registry.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/relations/__init__.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/relations/descriptors.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/session.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/ferro/state.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/hydration.rs +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/lib.rs +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/naming_ffi.rs +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/operations.rs +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/query.rs +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/schema_bind.rs +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/src/state.rs +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/__init__.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/conftest.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/db_backends.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/README.md +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/codec_registry_core_v1.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/query_account_exists_v7.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/query_account_scoped_exists_v7.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/query_owner_nested_exists_v7.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/query_owner_not_exists_v7.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/query_transaction_aggregate_v7.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/query_transaction_global_aggregate_v7.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/query_transaction_include_v7.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/query_transaction_left_join_v7.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/query_transaction_record_v7.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/query_transaction_traversal_v7.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/query_transaction_traversed_record_v7.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/query_user_compound_v7.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/query_user_m2m_exists_v7.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/query_user_not_compound_v7.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/query_user_not_leaf_v7.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/schema_invoice_baseline_v1.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/schema_phase1_fixture_models_v1.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/fixtures/ir_vectors/schema_raw_str_pk_autoincrement_v1.json +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/static_fixtures/bad_includes.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/static_fixtures/bad_predicates.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/static_fixtures/bad_projections.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/static_fixtures/good_includes.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/static_fixtures/good_predicates.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/static_fixtures/good_projections.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_aggregation.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_alembic_autogenerate.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_alembic_bridge.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_alembic_db_type.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_alembic_nullability.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_alembic_type_mapping.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_annotation_utils.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_bulk_create_chunking.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_bulk_install.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_bulk_update.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_catalog_cache.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_codec_plan.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_column_specs.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_compiled_query.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_composite_index.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_composite_unique.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_connection.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_connection_redaction.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_constraints.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_cross_emitter_parity.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_crud.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_db_backends.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_db_type_cross_emitter_parity.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_db_type_integration.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_db_type_typing.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_db_type_validation.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_deletion.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_deprecations.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_docs_examples.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_documentation_features.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_enum_cold_hydration.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_exception_mapping.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_exceptions.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_field_wrapper.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_fk_target_pk.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_generation_counter_dirty_tracking.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_global_aggregates.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_grouped_aggregates.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_helpers.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_hydration.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_hydration_equivalence.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_identity_memory.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_identity_refresh.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_identity_scoped_invalidation.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_identity_weakref.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_import_budget.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_include.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_ir_vectors_contract.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_metaclass_internals.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_metadata.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_model_identity.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_models.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_mutation_pagination_guard.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_named_connections_integration.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_naming_single_source.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_negation.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_one_to_one.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_operation_seam_sync.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_partial_selects.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_primary_key_facts.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_provisional_import.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_query_builder.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_query_column_validation.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_query_exists.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_query_immutability.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_query_joins.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_query_typing.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_query_wire_vectors.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_raw_sql.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_refresh.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_registry_entrypoints.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_registry_owner.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_relation_specs.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_relationship_engine.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_relationship_resolution_errors.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_route_single_site.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_routing_errors.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_save_pk_edges.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_save_semantics.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_schema.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_schema_constraints.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_schema_db_type_metadata.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_schema_enum_annotations.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_session.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_shadow_fk_types.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_sqlite_alembic_reconnect_hydration.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_static_contracts.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_string_search.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_structural_types.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_temporal_types.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_transactions.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_traversed_projection.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_typed_null_binds.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/tests/test_typed_save_bind.py +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/uv.lock +0 -0
- {ferro_orm-0.17.0 → ferro_orm-0.17.1}/zensical.toml +0 -0
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v0.17.1 (2026-07-19)
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- **migrate**: Auto-migrate pass ownership — column-before-index sequencing and FK on_delete
|
|
9
|
+
reconciliation ([#326](https://github.com/syn54x/ferro-orm/pull/326),
|
|
10
|
+
[`3451b1d`](https://github.com/syn54x/ferro-orm/commit/3451b1dad37d8ac39709d986aa20b1de4c12542a))
|
|
11
|
+
|
|
12
|
+
|
|
4
13
|
## v0.17.0 (2026-07-18)
|
|
5
14
|
|
|
6
15
|
### Documentation
|
|
@@ -123,3 +123,19 @@ _Avoid_: Import-time registration, partial registry
|
|
|
123
123
|
**Resolved registration**:
|
|
124
124
|
The registry epoch after relationship resolution completes — join tables exist, shadow FK columns are wired, and the SchemaIR modelset is authoritative for DDL and auto-migrate.
|
|
125
125
|
_Avoid_: Final registration, committed registry
|
|
126
|
+
|
|
127
|
+
**Create pass**:
|
|
128
|
+
The auto-migrate step that brings missing tables into existence — the table, its columns, its indexes and constraints, together. A table that already exists is left completely untouched by this pass, whatever its shape.
|
|
129
|
+
_Avoid_: Bootstrap, ensure-tables, table sync
|
|
130
|
+
|
|
131
|
+
**Reconciliation pass**:
|
|
132
|
+
The `migrate_updates` step that alters existing tables to match the registered models — the only authority for DDL against a table that already exists. Within one table, column changes land before the indexes and constraints that reference them.
|
|
133
|
+
_Avoid_: Update pass, schema sync, drift repair
|
|
134
|
+
|
|
135
|
+
**Ferro-owned artifact**:
|
|
136
|
+
An index or constraint whose name follows ferro's canonical naming (`idx_`, `uq_`, `fk_`, `ck_`), marking it as reconcilable: auto-migrate may create, rebuild, or drop it to match the declared model. Artifacts named any other way belong to the user and are never altered or dropped.
|
|
137
|
+
_Avoid_: Managed index, system constraint, internal index
|
|
138
|
+
|
|
139
|
+
**Constraint rebuild**:
|
|
140
|
+
Drop-and-recreate of a ferro-owned constraint whose live definition no longer matches the declared model — a foreign key's `on_delete`, its target, its columns. Metadata-only: rows are never touched. On a backend that cannot alter constraints, ferro warns loudly and skips; it never diverges silently.
|
|
141
|
+
_Avoid_: Constraint alter, FK patch, in-place constraint update
|
|
@@ -95,9 +95,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
|
|
95
95
|
|
|
96
96
|
[[package]]
|
|
97
97
|
name = "cc"
|
|
98
|
-
version = "1.
|
|
98
|
+
version = "1.3.0"
|
|
99
99
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
100
|
-
checksum = "
|
|
100
|
+
checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8"
|
|
101
101
|
dependencies = [
|
|
102
102
|
"find-msvc-tools",
|
|
103
103
|
"shlex",
|
|
@@ -210,7 +210,7 @@ dependencies = [
|
|
|
210
210
|
"ident_case",
|
|
211
211
|
"proc-macro2",
|
|
212
212
|
"quote",
|
|
213
|
-
"syn",
|
|
213
|
+
"syn 2.0.119",
|
|
214
214
|
]
|
|
215
215
|
|
|
216
216
|
[[package]]
|
|
@@ -221,7 +221,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
|
|
221
221
|
dependencies = [
|
|
222
222
|
"darling_core",
|
|
223
223
|
"quote",
|
|
224
|
-
"syn",
|
|
224
|
+
"syn 2.0.119",
|
|
225
225
|
]
|
|
226
226
|
|
|
227
227
|
[[package]]
|
|
@@ -269,7 +269,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
|
|
|
269
269
|
dependencies = [
|
|
270
270
|
"proc-macro2",
|
|
271
271
|
"quote",
|
|
272
|
-
"syn",
|
|
272
|
+
"syn 2.0.119",
|
|
273
273
|
]
|
|
274
274
|
|
|
275
275
|
[[package]]
|
|
@@ -327,7 +327,7 @@ dependencies = [
|
|
|
327
327
|
|
|
328
328
|
[[package]]
|
|
329
329
|
name = "ferro"
|
|
330
|
-
version = "0.17.
|
|
330
|
+
version = "0.17.1"
|
|
331
331
|
dependencies = [
|
|
332
332
|
"chrono",
|
|
333
333
|
"dashmap",
|
|
@@ -477,7 +477,7 @@ checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
|
|
|
477
477
|
dependencies = [
|
|
478
478
|
"proc-macro2",
|
|
479
479
|
"quote",
|
|
480
|
-
"syn",
|
|
480
|
+
"syn 2.0.119",
|
|
481
481
|
]
|
|
482
482
|
|
|
483
483
|
[[package]]
|
|
@@ -772,13 +772,13 @@ dependencies = [
|
|
|
772
772
|
|
|
773
773
|
[[package]]
|
|
774
774
|
name = "inherent"
|
|
775
|
-
version = "1.0.
|
|
775
|
+
version = "1.0.14"
|
|
776
776
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
777
|
-
checksum = "
|
|
777
|
+
checksum = "bee2c455ca60511a054699102d40ce7153621cb2429558f9eaa600e4499b5984"
|
|
778
778
|
dependencies = [
|
|
779
779
|
"proc-macro2",
|
|
780
780
|
"quote",
|
|
781
|
-
"syn",
|
|
781
|
+
"syn 3.0.0",
|
|
782
782
|
]
|
|
783
783
|
|
|
784
784
|
[[package]]
|
|
@@ -1069,9 +1069,9 @@ dependencies = [
|
|
|
1069
1069
|
|
|
1070
1070
|
[[package]]
|
|
1071
1071
|
name = "proc-macro2"
|
|
1072
|
-
version = "1.0.
|
|
1072
|
+
version = "1.0.107"
|
|
1073
1073
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1074
|
-
checksum = "
|
|
1074
|
+
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
|
1075
1075
|
dependencies = [
|
|
1076
1076
|
"unicode-ident",
|
|
1077
1077
|
]
|
|
@@ -1134,7 +1134,7 @@ dependencies = [
|
|
|
1134
1134
|
"proc-macro2",
|
|
1135
1135
|
"pyo3-macros-backend",
|
|
1136
1136
|
"quote",
|
|
1137
|
-
"syn",
|
|
1137
|
+
"syn 2.0.119",
|
|
1138
1138
|
]
|
|
1139
1139
|
|
|
1140
1140
|
[[package]]
|
|
@@ -1147,14 +1147,14 @@ dependencies = [
|
|
|
1147
1147
|
"proc-macro2",
|
|
1148
1148
|
"pyo3-build-config",
|
|
1149
1149
|
"quote",
|
|
1150
|
-
"syn",
|
|
1150
|
+
"syn 2.0.119",
|
|
1151
1151
|
]
|
|
1152
1152
|
|
|
1153
1153
|
[[package]]
|
|
1154
1154
|
name = "quote"
|
|
1155
|
-
version = "1.0.
|
|
1155
|
+
version = "1.0.47"
|
|
1156
1156
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1157
|
-
checksum = "
|
|
1157
|
+
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
|
1158
1158
|
dependencies = [
|
|
1159
1159
|
"proc-macro2",
|
|
1160
1160
|
]
|
|
@@ -1320,15 +1320,15 @@ dependencies = [
|
|
|
1320
1320
|
"heck 0.4.1",
|
|
1321
1321
|
"proc-macro2",
|
|
1322
1322
|
"quote",
|
|
1323
|
-
"syn",
|
|
1323
|
+
"syn 2.0.119",
|
|
1324
1324
|
"thiserror",
|
|
1325
1325
|
]
|
|
1326
1326
|
|
|
1327
1327
|
[[package]]
|
|
1328
1328
|
name = "serde"
|
|
1329
|
-
version = "1.0.
|
|
1329
|
+
version = "1.0.229"
|
|
1330
1330
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1331
|
-
checksum = "
|
|
1331
|
+
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
|
1332
1332
|
dependencies = [
|
|
1333
1333
|
"serde_core",
|
|
1334
1334
|
"serde_derive",
|
|
@@ -1336,22 +1336,22 @@ dependencies = [
|
|
|
1336
1336
|
|
|
1337
1337
|
[[package]]
|
|
1338
1338
|
name = "serde_core"
|
|
1339
|
-
version = "1.0.
|
|
1339
|
+
version = "1.0.229"
|
|
1340
1340
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1341
|
-
checksum = "
|
|
1341
|
+
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
|
1342
1342
|
dependencies = [
|
|
1343
1343
|
"serde_derive",
|
|
1344
1344
|
]
|
|
1345
1345
|
|
|
1346
1346
|
[[package]]
|
|
1347
1347
|
name = "serde_derive"
|
|
1348
|
-
version = "1.0.
|
|
1348
|
+
version = "1.0.229"
|
|
1349
1349
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1350
|
-
checksum = "
|
|
1350
|
+
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
|
1351
1351
|
dependencies = [
|
|
1352
1352
|
"proc-macro2",
|
|
1353
1353
|
"quote",
|
|
1354
|
-
"syn",
|
|
1354
|
+
"syn 3.0.0",
|
|
1355
1355
|
]
|
|
1356
1356
|
|
|
1357
1357
|
[[package]]
|
|
@@ -1533,7 +1533,7 @@ dependencies = [
|
|
|
1533
1533
|
"quote",
|
|
1534
1534
|
"sqlx-core",
|
|
1535
1535
|
"sqlx-macros-core",
|
|
1536
|
-
"syn",
|
|
1536
|
+
"syn 2.0.119",
|
|
1537
1537
|
]
|
|
1538
1538
|
|
|
1539
1539
|
[[package]]
|
|
@@ -1556,7 +1556,7 @@ dependencies = [
|
|
|
1556
1556
|
"sqlx-mysql",
|
|
1557
1557
|
"sqlx-postgres",
|
|
1558
1558
|
"sqlx-sqlite",
|
|
1559
|
-
"syn",
|
|
1559
|
+
"syn 2.0.119",
|
|
1560
1560
|
"tokio",
|
|
1561
1561
|
"url",
|
|
1562
1562
|
]
|
|
@@ -1707,6 +1707,17 @@ dependencies = [
|
|
|
1707
1707
|
"unicode-ident",
|
|
1708
1708
|
]
|
|
1709
1709
|
|
|
1710
|
+
[[package]]
|
|
1711
|
+
name = "syn"
|
|
1712
|
+
version = "3.0.0"
|
|
1713
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1714
|
+
checksum = "f2fac314a64dc9a36e61a9eb4261a5e9bbfbc922b27e518af97bc32b926cf967"
|
|
1715
|
+
dependencies = [
|
|
1716
|
+
"proc-macro2",
|
|
1717
|
+
"quote",
|
|
1718
|
+
"unicode-ident",
|
|
1719
|
+
]
|
|
1720
|
+
|
|
1710
1721
|
[[package]]
|
|
1711
1722
|
name = "synstructure"
|
|
1712
1723
|
version = "0.13.2"
|
|
@@ -1715,7 +1726,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
|
|
1715
1726
|
dependencies = [
|
|
1716
1727
|
"proc-macro2",
|
|
1717
1728
|
"quote",
|
|
1718
|
-
"syn",
|
|
1729
|
+
"syn 2.0.119",
|
|
1719
1730
|
]
|
|
1720
1731
|
|
|
1721
1732
|
[[package]]
|
|
@@ -1726,22 +1737,22 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
|
1726
1737
|
|
|
1727
1738
|
[[package]]
|
|
1728
1739
|
name = "thiserror"
|
|
1729
|
-
version = "2.0.
|
|
1740
|
+
version = "2.0.19"
|
|
1730
1741
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1731
|
-
checksum = "
|
|
1742
|
+
checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
|
|
1732
1743
|
dependencies = [
|
|
1733
1744
|
"thiserror-impl",
|
|
1734
1745
|
]
|
|
1735
1746
|
|
|
1736
1747
|
[[package]]
|
|
1737
1748
|
name = "thiserror-impl"
|
|
1738
|
-
version = "2.0.
|
|
1749
|
+
version = "2.0.19"
|
|
1739
1750
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1740
|
-
checksum = "
|
|
1751
|
+
checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
|
|
1741
1752
|
dependencies = [
|
|
1742
1753
|
"proc-macro2",
|
|
1743
1754
|
"quote",
|
|
1744
|
-
"syn",
|
|
1755
|
+
"syn 3.0.0",
|
|
1745
1756
|
]
|
|
1746
1757
|
|
|
1747
1758
|
[[package]]
|
|
@@ -1794,7 +1805,7 @@ checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba"
|
|
|
1794
1805
|
dependencies = [
|
|
1795
1806
|
"proc-macro2",
|
|
1796
1807
|
"quote",
|
|
1797
|
-
"syn",
|
|
1808
|
+
"syn 2.0.119",
|
|
1798
1809
|
]
|
|
1799
1810
|
|
|
1800
1811
|
[[package]]
|
|
@@ -1828,7 +1839,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
|
|
1828
1839
|
dependencies = [
|
|
1829
1840
|
"proc-macro2",
|
|
1830
1841
|
"quote",
|
|
1831
|
-
"syn",
|
|
1842
|
+
"syn 2.0.119",
|
|
1832
1843
|
]
|
|
1833
1844
|
|
|
1834
1845
|
[[package]]
|
|
@@ -1970,7 +1981,7 @@ dependencies = [
|
|
|
1970
1981
|
"bumpalo",
|
|
1971
1982
|
"proc-macro2",
|
|
1972
1983
|
"quote",
|
|
1973
|
-
"syn",
|
|
1984
|
+
"syn 2.0.119",
|
|
1974
1985
|
"wasm-bindgen-shared",
|
|
1975
1986
|
]
|
|
1976
1987
|
|
|
@@ -2032,7 +2043,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
|
|
2032
2043
|
dependencies = [
|
|
2033
2044
|
"proc-macro2",
|
|
2034
2045
|
"quote",
|
|
2035
|
-
"syn",
|
|
2046
|
+
"syn 2.0.119",
|
|
2036
2047
|
]
|
|
2037
2048
|
|
|
2038
2049
|
[[package]]
|
|
@@ -2043,7 +2054,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
|
|
2043
2054
|
dependencies = [
|
|
2044
2055
|
"proc-macro2",
|
|
2045
2056
|
"quote",
|
|
2046
|
-
"syn",
|
|
2057
|
+
"syn 2.0.119",
|
|
2047
2058
|
]
|
|
2048
2059
|
|
|
2049
2060
|
[[package]]
|
|
@@ -2243,7 +2254,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
|
|
|
2243
2254
|
dependencies = [
|
|
2244
2255
|
"proc-macro2",
|
|
2245
2256
|
"quote",
|
|
2246
|
-
"syn",
|
|
2257
|
+
"syn 2.0.119",
|
|
2247
2258
|
"synstructure",
|
|
2248
2259
|
]
|
|
2249
2260
|
|
|
@@ -2264,7 +2275,7 @@ checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
|
|
|
2264
2275
|
dependencies = [
|
|
2265
2276
|
"proc-macro2",
|
|
2266
2277
|
"quote",
|
|
2267
|
-
"syn",
|
|
2278
|
+
"syn 2.0.119",
|
|
2268
2279
|
]
|
|
2269
2280
|
|
|
2270
2281
|
[[package]]
|
|
@@ -2284,7 +2295,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
|
|
|
2284
2295
|
dependencies = [
|
|
2285
2296
|
"proc-macro2",
|
|
2286
2297
|
"quote",
|
|
2287
|
-
"syn",
|
|
2298
|
+
"syn 2.0.119",
|
|
2288
2299
|
"synstructure",
|
|
2289
2300
|
]
|
|
2290
2301
|
|
|
@@ -2324,7 +2335,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
|
|
|
2324
2335
|
dependencies = [
|
|
2325
2336
|
"proc-macro2",
|
|
2326
2337
|
"quote",
|
|
2327
|
-
"syn",
|
|
2338
|
+
"syn 2.0.119",
|
|
2328
2339
|
]
|
|
2329
2340
|
|
|
2330
2341
|
[[package]]
|
|
@@ -542,6 +542,13 @@ pub fn fk_name(table_lower: &str, col_name: &str, to_table: &str) -> String {
|
|
|
542
542
|
raw
|
|
543
543
|
}
|
|
544
544
|
|
|
545
|
+
/// Whether a live constraint name follows the ferro FK convention above — the
|
|
546
|
+
/// ownership test: reconciliation only ever rebuilds names ferro emits;
|
|
547
|
+
/// constraints named any other way belong to the user and are never altered.
|
|
548
|
+
pub fn is_ferro_fk_name(name: &str) -> bool {
|
|
549
|
+
name.starts_with("fk_")
|
|
550
|
+
}
|
|
551
|
+
|
|
545
552
|
/// Single-column unique name with 63-char guard.
|
|
546
553
|
pub fn single_unique_index_name(table_lower: &str, col_name: &str) -> String {
|
|
547
554
|
let raw = format!("uq_{table_lower}_{col_name}");
|
|
@@ -408,16 +408,7 @@ fn emit_add_column(
|
|
|
408
408
|
|
|
409
409
|
if let Some(fk) = model.foreign_keys.iter().find(|fk| fk.column == column) {
|
|
410
410
|
if dialect == Dialect::Postgres {
|
|
411
|
-
|
|
412
|
-
result.statements.push(format!(
|
|
413
|
-
"ALTER TABLE {} ADD CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {} ({}) ON DELETE {}",
|
|
414
|
-
quote_ident(table),
|
|
415
|
-
quote_ident(&fk_constraint_name(table, fk)),
|
|
416
|
-
quote_ident(column),
|
|
417
|
-
quote_ident(&fk.to_table),
|
|
418
|
-
quote_ident(&fk.to_column),
|
|
419
|
-
fk_action_sql(on_delete),
|
|
420
|
-
));
|
|
411
|
+
result.statements.push(render_add_fk_sql(table, fk));
|
|
421
412
|
} else {
|
|
422
413
|
result.warnings.push(format!(
|
|
423
414
|
"Added foreign-key column '{}.{}' without its FOREIGN KEY constraint \
|
|
@@ -432,6 +423,39 @@ fn emit_add_column(
|
|
|
432
423
|
Ok(result)
|
|
433
424
|
}
|
|
434
425
|
|
|
426
|
+
/// `ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY ... ON DELETE ...` for one
|
|
427
|
+
/// IR foreign key. Postgres-only — SQLite cannot add table constraints to an
|
|
428
|
+
/// existing table; its callers warn instead.
|
|
429
|
+
fn render_add_fk_sql(table: &str, fk: &ferro_schema_ir::SchemaForeignKey) -> String {
|
|
430
|
+
format!(
|
|
431
|
+
"ALTER TABLE {} ADD CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {} ({}) ON DELETE {}",
|
|
432
|
+
quote_ident(table),
|
|
433
|
+
quote_ident(&fk_constraint_name(table, fk)),
|
|
434
|
+
quote_ident(&fk.column),
|
|
435
|
+
quote_ident(&fk.to_table),
|
|
436
|
+
quote_ident(&fk.to_column),
|
|
437
|
+
fk_action_sql(fk_action_from_str(fk.on_delete.as_deref())),
|
|
438
|
+
)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/// Find the declared FK for `column` on `table` in the new-IR model.
|
|
442
|
+
fn find_foreign_key<'a>(
|
|
443
|
+
model: &'a SchemaModel,
|
|
444
|
+
table: &str,
|
|
445
|
+
column: &str,
|
|
446
|
+
) -> Result<&'a ferro_schema_ir::SchemaForeignKey, EmissionError> {
|
|
447
|
+
model
|
|
448
|
+
.foreign_keys
|
|
449
|
+
.iter()
|
|
450
|
+
.find(|fk| fk.column == column)
|
|
451
|
+
.ok_or_else(|| EmissionError {
|
|
452
|
+
message: format!(
|
|
453
|
+
"Foreign-key operation for '{}.{}' has no matching FK in the declared IR",
|
|
454
|
+
table, column
|
|
455
|
+
),
|
|
456
|
+
})
|
|
457
|
+
}
|
|
458
|
+
|
|
435
459
|
fn emit_alter_column_type(
|
|
436
460
|
table: &str,
|
|
437
461
|
column: &str,
|
|
@@ -673,6 +697,61 @@ pub fn emit_sql_with_ir(
|
|
|
673
697
|
MigrationOp::DropIndex { table: _, name } => {
|
|
674
698
|
result.statements.push(format!("DROP INDEX IF EXISTS \"{}\"", name));
|
|
675
699
|
}
|
|
700
|
+
MigrationOp::AddForeignKey { table, column } => {
|
|
701
|
+
let model = find_model(&new_models, table)?;
|
|
702
|
+
let fk = find_foreign_key(model, table, column)?;
|
|
703
|
+
match dialect {
|
|
704
|
+
Dialect::Postgres => result.statements.push(render_add_fk_sql(table, fk)),
|
|
705
|
+
Dialect::Sqlite => result.warnings.push(format!(
|
|
706
|
+
"Declared FOREIGN KEY on '{}.{}' (on_delete {}) has no live \
|
|
707
|
+
constraint, and SQLite cannot add table constraints to an \
|
|
708
|
+
existing table. Referential integrity for this column is not \
|
|
709
|
+
database-enforced; use Alembic if you need the constraint.",
|
|
710
|
+
table,
|
|
711
|
+
column,
|
|
712
|
+
fk_action_sql(fk_action_from_str(fk.on_delete.as_deref())),
|
|
713
|
+
)),
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
MigrationOp::RebuildForeignKey {
|
|
717
|
+
table,
|
|
718
|
+
column,
|
|
719
|
+
old_name,
|
|
720
|
+
} => {
|
|
721
|
+
let model = find_model(&new_models, table)?;
|
|
722
|
+
let fk = find_foreign_key(model, table, column)?;
|
|
723
|
+
match dialect {
|
|
724
|
+
Dialect::Postgres => {
|
|
725
|
+
result.statements.push(format!(
|
|
726
|
+
"ALTER TABLE {} DROP CONSTRAINT {}",
|
|
727
|
+
quote_ident(table),
|
|
728
|
+
quote_ident(old_name),
|
|
729
|
+
));
|
|
730
|
+
result.statements.push(render_add_fk_sql(table, fk));
|
|
731
|
+
}
|
|
732
|
+
Dialect::Sqlite => {
|
|
733
|
+
let live_action = find_model(&old_models, table)
|
|
734
|
+
.ok()
|
|
735
|
+
.and_then(|old| {
|
|
736
|
+
old.foreign_keys.iter().find(|live| live.column == *column)
|
|
737
|
+
})
|
|
738
|
+
.map(|live| {
|
|
739
|
+
fk_action_sql(fk_action_from_str(live.on_delete.as_deref()))
|
|
740
|
+
})
|
|
741
|
+
.unwrap_or("<unknown>");
|
|
742
|
+
result.warnings.push(format!(
|
|
743
|
+
"Foreign key on '{}.{}' declares on_delete {} but the live \
|
|
744
|
+
constraint enforces {}; SQLite cannot alter constraints in \
|
|
745
|
+
place, so the live behavior remains. Migrate with Alembic to \
|
|
746
|
+
apply the declared action.",
|
|
747
|
+
table,
|
|
748
|
+
column,
|
|
749
|
+
fk_action_sql(fk_action_from_str(fk.on_delete.as_deref())),
|
|
750
|
+
live_action,
|
|
751
|
+
));
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
676
755
|
}
|
|
677
756
|
}
|
|
678
757
|
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
mod emit;
|
|
7
7
|
mod order;
|
|
8
8
|
|
|
9
|
-
use ferro_ddl_lowering::
|
|
9
|
+
use ferro_ddl_lowering::{
|
|
10
|
+
fk_action_from_str, fk_action_sql, fk_name, is_ferro_fk_name, schema_columns_storage_drift,
|
|
11
|
+
};
|
|
10
12
|
use ferro_schema_ir::{IrEnvelope, SchemaIrPayload, SchemaModel};
|
|
11
13
|
use std::collections::{BTreeMap, BTreeSet};
|
|
12
14
|
|
|
@@ -97,6 +99,25 @@ pub enum MigrationOp {
|
|
|
97
99
|
/// Index name.
|
|
98
100
|
name: String,
|
|
99
101
|
},
|
|
102
|
+
/// A declared FK whose column exists live but has no FK constraint at all.
|
|
103
|
+
/// (FKs on newly added columns ride the `AddColumn` emission instead.)
|
|
104
|
+
AddForeignKey {
|
|
105
|
+
/// Owning table.
|
|
106
|
+
table: String,
|
|
107
|
+
/// Local FK column.
|
|
108
|
+
column: String,
|
|
109
|
+
},
|
|
110
|
+
/// A live ferro-owned FK whose definition (`on_delete`, target) drifted
|
|
111
|
+
/// from the declared FK on the same column — rebuilt as
|
|
112
|
+
/// `DROP CONSTRAINT` + `ADD CONSTRAINT` where the backend allows it.
|
|
113
|
+
RebuildForeignKey {
|
|
114
|
+
/// Owning table.
|
|
115
|
+
table: String,
|
|
116
|
+
/// Local FK column.
|
|
117
|
+
column: String,
|
|
118
|
+
/// Name of the live constraint to drop.
|
|
119
|
+
old_name: String,
|
|
120
|
+
},
|
|
100
121
|
}
|
|
101
122
|
|
|
102
123
|
/// Ordered migration operations plus non-fatal warnings collected during planning.
|
|
@@ -167,6 +188,13 @@ pub fn emit_sql(plan: &MigrationPlan, dialect: Dialect) -> Vec<String> {
|
|
|
167
188
|
MigrationOp::DropIndex { name, .. } => {
|
|
168
189
|
sql.push(format!("-- index '{}' handled by emit_sql_with_ir", name));
|
|
169
190
|
}
|
|
191
|
+
MigrationOp::AddForeignKey { table, column }
|
|
192
|
+
| MigrationOp::RebuildForeignKey { table, column, .. } => {
|
|
193
|
+
sql.push(format!(
|
|
194
|
+
"-- foreign key for '{}.{}' handled by emit_sql_with_ir",
|
|
195
|
+
table, column
|
|
196
|
+
));
|
|
197
|
+
}
|
|
170
198
|
}
|
|
171
199
|
}
|
|
172
200
|
sql
|
|
@@ -205,6 +233,7 @@ pub fn plan_from_ir(
|
|
|
205
233
|
};
|
|
206
234
|
diff_model_columns(*table, old_model, new_model, dialect, &mut plan);
|
|
207
235
|
diff_model_indexes(*table, old_model, new_model, &mut plan);
|
|
236
|
+
diff_model_foreign_keys(*table, old_model, new_model, &mut plan);
|
|
208
237
|
}
|
|
209
238
|
|
|
210
239
|
plan
|
|
@@ -320,5 +349,78 @@ fn diff_model_indexes(
|
|
|
320
349
|
}
|
|
321
350
|
}
|
|
322
351
|
|
|
352
|
+
fn diff_model_foreign_keys(
|
|
353
|
+
table: &str,
|
|
354
|
+
old_model: &SchemaModel,
|
|
355
|
+
new_model: &SchemaModel,
|
|
356
|
+
plan: &mut MigrationPlan,
|
|
357
|
+
) {
|
|
358
|
+
let old_col_names: BTreeSet<&str> = old_model.columns.iter().map(|c| c.name.as_str()).collect();
|
|
359
|
+
|
|
360
|
+
for fk in &new_model.foreign_keys {
|
|
361
|
+
// An FK on a newly added column rides the AddColumn emission; the
|
|
362
|
+
// reconcile step only governs FKs whose column already exists live.
|
|
363
|
+
if !old_col_names.contains(fk.column.as_str()) {
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
let Some(live) = old_model
|
|
368
|
+
.foreign_keys
|
|
369
|
+
.iter()
|
|
370
|
+
.find(|live| live.column == fk.column)
|
|
371
|
+
else {
|
|
372
|
+
plan.operations.push(MigrationOp::AddForeignKey {
|
|
373
|
+
table: table.to_string(),
|
|
374
|
+
column: fk.column.clone(),
|
|
375
|
+
});
|
|
376
|
+
continue;
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// Live `to_column` can be empty when the backend reports an
|
|
380
|
+
// implicit-PK reference (SQLite); only a stated target can drift.
|
|
381
|
+
let target_drift = live.to_table != fk.to_table
|
|
382
|
+
|| (!live.to_column.is_empty() && live.to_column != fk.to_column);
|
|
383
|
+
// Compare via the canonical SQL rendering — sea-query's
|
|
384
|
+
// `ForeignKeyAction` has no equality of its own.
|
|
385
|
+
let action_drift = fk_action_sql(fk_action_from_str(live.on_delete.as_deref()))
|
|
386
|
+
!= fk_action_sql(fk_action_from_str(fk.on_delete.as_deref()));
|
|
387
|
+
if !target_drift && !action_drift {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
match live.name.as_deref() {
|
|
392
|
+
// A drifting constraint ferro does not own is never altered —
|
|
393
|
+
// but it is never silent either.
|
|
394
|
+
Some(name) if !is_ferro_fk_name(name) => {
|
|
395
|
+
plan.warnings.push(format!(
|
|
396
|
+
"Foreign key on '{}.{}' drifts from the model (live: REFERENCES {} \
|
|
397
|
+
ON DELETE {}; declared: REFERENCES {} ON DELETE {}), but the live \
|
|
398
|
+
constraint '{}' is not ferro-owned, so it is left untouched. \
|
|
399
|
+
Migrate it manually or with Alembic.",
|
|
400
|
+
table,
|
|
401
|
+
fk.column,
|
|
402
|
+
live.to_table,
|
|
403
|
+
fk_action_sql(fk_action_from_str(live.on_delete.as_deref())),
|
|
404
|
+
fk.to_table,
|
|
405
|
+
fk_action_sql(fk_action_from_str(fk.on_delete.as_deref())),
|
|
406
|
+
name,
|
|
407
|
+
));
|
|
408
|
+
}
|
|
409
|
+
_ => {
|
|
410
|
+
plan.operations.push(MigrationOp::RebuildForeignKey {
|
|
411
|
+
table: table.to_string(),
|
|
412
|
+
column: fk.column.clone(),
|
|
413
|
+
// SQLite exposes no live constraint names; fall back to
|
|
414
|
+
// the canonical name (unused there — emission warns).
|
|
415
|
+
old_name: live
|
|
416
|
+
.name
|
|
417
|
+
.clone()
|
|
418
|
+
.unwrap_or_else(|| fk_name(table, &fk.column, &live.to_table)),
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
323
425
|
#[cfg(test)]
|
|
324
426
|
mod tests;
|