ferro-orm 0.13.0__tar.gz → 0.14.0__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.13.0 → ferro_orm-0.14.0}/.github/workflows/ci.yml +4 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/AGENTS.md +13 -8
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/CHANGELOG.md +100 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/Cargo.lock +148 -13
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/Cargo.toml +9 -2
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/PKG-INFO +2 -2
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/README.md +1 -1
- ferro_orm-0.14.0/benchmarks/README.md +133 -0
- ferro_orm-0.14.0/benchmarks/__init__.py +11 -0
- ferro_orm-0.14.0/benchmarks/__main__.py +10 -0
- ferro_orm-0.14.0/benchmarks/backends.py +88 -0
- ferro_orm-0.14.0/benchmarks/baselines/postgres.json +49 -0
- ferro_orm-0.14.0/benchmarks/baselines/sqlite.json +49 -0
- ferro_orm-0.14.0/benchmarks/compare.py +98 -0
- ferro_orm-0.14.0/benchmarks/harness.py +99 -0
- ferro_orm-0.14.0/benchmarks/model.py +84 -0
- ferro_orm-0.14.0/benchmarks/run.py +270 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/crates/ferro-ddl-lowering/src/lib.rs +680 -46
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/crates/ferro-migrate/src/emit.rs +155 -83
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/crates/ferro-migrate/src/tests.rs +291 -45
- ferro_orm-0.14.0/docs/brainstorms/2026-07-02-ff-c1-codec-plan-design.md +150 -0
- ferro_orm-0.14.0/docs/brainstorms/2026-07-06-upgrade-guide-consolidation-requirements.md +222 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/examples/multiple_databases.py +9 -7
- ferro_orm-0.14.0/docs/examples/mutations.py +112 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/examples/pagination.py +11 -9
- ferro_orm-0.14.0/docs/examples/predicates.py +84 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/examples/predicates_annotated.py +12 -10
- ferro_orm-0.14.0/docs/examples/quickstart.py +112 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/examples/quickstart_annotated.py +7 -6
- ferro_orm-0.14.0/docs/examples/raw_sql.py +45 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/examples/relationships.py +37 -36
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/examples/relationships_annotated.py +18 -17
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/examples/soft_deletes.py +11 -10
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/examples/soft_deletes_annotated.py +8 -7
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/examples/testing_conftest.py +3 -2
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/examples/timestamps.py +10 -9
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/examples/timestamps_annotated.py +7 -6
- ferro_orm-0.14.0/docs/examples/transactions.py +59 -0
- ferro_orm-0.14.0/docs/pages/api/exceptions.md +57 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/api/migrations.md +1 -1
- ferro_orm-0.14.0/docs/pages/api/queries.md +9 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/concepts/architecture.md +2 -2
- ferro_orm-0.14.0/docs/pages/concepts/identity-map.md +227 -0
- ferro_orm-0.14.0/docs/pages/concepts/query-typing.md +56 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/concepts/type-safety.md +2 -2
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/faq.md +3 -1
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/getting-started/quickstart.md +3 -3
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/guide/connections.md +2 -2
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/guide/models-and-fields.md +64 -1
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/guide/mutations.md +72 -16
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/guide/queries.md +47 -40
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/howto/migrate-from-sqlalchemy.md +4 -4
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/howto/multiple-databases.md +5 -3
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/howto/pagination.md +2 -2
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/howto/soft-deletes.md +1 -1
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/howto/testing.md +7 -5
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/howto/timestamps.md +1 -1
- ferro_orm-0.14.0/docs/pages/howto/upgrade-guide.md +446 -0
- ferro_orm-0.14.0/docs/plans/2026-07-02-001-fable-fixes-roadmap.md +449 -0
- ferro_orm-0.14.0/docs/plans/2026-07-02-002-feat-ff-a-172-typed-exceptions-plan.md +37 -0
- ferro_orm-0.14.0/docs/plans/2026-07-02-003-ff-c-native-decode-design.md +105 -0
- ferro_orm-0.14.0/docs/plans/2026-07-02-004-ff-c-c2-catalog-cache-design.md +159 -0
- ferro_orm-0.14.0/docs/plans/2026-07-02-005-ff-d-identity-routing-design.md +207 -0
- ferro_orm-0.14.0/docs/plans/2026-07-02-006-ff-d-identity-routing-plan.md +1474 -0
- ferro_orm-0.14.0/docs/plans/2026-07-03-007-ff-e-registry-identity-design.md +267 -0
- ferro_orm-0.14.0/docs/plans/2026-07-03-008-ff-e-registry-identity-plan.md +1584 -0
- ferro_orm-0.14.0/docs/plans/2026-07-06-009-ff-f-query-builder-1.0-design.md +152 -0
- ferro_orm-0.14.0/docs/plans/2026-07-06-010-ff-f-query-builder-1.0-plan.md +1290 -0
- ferro_orm-0.14.0/docs/plans/2026-07-06-011-ff-g-hardening-design.md +268 -0
- ferro_orm-0.14.0/docs/plans/2026-07-06-012-ff-g-hardening-plan.md +985 -0
- ferro_orm-0.14.0/docs/plans/2026-07-06-013-ff-g2-operations-dedup-design.md +193 -0
- ferro_orm-0.14.0/docs/plans/2026-07-06-014-ff-g2-operations-dedup-plan.md +1203 -0
- ferro_orm-0.14.0/docs/plans/2026-07-06-015-docs-upgrade-guide-consolidation-plan.md +518 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/ir-first-migration-guide.md +11 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/issues/sa-pk-column-nullable-divergence.md +10 -1
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/issues/sa-vs-rust-unique-constraint-shape.md +13 -1
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/patterns/cross-emitter-ddl-parity.md +9 -8
- ferro_orm-0.14.0/docs/solutions/patterns/derived-type-and-naming-decision-table.md +161 -0
- ferro_orm-0.14.0/docs/solutions/patterns/persistence-state.md +76 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/justfile +6 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/pyproject.toml +2 -4
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/backend.rs +211 -4
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/codec.rs +185 -273
- ferro_orm-0.14.0/src/codec_plan.rs +591 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/connection.rs +48 -20
- ferro_orm-0.14.0/src/errors.rs +181 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/__init__.py +39 -6
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/_core.pyi +74 -64
- ferro_orm-0.14.0/src/ferro/exceptions.py +134 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/ir/compiler.py +42 -23
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/metaclass.py +102 -24
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/migrations/alembic.py +90 -81
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/models.py +218 -140
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/query/__init__.py +1 -2
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/query/builder.py +174 -195
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/query/nodes.py +60 -128
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/raw.py +17 -24
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/relations/__init__.py +36 -17
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/relations/descriptors.py +3 -11
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/schema_metadata.py +20 -1
- ferro_orm-0.14.0/src/ferro/state.py +217 -0
- ferro_orm-0.14.0/src/hydration.rs +286 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/introspect.rs +4 -3
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/lib.rs +20 -1
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/migrate.rs +398 -272
- ferro_orm-0.14.0/src/naming_ffi.rs +92 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/operations.rs +1425 -1186
- ferro_orm-0.14.0/src/query.rs +558 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/schema.rs +125 -72
- ferro_orm-0.14.0/src/schema_bind.rs +23 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/state.rs +270 -44
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/conftest.py +25 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/fixtures/ir_vectors/schema_phase1_fixture_models_v1.json +118 -7
- ferro_orm-0.14.0/tests/fixtures/shadow_reports/postgres.json +32 -0
- ferro_orm-0.14.0/tests/fixtures/shadow_reports/sqlite.json +32 -0
- ferro_orm-0.14.0/tests/static_fixtures/bad_predicates.py +19 -0
- ferro_orm-0.14.0/tests/static_fixtures/good_predicates.py +20 -0
- ferro_orm-0.14.0/tests/test_aggregation.py +74 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_alembic_autogenerate.py +13 -10
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_alembic_bridge.py +19 -17
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_auto_migrate.py +398 -273
- ferro_orm-0.14.0/tests/test_bulk_update.py +66 -0
- ferro_orm-0.14.0/tests/test_catalog_cache.py +244 -0
- ferro_orm-0.14.0/tests/test_codec_plan.py +81 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_composite_index.py +21 -17
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_composite_unique.py +19 -17
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_connection.py +92 -31
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_connection_redaction.py +2 -2
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_constraints.py +6 -7
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_cross_emitter_parity.py +63 -74
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_crud.py +86 -59
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_db_type_cross_emitter_parity.py +114 -6
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_db_type_integration.py +56 -52
- ferro_orm-0.14.0/tests/test_deletion.py +90 -0
- ferro_orm-0.14.0/tests/test_documentation_features.py +874 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_enum_cold_hydration.py +11 -8
- ferro_orm-0.14.0/tests/test_exception_mapping.py +160 -0
- ferro_orm-0.14.0/tests/test_exceptions.py +126 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_field_wrapper.py +17 -11
- ferro_orm-0.14.0/tests/test_fk_target_pk.py +69 -0
- ferro_orm-0.14.0/tests/test_helpers.py +136 -0
- ferro_orm-0.14.0/tests/test_hydration.py +204 -0
- ferro_orm-0.14.0/tests/test_hydration_equivalence.py +275 -0
- ferro_orm-0.14.0/tests/test_identity_memory.py +80 -0
- ferro_orm-0.14.0/tests/test_identity_refresh.py +79 -0
- ferro_orm-0.14.0/tests/test_identity_scoped_invalidation.py +86 -0
- ferro_orm-0.14.0/tests/test_identity_weakref.py +37 -0
- ferro_orm-0.14.0/tests/test_import_budget.py +38 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_metadata.py +25 -22
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_migrate_plan.py +14 -14
- ferro_orm-0.14.0/tests/test_model_identity.py +234 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_models.py +4 -1
- ferro_orm-0.14.0/tests/test_mutation_pagination_guard.py +99 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_named_connections_integration.py +52 -32
- ferro_orm-0.14.0/tests/test_naming_single_source.py +315 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_one_to_one.py +22 -20
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_query_builder.py +90 -112
- ferro_orm-0.14.0/tests/test_query_column_validation.py +151 -0
- ferro_orm-0.14.0/tests/test_query_immutability.py +101 -0
- ferro_orm-0.14.0/tests/test_query_typing.py +173 -0
- ferro_orm-0.14.0/tests/test_raw_sql.py +528 -0
- ferro_orm-0.14.0/tests/test_refresh.py +55 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_relationship_engine.py +10 -2
- ferro_orm-0.14.0/tests/test_relationship_resolution_errors.py +47 -0
- ferro_orm-0.14.0/tests/test_route_single_site.py +33 -0
- ferro_orm-0.14.0/tests/test_routing_errors.py +64 -0
- ferro_orm-0.14.0/tests/test_save_pk_edges.py +53 -0
- ferro_orm-0.14.0/tests/test_save_semantics.py +358 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_schema.py +6 -6
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_session.py +14 -18
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_shadow_fk_types.py +58 -54
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_shadow_reports.py +16 -43
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_sqlite_alembic_reconnect_hydration.py +21 -11
- ferro_orm-0.14.0/tests/test_static_contracts.py +191 -0
- ferro_orm-0.14.0/tests/test_string_search.py +54 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_structural_types.py +152 -140
- ferro_orm-0.14.0/tests/test_temporal_types.py +75 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_transactions.py +134 -117
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_typed_null_binds.py +130 -115
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_typed_save_bind.py +75 -67
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/uv.lock +28 -1
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/zensical.toml +2 -2
- ferro_orm-0.13.0/docs/examples/mutations.py +0 -49
- ferro_orm-0.13.0/docs/examples/predicates.py +0 -92
- ferro_orm-0.13.0/docs/examples/quickstart.py +0 -111
- ferro_orm-0.13.0/docs/examples/raw_sql.py +0 -43
- ferro_orm-0.13.0/docs/examples/transactions.py +0 -58
- ferro_orm-0.13.0/docs/pages/api/exceptions.md +0 -5
- ferro_orm-0.13.0/docs/pages/api/queries.md +0 -11
- ferro_orm-0.13.0/docs/pages/concepts/identity-map.md +0 -151
- ferro_orm-0.13.0/docs/pages/concepts/query-typing.md +0 -106
- ferro_orm-0.13.0/docs/pages/howto/migrating-to-v0-12-0.md +0 -153
- ferro_orm-0.13.0/src/ferro/exceptions.py +0 -17
- ferro_orm-0.13.0/src/ferro/state.py +0 -166
- ferro_orm-0.13.0/src/hydration.rs +0 -85
- ferro_orm-0.13.0/src/query.rs +0 -917
- ferro_orm-0.13.0/src/schema_bind.rs +0 -49
- ferro_orm-0.13.0/tests/fixtures/shadow_reports/postgres.json +0 -66
- ferro_orm-0.13.0/tests/fixtures/shadow_reports/sqlite.json +0 -66
- ferro_orm-0.13.0/tests/test_aggregation.py +0 -70
- ferro_orm-0.13.0/tests/test_bulk_update.py +0 -62
- ferro_orm-0.13.0/tests/test_deletion.py +0 -87
- ferro_orm-0.13.0/tests/test_deprecated_operator_inventory.py +0 -51
- ferro_orm-0.13.0/tests/test_documentation_features.py +0 -833
- ferro_orm-0.13.0/tests/test_framework_predicates.py +0 -72
- ferro_orm-0.13.0/tests/test_helpers.py +0 -129
- ferro_orm-0.13.0/tests/test_hydration.py +0 -167
- ferro_orm-0.13.0/tests/test_query_typing.py +0 -295
- ferro_orm-0.13.0/tests/test_raw_sql.py +0 -493
- ferro_orm-0.13.0/tests/test_refresh.py +0 -53
- ferro_orm-0.13.0/tests/test_static_contracts.py +0 -8
- ferro_orm-0.13.0/tests/test_string_search.py +0 -52
- ferro_orm-0.13.0/tests/test_temporal_types.py +0 -71
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/.github/PERMISSIONS.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/.github/PYPI_CHECKLIST.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/.github/PYPI_SETUP.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/.github/generated/wheels.generated.yml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/.github/pull_request_template.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/.github/workflows/packaging-smoke.yml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/.github/workflows/publish-docs.yml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/.github/workflows/publish.yml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/.github/workflows/release.yml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/.gitignore +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/.pre-commit-config.yaml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/.python-version +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/CONTRIBUTING.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/LICENSE +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/crates/ferro-ddl-lowering/Cargo.toml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/crates/ferro-migrate/Cargo.toml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/crates/ferro-migrate/src/lib.rs +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/crates/ferro-schema-ir/Cargo.toml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/crates/ferro-schema-ir/src/lib.rs +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-04-29-named-connections-role-routing-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-05-08-typed-query-predicates-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-05-13-configurable-column-storage-types-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-05-14-autogenerate-support-for-db-type-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-05-25-annotated-strenum-cold-hydration-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-06-24-ir-p8-119-wire-automigrate-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-06-25-ir-p8-120-parity-gate-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-06-26-ir-p8.5-140-shared-lowering-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-06-26-ir-p8.5-143-db-type-drop-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-06-26-ir-p8.5-144-reconcile-indexes-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-06-28-ir-p8.5-141-single-schema-ir-producer-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-06-29-ir-p8.6-146-dialect-enum-unification-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-06-29-ir-p8.6-153-create-path-ir-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-06-30-ir-p8.6-155-deferred-annotations-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-06-30-ir-p8.6-158-check-renderer-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-07-01-ir-p8.6-154-datetime-tz-coarseness-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-07-01-ir-p8.6-162-typed-save-bind-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/brainstorms/2026-07-01-ir-p8.6-165-blob-introspection-false-positive-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/api/connection.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/api/fields.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/api/model.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/api/raw-sql.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/api/relationships.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/api/transactions.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/changelog.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/concepts/backends.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/concepts/performance.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/contributing.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/getting-started/installation.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/getting-started/next-steps.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/guide/migrations.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/guide/raw-sql.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/guide/relationships.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/guide/transactions.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/index.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/roadmap.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/stylesheets/extra.css +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/pages/why-ferro.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-04-24-001-refactor-multi-db-backend-architecture-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-04-29-001-typed-null-binds-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-04-29-002-feat-named-connections-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-05-07-001-refactor-generic-model-connection-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-05-08-001-feat-typed-query-predicates-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-05-13-001-feat-configurable-column-storage-types-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-05-25-001-fix-annotated-strenum-cold-hydration-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-06-19-001-ir-first-roadmap.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-06-24-001-feat-ir-p8-119-wire-automigrate-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-06-25-001-feat-ir-p8-120-parity-gate-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-06-26-001-feat-ir-p8.5-140-shared-lowering-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-06-26-002-feat-ir-p8.5-143-db-type-drop-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-06-26-003-feat-ir-p8.5-144-reconcile-indexes-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-06-28-001-feat-ir-p8.5-141-single-schema-ir-producer-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-06-29-001-refactor-ir-p8.6-153-create-path-unification-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-06-29-002-refactor-ir-p8.6-146-dialect-enum-unification-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-06-30-001-refactor-ir-p8.6-158-check-renderer-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-06-30-002-fix-ir-p8.6-155-deferred-annotations-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-07-01-001-feat-ir-p8.6-162-typed-save-bind-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-07-01-002-fix-ir-p8.6-154-datetime-tz-coarseness-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/2026-07-01-003-fix-ir-p8.6-165-blob-introspection-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/plans/ir-first-release-checklist.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/rfc/ir-contracts-v1.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/README.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/architecture-patterns/ir-first-lowering-consolidation-audit.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/architecture-patterns/ir-first-merge-readiness-review.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/issues/pydantic-slots-missing-after-ferro-hydration.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/issues/python-3.14-deferred-annotation-typeerror-swallow.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/issues/sqlite-integer-decimal-hydrates-as-none.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/issues/sqlite-null-hydrates-as-int-zero.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/issues/typed-where-null-panics-is-null.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/patterns/configurable-column-storage-types.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/patterns/ddl-on-live-engine.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/patterns/foreign-key-index.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/patterns/index-unique-redundancy.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/patterns/ir-invariants.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/patterns/shadow-fk-columns.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/patterns/sqlite-alembic-reconnect-hydration-tests.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/docs/solutions/patterns/typed-null-binds.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/scripts/demo_queries.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/scripts/demo_queries_pre_v012.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/_annotation_utils.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/_bind_payload.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/_deprecations.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/_shadow_fk_types.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/base.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/composite_indexes.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/composite_uniques.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/fields.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/ir/__init__.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/migrations/__init__.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/py.typed +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/src/ferro/session.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/__init__.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/db_backends.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/fixtures/ir_vectors/README.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/fixtures/ir_vectors/codec_registry_core_v1.json +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/fixtures/ir_vectors/query_user_compound_v1.json +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/fixtures/ir_vectors/schema_invoice_baseline_v1.json +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_alembic_db_type.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_alembic_nullability.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_alembic_type_mapping.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_db_backends.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_db_type_typing.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_db_type_validation.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_deprecations.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_docs_examples.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_ir_vectors_contract.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_metaclass_internals.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_schema_constraints.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_schema_db_type_metadata.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.14.0}/tests/test_schema_enum_annotations.py +0 -0
|
@@ -152,6 +152,10 @@ jobs:
|
|
|
152
152
|
run: |
|
|
153
153
|
uv run maturin develop
|
|
154
154
|
|
|
155
|
+
- name: Static type gate (ty, scoped)
|
|
156
|
+
run: |
|
|
157
|
+
uv run ty check src/ferro/query tests/test_query_typing.py tests/test_static_contracts.py
|
|
158
|
+
|
|
155
159
|
- name: Run IR vector contract harness
|
|
156
160
|
run: |
|
|
157
161
|
uv run pytest -v tests/test_ir_vectors_contract.py
|
|
@@ -27,18 +27,23 @@ For a single model, every emitter must agree on:
|
|
|
27
27
|
|
|
28
28
|
1. **Table name** — already handled by `model_name.lower()`.
|
|
29
29
|
2. **Column names** — including shadow `*_id` columns from `ForeignKey`.
|
|
30
|
-
3. **Column types** —
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
`
|
|
35
|
-
|
|
36
|
-
`tests/test_db_type_cross_emitter_parity.py
|
|
30
|
+
3. **Column types** — decided by ONE function:
|
|
31
|
+
`ferro_ddl_lowering::resolve_column_storage` (explicit `db_type` token →
|
|
32
|
+
native-enum resolution → the `canonical_from_parts` cascade). The Alembic
|
|
33
|
+
bridge consumes it mechanically over FFI (`_core._resolve_storage_type`)
|
|
34
|
+
and `_db_type_to_sa_type` is only the SA *rendering* of the shared token
|
|
35
|
+
vocabulary — never a second decision table. Pinned exhaustively by
|
|
36
|
+
`tests/test_db_type_cross_emitter_parity.py` (every token and every
|
|
37
|
+
derived annotation × both dialects). See
|
|
38
|
+
`docs/solutions/patterns/derived-type-and-naming-decision-table.md`.
|
|
37
39
|
4. **Index names** — `idx_<table>_<col>` for single-column indexes,
|
|
38
40
|
`idx_<table>_<col1>_<col2>...` for composite indexes.
|
|
39
41
|
5. **Unique constraint names** — `uq_<table>_<col>` for single-column,
|
|
40
42
|
`uq_<table>_<col1>_<col2>...` for composite.
|
|
41
|
-
6. **Foreign key constraint names** —
|
|
43
|
+
6. **Foreign key constraint names** — `fk_<table>_<col>_<to_table>`, always
|
|
44
|
+
emitted (both emitters render `SchemaForeignKey.name`; single-sourced in
|
|
45
|
+
`ferro_ddl_lowering::fk_name`). See
|
|
46
|
+
`docs/solutions/patterns/derived-type-and-naming-decision-table.md`.
|
|
42
47
|
7. **Primary key constraint names** — when explicitly named.
|
|
43
48
|
8. **Check constraint names** — `ck_<table>_<col>` for the single-column
|
|
44
49
|
`db_check=True` constraint; generated by `_ck_constraint_name` (Python)
|
|
@@ -1,6 +1,106 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v0.14.0 (2026-07-07)
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- **ff-g**: Make Postgres db_check ADD CONSTRAINT idempotent (G6, #176)
|
|
9
|
+
([#181](https://github.com/syn54x/ferro-orm/pull/181),
|
|
10
|
+
[`2c0e4cf`](https://github.com/syn54x/ferro-orm/commit/2c0e4cf6c922a73454198f6aa0aa75b30eb1f0c8))
|
|
11
|
+
|
|
12
|
+
### Chores
|
|
13
|
+
|
|
14
|
+
- **benchmarks**: Pinned async benchmark suite over the rich-type hot path
|
|
15
|
+
([#196](https://github.com/syn54x/ferro-orm/pull/196),
|
|
16
|
+
[`d9a656b`](https://github.com/syn54x/ferro-orm/commit/d9a656b320bd7fb6b613f4408a796161e9badc41))
|
|
17
|
+
|
|
18
|
+
### Documentation
|
|
19
|
+
|
|
20
|
+
- Consolidate migration guides into one evergreen upgrade guide
|
|
21
|
+
([#234](https://github.com/syn54x/ferro-orm/pull/234),
|
|
22
|
+
[`15c83db`](https://github.com/syn54x/ferro-orm/commit/15c83dbc6b1c057a78dfc425d70441e50c359f5d))
|
|
23
|
+
|
|
24
|
+
- Fixes roadmap
|
|
25
|
+
([`5718d02`](https://github.com/syn54x/ferro-orm/commit/5718d02630b6c84d3ced6f8a3080adf4e7fe2383))
|
|
26
|
+
|
|
27
|
+
- **fable-fixes**: Fold #176 into Epic FF-G as sub-task G6
|
|
28
|
+
([`b966e65`](https://github.com/syn54x/ferro-orm/commit/b966e65739d7c33ed666a7fd14086a27688516dd))
|
|
29
|
+
|
|
30
|
+
- **ff-a**: A5 — docs & migration guide for the mutation-surface changes
|
|
31
|
+
([#180](https://github.com/syn54x/ferro-orm/pull/180),
|
|
32
|
+
[`ffc5476`](https://github.com/syn54x/ferro-orm/commit/ffc5476f9ba72f03a35048f283b11390f813856b))
|
|
33
|
+
|
|
34
|
+
- **ff-b**: Tick FF-B sub-task and exit-gate boxes in the fable-fixes roadmap
|
|
35
|
+
([`29f9172`](https://github.com/syn54x/ferro-orm/commit/29f91724e2b6886f8299bbb42940c804c8e88141))
|
|
36
|
+
|
|
37
|
+
### Features
|
|
38
|
+
|
|
39
|
+
- **ff-a**: Create() is a real INSERT; save() distinguishes INSERT from UPDATE (A3+A4)
|
|
40
|
+
([#179](https://github.com/syn54x/ferro-orm/pull/179),
|
|
41
|
+
[`de3ec30`](https://github.com/syn54x/ferro-orm/commit/de3ec30196202ee14c42afc5b02f163f82a68451))
|
|
42
|
+
|
|
43
|
+
- **ff-a**: Reject limit/offset on mutating queries
|
|
44
|
+
([#178](https://github.com/syn54x/ferro-orm/pull/178),
|
|
45
|
+
[`67faf42`](https://github.com/syn54x/ferro-orm/commit/67faf421d49984c5be16d2163982f13ab86cc5e4))
|
|
46
|
+
|
|
47
|
+
- **ff-a**: Typed DBAPI-shaped exception hierarchy mapped from sqlx errors
|
|
48
|
+
([#177](https://github.com/syn54x/ferro-orm/pull/177),
|
|
49
|
+
[`9c306c5`](https://github.com/syn54x/ferro-orm/commit/9c306c549e716dd4a5158d655265aa7890e63c0b))
|
|
50
|
+
|
|
51
|
+
- **ff-b**: B1 canonical derived-type & naming decision table + refusal-rail scaffolding
|
|
52
|
+
([`8c879f7`](https://github.com/syn54x/ferro-orm/commit/8c879f77833a7e5a983d53012efc8332cca5b852))
|
|
53
|
+
|
|
54
|
+
- **ff-b**: B2+B6 one derived-type decision table; native PG enums + timestamptz/time parity; delete
|
|
55
|
+
bridge mirrors
|
|
56
|
+
([`bfdc1fd`](https://github.com/syn54x/ferro-orm/commit/bfdc1fda1eceebe70ae711e38b785af88b46e11e))
|
|
57
|
+
|
|
58
|
+
- **ff-b**: B3+B4 single-source artifact naming; both emitters emit named fk_/uq_ artifacts
|
|
59
|
+
([`6b771f3`](https://github.com/syn54x/ferro-orm/commit/6b771f38933c72e8f848013a5ae93708fabf5c7a))
|
|
60
|
+
|
|
61
|
+
- **ff-c**: C1 — per-model ColumnCodec plan; delete codec.rs schema sniffing (F5)
|
|
62
|
+
([#197](https://github.com/syn54x/ferro-orm/pull/197),
|
|
63
|
+
[`0e8e572`](https://github.com/syn54x/ferro-orm/commit/0e8e57252f4cca2d2bc9fffd06d9155338056cf3))
|
|
64
|
+
|
|
65
|
+
- **ff-c**: C2 — schema-epoch catalog cache; zero catalog queries on steady-state CRUD
|
|
66
|
+
([#200](https://github.com/syn54x/ferro-orm/pull/200),
|
|
67
|
+
[`8c6d6ed`](https://github.com/syn54x/ferro-orm/commit/8c6d6edf578cf55ed7ada0d49359a7e09528da1e))
|
|
68
|
+
|
|
69
|
+
- **ff-c**: C3+C4 — native typed Postgres decode; plan-driven enum hydration replaces _fix_types
|
|
70
|
+
([#198](https://github.com/syn54x/ferro-orm/pull/198),
|
|
71
|
+
[`05a008c`](https://github.com/syn54x/ferro-orm/commit/05a008cb73f6c4dc8b2478a441c99282d07bd41d))
|
|
72
|
+
|
|
73
|
+
- **ff-d**: Session-scoped weak identity map with refresh-on-load; single-handle routing
|
|
74
|
+
([#201](https://github.com/syn54x/ferro-orm/pull/201),
|
|
75
|
+
[`a328cfc`](https://github.com/syn54x/ferro-orm/commit/a328cfcd107e2cac998ddc8af50845857ec669a5))
|
|
76
|
+
|
|
77
|
+
- **ff-e**: Registry & model identity — qualified keys, configurable tables, O(N) import
|
|
78
|
+
([#209](https://github.com/syn54x/ferro-orm/pull/209),
|
|
79
|
+
[`6391805`](https://github.com/syn54x/ferro-orm/commit/6391805c1b4ee46c0590513ac6684237c18f7430))
|
|
80
|
+
|
|
81
|
+
- **ff-f**: Query builder 1.0 shape — immutable chaining, build-time column validation, lambda-only
|
|
82
|
+
predicates, QueryIR-only Rust ([#223](https://github.com/syn54x/ferro-orm/pull/223),
|
|
83
|
+
[`eb7fece`](https://github.com/syn54x/ferro-orm/commit/eb7fece8fbc2231220ecf9111616f803f2f987eb))
|
|
84
|
+
|
|
85
|
+
- **ff-g-a**: Hardening — hydration ABI guard, transactional PG migrate, correctness edges,
|
|
86
|
+
decode-path caching ([#232](https://github.com/syn54x/ferro-orm/pull/232),
|
|
87
|
+
[`22617e1`](https://github.com/syn54x/ferro-orm/commit/22617e1cfe5e2bc9d0a3d7a1fed5acf4d8589cef))
|
|
88
|
+
|
|
89
|
+
### Refactoring
|
|
90
|
+
|
|
91
|
+
- **ff-g-b**: Operations.rs dedup — ModelMeta + Executor (G2)
|
|
92
|
+
([#233](https://github.com/syn54x/ferro-orm/pull/233),
|
|
93
|
+
[`09c2c62`](https://github.com/syn54x/ferro-orm/commit/09c2c6214af9e2f9510d39d037935c53bd2e796d))
|
|
94
|
+
|
|
95
|
+
### Testing
|
|
96
|
+
|
|
97
|
+
- **ff-b**: B5 I-1 sentinel on the full backend matrix with a full-type fixture, zero filters
|
|
98
|
+
([`1141eb4`](https://github.com/syn54x/ferro-orm/commit/1141eb437b4843303e01556a5b24ddee9d0e0279))
|
|
99
|
+
|
|
100
|
+
- **ff-b**: Force psycopg v3 driver in the Postgres sentinel regardless of URL scheme
|
|
101
|
+
([`64f9845`](https://github.com/syn54x/ferro-orm/commit/64f98454bdb5e090a41bd214de06b7670974f4eb))
|
|
102
|
+
|
|
103
|
+
|
|
4
104
|
## v0.13.0 (2026-07-02)
|
|
5
105
|
|
|
6
106
|
### Bug Fixes
|
|
@@ -8,6 +8,15 @@ version = "0.2.21"
|
|
|
8
8
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
9
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
|
10
10
|
|
|
11
|
+
[[package]]
|
|
12
|
+
name = "android_system_properties"
|
|
13
|
+
version = "0.1.5"
|
|
14
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
15
|
+
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
|
16
|
+
dependencies = [
|
|
17
|
+
"libc",
|
|
18
|
+
]
|
|
19
|
+
|
|
11
20
|
[[package]]
|
|
12
21
|
name = "atoi"
|
|
13
22
|
version = "2.0.0"
|
|
@@ -35,6 +44,19 @@ version = "1.8.3"
|
|
|
35
44
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
36
45
|
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
|
37
46
|
|
|
47
|
+
[[package]]
|
|
48
|
+
name = "bigdecimal"
|
|
49
|
+
version = "0.4.10"
|
|
50
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
51
|
+
checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695"
|
|
52
|
+
dependencies = [
|
|
53
|
+
"autocfg",
|
|
54
|
+
"libm",
|
|
55
|
+
"num-bigint",
|
|
56
|
+
"num-integer",
|
|
57
|
+
"num-traits",
|
|
58
|
+
]
|
|
59
|
+
|
|
38
60
|
[[package]]
|
|
39
61
|
name = "bitflags"
|
|
40
62
|
version = "2.13.0"
|
|
@@ -73,9 +95,9 @@ checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
|
|
|
73
95
|
|
|
74
96
|
[[package]]
|
|
75
97
|
name = "cc"
|
|
76
|
-
version = "1.2.
|
|
98
|
+
version = "1.2.66"
|
|
77
99
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
78
|
-
checksum = "
|
|
100
|
+
checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996"
|
|
79
101
|
dependencies = [
|
|
80
102
|
"find-msvc-tools",
|
|
81
103
|
"shlex",
|
|
@@ -87,6 +109,17 @@ version = "1.0.4"
|
|
|
87
109
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
88
110
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
89
111
|
|
|
112
|
+
[[package]]
|
|
113
|
+
name = "chrono"
|
|
114
|
+
version = "0.4.45"
|
|
115
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
116
|
+
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
|
|
117
|
+
dependencies = [
|
|
118
|
+
"iana-time-zone",
|
|
119
|
+
"num-traits",
|
|
120
|
+
"windows-link",
|
|
121
|
+
]
|
|
122
|
+
|
|
90
123
|
[[package]]
|
|
91
124
|
name = "concurrent-queue"
|
|
92
125
|
version = "2.5.0"
|
|
@@ -102,6 +135,12 @@ version = "0.9.6"
|
|
|
102
135
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
103
136
|
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
|
104
137
|
|
|
138
|
+
[[package]]
|
|
139
|
+
name = "core-foundation-sys"
|
|
140
|
+
version = "0.8.7"
|
|
141
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
142
|
+
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
|
143
|
+
|
|
105
144
|
[[package]]
|
|
106
145
|
name = "cpufeatures"
|
|
107
146
|
version = "0.2.17"
|
|
@@ -128,18 +167,18 @@ checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
|
|
|
128
167
|
|
|
129
168
|
[[package]]
|
|
130
169
|
name = "crossbeam-queue"
|
|
131
|
-
version = "0.3.
|
|
170
|
+
version = "0.3.13"
|
|
132
171
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
133
|
-
checksum = "
|
|
172
|
+
checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26"
|
|
134
173
|
dependencies = [
|
|
135
174
|
"crossbeam-utils",
|
|
136
175
|
]
|
|
137
176
|
|
|
138
177
|
[[package]]
|
|
139
178
|
name = "crossbeam-utils"
|
|
140
|
-
version = "0.8.
|
|
179
|
+
version = "0.8.22"
|
|
141
180
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
142
|
-
checksum = "
|
|
181
|
+
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
|
|
143
182
|
|
|
144
183
|
[[package]]
|
|
145
184
|
name = "crypto-common"
|
|
@@ -288,8 +327,9 @@ dependencies = [
|
|
|
288
327
|
|
|
289
328
|
[[package]]
|
|
290
329
|
name = "ferro"
|
|
291
|
-
version = "0.
|
|
330
|
+
version = "0.14.0"
|
|
292
331
|
dependencies = [
|
|
332
|
+
"chrono",
|
|
293
333
|
"dashmap",
|
|
294
334
|
"ferro-ddl-lowering",
|
|
295
335
|
"ferro-migrate",
|
|
@@ -578,6 +618,30 @@ dependencies = [
|
|
|
578
618
|
"windows-sys 0.61.2",
|
|
579
619
|
]
|
|
580
620
|
|
|
621
|
+
[[package]]
|
|
622
|
+
name = "iana-time-zone"
|
|
623
|
+
version = "0.1.65"
|
|
624
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
625
|
+
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
|
626
|
+
dependencies = [
|
|
627
|
+
"android_system_properties",
|
|
628
|
+
"core-foundation-sys",
|
|
629
|
+
"iana-time-zone-haiku",
|
|
630
|
+
"js-sys",
|
|
631
|
+
"log",
|
|
632
|
+
"wasm-bindgen",
|
|
633
|
+
"windows-core",
|
|
634
|
+
]
|
|
635
|
+
|
|
636
|
+
[[package]]
|
|
637
|
+
name = "iana-time-zone-haiku"
|
|
638
|
+
version = "0.1.2"
|
|
639
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
640
|
+
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
|
641
|
+
dependencies = [
|
|
642
|
+
"cc",
|
|
643
|
+
]
|
|
644
|
+
|
|
581
645
|
[[package]]
|
|
582
646
|
name = "icu_collections"
|
|
583
647
|
version = "2.2.0"
|
|
@@ -835,6 +899,16 @@ dependencies = [
|
|
|
835
899
|
"windows-sys 0.61.2",
|
|
836
900
|
]
|
|
837
901
|
|
|
902
|
+
[[package]]
|
|
903
|
+
name = "num-bigint"
|
|
904
|
+
version = "0.4.8"
|
|
905
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
906
|
+
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
|
|
907
|
+
dependencies = [
|
|
908
|
+
"num-integer",
|
|
909
|
+
"num-traits",
|
|
910
|
+
]
|
|
911
|
+
|
|
838
912
|
[[package]]
|
|
839
913
|
name = "num-bigint-dig"
|
|
840
914
|
version = "0.8.6"
|
|
@@ -1210,9 +1284,9 @@ dependencies = [
|
|
|
1210
1284
|
|
|
1211
1285
|
[[package]]
|
|
1212
1286
|
name = "rustversion"
|
|
1213
|
-
version = "1.0.
|
|
1287
|
+
version = "1.0.23"
|
|
1214
1288
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1215
|
-
checksum = "
|
|
1289
|
+
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
|
1216
1290
|
|
|
1217
1291
|
[[package]]
|
|
1218
1292
|
name = "ryu"
|
|
@@ -1418,7 +1492,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
1418
1492
|
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
|
|
1419
1493
|
dependencies = [
|
|
1420
1494
|
"base64",
|
|
1495
|
+
"bigdecimal",
|
|
1421
1496
|
"bytes",
|
|
1497
|
+
"chrono",
|
|
1422
1498
|
"crc",
|
|
1423
1499
|
"crossbeam-queue",
|
|
1424
1500
|
"either",
|
|
@@ -1494,9 +1570,11 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
|
|
|
1494
1570
|
dependencies = [
|
|
1495
1571
|
"atoi",
|
|
1496
1572
|
"base64",
|
|
1573
|
+
"bigdecimal",
|
|
1497
1574
|
"bitflags",
|
|
1498
1575
|
"byteorder",
|
|
1499
1576
|
"bytes",
|
|
1577
|
+
"chrono",
|
|
1500
1578
|
"crc",
|
|
1501
1579
|
"digest",
|
|
1502
1580
|
"dotenvy",
|
|
@@ -1537,8 +1615,10 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
|
|
|
1537
1615
|
dependencies = [
|
|
1538
1616
|
"atoi",
|
|
1539
1617
|
"base64",
|
|
1618
|
+
"bigdecimal",
|
|
1540
1619
|
"bitflags",
|
|
1541
1620
|
"byteorder",
|
|
1621
|
+
"chrono",
|
|
1542
1622
|
"crc",
|
|
1543
1623
|
"dotenvy",
|
|
1544
1624
|
"etcetera",
|
|
@@ -1553,6 +1633,7 @@ dependencies = [
|
|
|
1553
1633
|
"log",
|
|
1554
1634
|
"md-5",
|
|
1555
1635
|
"memchr",
|
|
1636
|
+
"num-bigint",
|
|
1556
1637
|
"once_cell",
|
|
1557
1638
|
"rand",
|
|
1558
1639
|
"serde",
|
|
@@ -1574,6 +1655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
1574
1655
|
checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
|
|
1575
1656
|
dependencies = [
|
|
1576
1657
|
"atoi",
|
|
1658
|
+
"chrono",
|
|
1577
1659
|
"flume",
|
|
1578
1660
|
"futures-channel",
|
|
1579
1661
|
"futures-core",
|
|
@@ -1930,12 +2012,65 @@ dependencies = [
|
|
|
1930
2012
|
"wasite",
|
|
1931
2013
|
]
|
|
1932
2014
|
|
|
2015
|
+
[[package]]
|
|
2016
|
+
name = "windows-core"
|
|
2017
|
+
version = "0.62.2"
|
|
2018
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2019
|
+
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
|
2020
|
+
dependencies = [
|
|
2021
|
+
"windows-implement",
|
|
2022
|
+
"windows-interface",
|
|
2023
|
+
"windows-link",
|
|
2024
|
+
"windows-result",
|
|
2025
|
+
"windows-strings",
|
|
2026
|
+
]
|
|
2027
|
+
|
|
2028
|
+
[[package]]
|
|
2029
|
+
name = "windows-implement"
|
|
2030
|
+
version = "0.60.2"
|
|
2031
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2032
|
+
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
|
2033
|
+
dependencies = [
|
|
2034
|
+
"proc-macro2",
|
|
2035
|
+
"quote",
|
|
2036
|
+
"syn",
|
|
2037
|
+
]
|
|
2038
|
+
|
|
2039
|
+
[[package]]
|
|
2040
|
+
name = "windows-interface"
|
|
2041
|
+
version = "0.59.3"
|
|
2042
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2043
|
+
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
|
2044
|
+
dependencies = [
|
|
2045
|
+
"proc-macro2",
|
|
2046
|
+
"quote",
|
|
2047
|
+
"syn",
|
|
2048
|
+
]
|
|
2049
|
+
|
|
1933
2050
|
[[package]]
|
|
1934
2051
|
name = "windows-link"
|
|
1935
2052
|
version = "0.2.1"
|
|
1936
2053
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1937
2054
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
1938
2055
|
|
|
2056
|
+
[[package]]
|
|
2057
|
+
name = "windows-result"
|
|
2058
|
+
version = "0.4.1"
|
|
2059
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2060
|
+
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
|
2061
|
+
dependencies = [
|
|
2062
|
+
"windows-link",
|
|
2063
|
+
]
|
|
2064
|
+
|
|
2065
|
+
[[package]]
|
|
2066
|
+
name = "windows-strings"
|
|
2067
|
+
version = "0.5.1"
|
|
2068
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2069
|
+
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
|
2070
|
+
dependencies = [
|
|
2071
|
+
"windows-link",
|
|
2072
|
+
]
|
|
2073
|
+
|
|
1939
2074
|
[[package]]
|
|
1940
2075
|
name = "windows-sys"
|
|
1941
2076
|
version = "0.48.0"
|
|
@@ -2115,18 +2250,18 @@ dependencies = [
|
|
|
2115
2250
|
|
|
2116
2251
|
[[package]]
|
|
2117
2252
|
name = "zerocopy"
|
|
2118
|
-
version = "0.8.
|
|
2253
|
+
version = "0.8.53"
|
|
2119
2254
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2120
|
-
checksum = "
|
|
2255
|
+
checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1"
|
|
2121
2256
|
dependencies = [
|
|
2122
2257
|
"zerocopy-derive",
|
|
2123
2258
|
]
|
|
2124
2259
|
|
|
2125
2260
|
[[package]]
|
|
2126
2261
|
name = "zerocopy-derive"
|
|
2127
|
-
version = "0.8.
|
|
2262
|
+
version = "0.8.53"
|
|
2128
2263
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2129
|
-
checksum = "
|
|
2264
|
+
checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71"
|
|
2130
2265
|
dependencies = [
|
|
2131
2266
|
"proc-macro2",
|
|
2132
2267
|
"quote",
|
|
@@ -3,7 +3,7 @@ members = ["crates/ferro-schema-ir", "crates/ferro-ddl-lowering", "crates/ferro-
|
|
|
3
3
|
|
|
4
4
|
[package]
|
|
5
5
|
name = "ferro"
|
|
6
|
-
version = "0.
|
|
6
|
+
version = "0.14.0"
|
|
7
7
|
edition = "2024"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
|
|
@@ -30,11 +30,18 @@ serde = { version = "1.0", features = ["derive"] }
|
|
|
30
30
|
serde_json = "1.0"
|
|
31
31
|
once_cell = "1.21"
|
|
32
32
|
dashmap = "6.1"
|
|
33
|
-
|
|
33
|
+
# chrono/bigdecimal/json enable native typed decode of Postgres temporal,
|
|
34
|
+
# numeric, and json wire values (FF-C C3). bigdecimal (not rust_decimal)
|
|
35
|
+
# because PG `numeric` is arbitrary-precision: rust_decimal caps at 28
|
|
36
|
+
# significant digits and would silently corrupt wider values.
|
|
37
|
+
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "postgres", "any", "tls-rustls-ring-webpki", "uuid", "chrono", "bigdecimal", "json"]}
|
|
34
38
|
sea-query = { version = "0.32", features = ["with-uuid"] }
|
|
35
39
|
tokio = { version = "1.49", features = ["full"] }
|
|
36
40
|
pyo3-async-runtimes = { version = "0.27", features = ["tokio-runtime"] }
|
|
37
41
|
uuid = { version = "1.11", features = ["v4"] }
|
|
42
|
+
# Same crate sqlx re-exports; direct dep only for formatting helpers
|
|
43
|
+
# (SecondsFormat) sqlx does not re-export.
|
|
44
|
+
chrono = { version = "0.4", default-features = false, features = ["std"] }
|
|
38
45
|
ferro-schema-ir = { path = "crates/ferro-schema-ir" }
|
|
39
46
|
ferro-ddl-lowering = { path = "crates/ferro-ddl-lowering" }
|
|
40
47
|
ferro-migrate = { path = "crates/ferro-migrate" }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ferro-orm
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.0
|
|
4
4
|
Requires-Dist: pydantic>=2.0
|
|
5
5
|
Requires-Dist: alembic>=1.18.1 ; extra == 'alembic'
|
|
6
6
|
Requires-Dist: sqlalchemy>=2.0.46 ; extra == 'alembic'
|
|
@@ -54,7 +54,7 @@ pip install ferro-orm
|
|
|
54
54
|
pip install "ferro-orm[alembic]"
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
Ferro currently supports SQLite and PostgreSQL. Register multiple named connections with `connect(..., name="...")` when a process needs more than one database — see the [connections guide](https://
|
|
57
|
+
Ferro currently supports SQLite and PostgreSQL. Register multiple named connections with `connect(..., name="...")` when a process needs more than one database — see the [connections guide](https://ferro-orm.x54.sh/guide/connections/).
|
|
58
58
|
|
|
59
59
|
## Quick Start
|
|
60
60
|
|
|
@@ -41,7 +41,7 @@ pip install ferro-orm
|
|
|
41
41
|
pip install "ferro-orm[alembic]"
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Ferro currently supports SQLite and PostgreSQL. Register multiple named connections with `connect(..., name="...")` when a process needs more than one database — see the [connections guide](https://
|
|
44
|
+
Ferro currently supports SQLite and PostgreSQL. Register multiple named connections with `connect(..., name="...")` when a process needs more than one database — see the [connections guide](https://ferro-orm.x54.sh/guide/connections/).
|
|
45
45
|
|
|
46
46
|
## Quick Start
|
|
47
47
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Ferro hot-path benchmarks
|
|
2
|
+
|
|
3
|
+
A small, pinned, repeatable suite that measures Ferro's Python-facing async hot
|
|
4
|
+
path — **single save**, **bulk save**, and a **filtered fetch of ~10k rows** — on
|
|
5
|
+
both backends, over a rich-type fixture model. It exists so the before/after of
|
|
6
|
+
Epic **FF-C** (per-model codec plans, catalog cache, native Postgres decode, enum
|
|
7
|
+
hydration) is quantifiable and future hot-path regressions are visible.
|
|
8
|
+
|
|
9
|
+
It is **purely additive**: it imports the public Ferro API and changes no
|
|
10
|
+
production code. It is **not** part of `just test`, so it never slows the
|
|
11
|
+
functional matrix.
|
|
12
|
+
|
|
13
|
+
## What it measures
|
|
14
|
+
|
|
15
|
+
Four cases per backend, over `BenchRecord` (see `model.py`):
|
|
16
|
+
|
|
17
|
+
| benchmark | operation | rows |
|
|
18
|
+
| ------------------------- | ----------------------------------------------------- | ---- |
|
|
19
|
+
| `single_save` | `record.save()` — one INSERT | 1 |
|
|
20
|
+
| `bulk_save` | `BenchRecord.bulk_create([...])` — one bulk INSERT | 10k |
|
|
21
|
+
| `fetch_10k_identity_on` | `BenchRecord.where(...).all()`, identity map **on** | 10k |
|
|
22
|
+
| `fetch_10k_identity_off` | `BenchRecord.where(...).all()`, identity map **off** | 10k |
|
|
23
|
+
|
|
24
|
+
Only **wall-clock** is recorded (median / min / mean / stdev / p95, in ms).
|
|
25
|
+
|
|
26
|
+
### Why the rich-type fixture is load-bearing
|
|
27
|
+
|
|
28
|
+
A plain `int`/`str` model would show *nothing* from FF-C. Every `BenchRecord`
|
|
29
|
+
field maps to a concrete cost C1–C4 removes, so the suite makes that work
|
|
30
|
+
visible:
|
|
31
|
+
|
|
32
|
+
- `created_at: datetime` (`db_type="timestamptz"`) → **C3** native typed decode
|
|
33
|
+
(today a `CAST(... AS text)` projection on Postgres — finding **F8**).
|
|
34
|
+
- `balance: Decimal` → **C1/F5** per-value codec (the `pattern_looks_decimal`
|
|
35
|
+
heuristic C1 deletes).
|
|
36
|
+
- `external_id: uuid.UUID` (`db_type="uuid"`) → **C3** typed decode / UUID casing.
|
|
37
|
+
- `status: Status` (`str, Enum`, no `db_type`) → native Postgres enum, **C4**
|
|
38
|
+
enum hydration (removes the `_fix_types` Python post-pass).
|
|
39
|
+
- `attributes: dict[str, str]` → JSON codec.
|
|
40
|
+
- `name`, `score` → the plain-typed baseline columns.
|
|
41
|
+
|
|
42
|
+
Catalog round-trips (**F4**) are incurred by all of the above during
|
|
43
|
+
save/fetch/migrate, so they are exercised implicitly.
|
|
44
|
+
|
|
45
|
+
## Running
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
just bench # SQLite always; Postgres if FERRO_POSTGRES_URL is set
|
|
49
|
+
# or directly:
|
|
50
|
+
uv run python -m benchmarks.run
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Postgres is included automatically when a URL is configured (via
|
|
54
|
+
`FERRO_POSTGRES_URL` or a repo-root `.env`, reusing `tests/db_backends.py`); it is
|
|
55
|
+
skipped with a printed note otherwise.
|
|
56
|
+
|
|
57
|
+
Useful flags: `--rows N`, `--iters N`, `--warmup N`, `--seed N`,
|
|
58
|
+
`--backends sqlite,postgres`, `--out DIR`.
|
|
59
|
+
|
|
60
|
+
Each run writes `baselines/<backend>.json` (per-backend, keyed by benchmark name)
|
|
61
|
+
and prints a summary table.
|
|
62
|
+
|
|
63
|
+
## Capturing a before/after around a change
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# 1. Baseline the current code into a scratch dir
|
|
67
|
+
uv run python -m benchmarks.run --out /tmp/before
|
|
68
|
+
|
|
69
|
+
# 2. Make your hot-path change, rebuild the Rust core
|
|
70
|
+
uv run maturin develop
|
|
71
|
+
|
|
72
|
+
# 3. Re-run into a second dir
|
|
73
|
+
uv run python -m benchmarks.run --out /tmp/after
|
|
74
|
+
|
|
75
|
+
# 4. Diff the medians (per backend)
|
|
76
|
+
uv run python -m benchmarks.compare /tmp/before/sqlite.json /tmp/after/sqlite.json
|
|
77
|
+
uv run python -m benchmarks.compare /tmp/before/postgres.json /tmp/after/postgres.json
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`compare` prints each benchmark's `old → new` median with **Δms** and **Δ%**.
|
|
81
|
+
Negative % is a speed-up (green); positive is a regression (red).
|
|
82
|
+
|
|
83
|
+
## Reading the deltas
|
|
84
|
+
|
|
85
|
+
- Compare **same backend, same machine** only. Absolute numbers vary wildly by
|
|
86
|
+
backend and hardware; the deliverable is a stable *per-backend delta*, never a
|
|
87
|
+
SQLite-vs-Postgres comparison.
|
|
88
|
+
- The checked-in `baselines/*.json` were captured on a developer machine and are
|
|
89
|
+
a reference point, not a CI gate. Re-capture your own baseline before measuring
|
|
90
|
+
a change so both sides come from the same box.
|
|
91
|
+
- Watch the **median** for the headline and **stdev/p95** for noise. If stdev is
|
|
92
|
+
a large fraction of the median, raise `--warmup`/`--iters` until it settles
|
|
93
|
+
before trusting a delta.
|
|
94
|
+
|
|
95
|
+
## Stability knobs
|
|
96
|
+
|
|
97
|
+
Fixed row counts, deterministic seed data (`random.Random(seed)`), discarded
|
|
98
|
+
warmup iterations, a fresh database per case (temp SQLite file / dropped-and-
|
|
99
|
+
recreated PG schema), a pinned pool (`max_connections=5`), and a single
|
|
100
|
+
persistent event loop across all cases. The fetch cases are measured with the
|
|
101
|
+
identity map both on and off because that bookkeeping is real hot-path cost the
|
|
102
|
+
FF-C rewrite interacts with.
|
|
103
|
+
|
|
104
|
+
## FF-C epic results (recorded 2026-07-02)
|
|
105
|
+
|
|
106
|
+
Full before/after of Epic FF-C — **before** is the pre-C1 baseline capture
|
|
107
|
+
(`d9a656b`), **after** is post-C2 (catalog cache; C1 codec plan, C3 native
|
|
108
|
+
decode, and C4 enum hydration included). Same machine, release-profile `.so`
|
|
109
|
+
(sha-verified), medians in ms.
|
|
110
|
+
|
|
111
|
+
**Postgres**
|
|
112
|
+
|
|
113
|
+
| benchmark | pre-C1 | post-C2 | Δ % | C2-only Δ % (vs post-C3) |
|
|
114
|
+
| ------------------------ | ------ | ------- | ------ | ------------------------ |
|
|
115
|
+
| single_save | 1.708 | 0.399 | −76.6% | −66.9% |
|
|
116
|
+
| bulk_save | 389.5 | 92.1 | −76.4% | −11.8% |
|
|
117
|
+
| fetch_10000_identity_on | 189.5 | 16.1 | −91.5% | −16.9% |
|
|
118
|
+
| fetch_10000_identity_off | 285.9 | 67.3 | −76.5% | +0.0% |
|
|
119
|
+
|
|
120
|
+
**SQLite**
|
|
121
|
+
|
|
122
|
+
| benchmark | pre-C1 | post-C2 | Δ % | C2-only Δ % (vs post-C3) |
|
|
123
|
+
| ------------------------ | ------ | ------- | ------ | ------------------------ |
|
|
124
|
+
| single_save | 0.352 | 0.277 | −21.2% | −9.8% |
|
|
125
|
+
| bulk_save | 221.5 | 54.6 | −75.4% | −0.1% |
|
|
126
|
+
| fetch_10000_identity_on | 175.5 | 34.5 | −80.4% | +1.0% |
|
|
127
|
+
| fetch_10000_identity_off | 271.2 | 80.9 | −70.2% | +0.4% |
|
|
128
|
+
|
|
129
|
+
The C2 column isolates the catalog cache: on Postgres the save path drops
|
|
130
|
+
sharply (3 catalog round-trips per op eliminated — F4; single_save
|
|
131
|
+
1.21 → 0.40 ms), while SQLite deltas are noise, as required (the cache is a
|
|
132
|
+
no-op off Postgres). The checked-in `baselines/*.json` are the post-C2
|
|
133
|
+
capture.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Ferro hot-path benchmark suite (Epic FF-C, sub-task C5).
|
|
2
|
+
|
|
3
|
+
A small, pinned, repeatable suite measuring the Python-facing async hot path —
|
|
4
|
+
single save, bulk save, and a filtered fetch of ~10k rows — on both backends
|
|
5
|
+
(SQLite always; Postgres when ``FERRO_POSTGRES_URL`` is configured), over a
|
|
6
|
+
rich-type fixture model that exercises the type-decision path FF-C's C1–C4
|
|
7
|
+
rewrite touches.
|
|
8
|
+
|
|
9
|
+
The suite is purely additive: it imports the public Ferro API and never changes
|
|
10
|
+
production code. Run it with ``just bench`` (see ``benchmarks/README.md``).
|
|
11
|
+
"""
|