0.18.1
0.18.1 Summary
Fixed
- Composite Primary Key Deduplication Bug in Joins by @igorbenav
- Fixed critical bug where models with composite primary keys were incorrectly deduplicated in one-to-many relationships
- Enhanced primary key handling to support full composite key comparison instead of just the first key
- Comprehensive refactoring of join helper functions for better maintainability and performance
- Added extensive test coverage for composite primary key scenarios
Improved
- Join Processing Architecture by @igorbenav
- Major refactoring of
fastcrud/crud/helper.pywith improved code organization - Enhanced column extraction and labeling logic for better join performance
- Improved error handling and validation for model attributes
- Better separation of concerns in join configuration processing
- Major refactoring of
Breaking Changes
Details
Composite Primary Key Deduplication Bug Fix
Description
Fixed a critical bug where models with composite primary keys were incorrectly deduplicated during one-to-many join operations. The issue occurred because the deduplication logic only considered the first primary key field, causing records with the same first key but different second keys to be treated as duplicates.
Problem Solved
Previously, when working with models that had composite primary keys, the join functionality would incorrectly deduplicate records:
# Models with composite primary keys
class ChildModel(Base):
__tablename__ = "children"
child_id = Column(Integer, primary_key=True) # First PK
version = Column(Integer, primary_key=True) # Second PK - was ignored
name = Column(String(50))
parent_id = Column(Integer, ForeignKey("parents.id"))
# This data would be incorrectly deduplicated:
# Child 1: (child_id=1, version=1) ✓ Returned
# Child 2: (child_id=1, version=2) ✗ Incorrectly filtered out as duplicateThe bug manifested in scenarios such as:
- Versioned Records: Same entity with different version numbers
- Inventory Systems: Same product in different batches or locations
- Multi-Tenant Applications: Same logical record across different tenants
- Time-Series Data: Same entity at different time periods
Solution Implemented
- Enhanced Primary Key Detection: Updated
_get_primary_keyfunction to handle composite keys properly - Full Composite Key Comparison: Deduplication now considers all primary key fields, not just the first one
- Improved Helper Functions: Added new utility functions for better primary key handling:
_get_primary_key_names(): Extracts all primary key field names_create_composite_key(): Creates proper composite key representations
- Comprehensive Validation: Enhanced model validation to ensure proper table attributes
Technical Changes
New Helper Functions:
def _get_primary_key_names(model: ModelType) -> List[str]:
"""Extract all primary key field names from a model."""
def _create_composite_key(instance: Any, pk_names: List[str]) -> tuple:
"""Create a composite key tuple from an instance."""Enhanced Column Processing:
def _build_column_label(temp_prefix: str, prefix: Optional[str], field_name: str) -> str:
"""Build column labels with appropriate prefixes for SQLAlchemy selection."""
def _extract_schema_columns(
model_or_alias: Union[ModelType, AliasedClass],
schema: type[SelectSchemaType],
mapper,
prefix: Optional[str],
use_temporary_prefix: bool,
temp_prefix: str,
) -> list[Any]:
"""Extract specific columns from SQLAlchemy model based on Pydantic schema."""Test Coverage
Added comprehensive test suites covering multiple composite key scenarios:
Scenario 1: Versioned Child Records
# Parent with children having same child_id but different versions
child1 = ChildModel(child_id=1, version=1, name="Child 1-v1")
child2 = ChildModel(child_id=1, version=2, name="Child 1-v2")
# Both should be returned, not deduplicatedScenario 2: Inventory Management
# Warehouse with same product in different batches
item1 = InventoryModel(product_id=100, batch_id=1, quantity=50)
item2 = InventoryModel(product_id=100, batch_id=2, quantity=30)
# Both should be returned as separate inventory itemsImpact and Benefits
- Data Integrity: Ensures all records with composite primary keys are properly returned
- Accuracy: Eliminates silent data loss in one-to-many relationships with composite keys
- Performance: Improved join processing with better helper function architecture
- Reliability: Comprehensive test coverage prevents regression of this critical bug
- Compatibility: Maintains full backward compatibility while fixing the underlying issue
Before vs After
Before (Buggy Behavior):
# Query returns only 1 child instead of 2
result = await crud_parent.get_multi_joined(...)
assert len(result["data"][0]["children"]) == 1 # ❌ Missing second versionAfter (Fixed Behavior):
# Query correctly returns both children
result = await crud_parent.get_multi_joined(...)
assert len(result["data"][0]["children"]) == 2 # ✅ Both versions present
assert {child["version"] for child in result["data"][0]["children"]} == {1, 2}Join Processing Architecture Improvements
Description
Significant refactoring and improvement of the join processing architecture in fastcrud/crud/helper.py, enhancing code organization, maintainability, and performance.
Refactoring Highlights
- Code Organization: Restructured helper functions for better logical grouping and readability
- Function Extraction: Broke down large functions into smaller, focused utilities
- Enhanced Validation: Improved model and attribute validation throughout the join process
- Better Error Handling: More robust error handling with clearer error messages
- Performance Optimizations: Streamlined column extraction and labeling processes
New Utility Functions
Model Validation:
def _validate_model_has_table(model: Union[ModelType, AliasedClass]) -> None:
"""Validates that a model has a __table__ attribute."""Column Processing:
def _build_column_label(temp_prefix: str, prefix: Optional[str], field_name: str) -> str:
"""Builds column labels with appropriate prefixes for SQLAlchemy column selection."""
def _extract_schema_columns(...) -> list[Any]:
"""Extracts specific columns from SQLAlchemy model based on Pydantic schema field names."""Benefits
- Maintainability: Cleaner, more modular code structure
- Testability: Smaller functions are easier to test in isolation
- Debugging: Better error messages and validation help with troubleshooting
- Performance: Optimized column processing reduces query overhead
- Extensibility: Modular architecture makes future enhancements easier
File Changes Summary
- fastcrud/crud/helper.py: Major refactoring with 664 lines changed (+478 -186)
- fastcrud/endpoint/helper.py: Enhanced with new primary key utility functions
- Test Coverage: Added comprehensive test suites for composite key scenarios in both SQLAlchemy and SQLModel variants
What's Changed
- fix composite key bug in joins, refactor helper by @igorbenav in #272
- change version by @igorbenav in #273
Full Changelog: v0.18.0...v0.18.1