@@ -396,3 +396,171 @@ class Test(ModelBase, table_name="test_upsert", primary_key="id"):
396396 assert result [0 ].created_at == "2023-01-04" # Should be updated
397397 assert result [0 ].name == "Alice Force"
398398 assert result [0 ].count == 20
399+
400+
401+ async def test_replace_multiple_with_replace_ignore (conn ):
402+ """Test replace_ignore ColumnInfo attribute."""
403+
404+ @dataclass (order = True )
405+ class Test (ModelBase , table_name = "test" , primary_key = "id" ):
406+ id : int
407+ name : str
408+ count : int
409+ # metadata field should be ignored during comparison
410+ metadata : Annotated [str , ColumnInfo (replace_ignore = True )]
411+
412+ await conn .execute (* Test .create_table_sql ())
413+
414+ # Insert initial data
415+ data = [
416+ Test (1 , "Alice" , 10 , "meta1" ),
417+ Test (2 , "Bob" , 20 , "meta2" ),
418+ Test (3 , "Charlie" , 30 , "meta3" ),
419+ ]
420+ await Test .insert_multiple (conn , data )
421+
422+ # Replace with same data but different metadata
423+ # Since metadata is ignored, no updates should happen
424+ new_data = [
425+ Test (1 , "Alice" , 10 , "different_meta" ),
426+ Test (2 , "Bob" , 20 , "different_meta" ),
427+ Test (3 , "Charlie" , 30 , "different_meta" ),
428+ ]
429+ c , u , d = await Test .replace_multiple (conn , new_data , where = [])
430+ assert not c # No creates
431+ assert not u # No updates because metadata is ignored
432+ assert not d # No deletes
433+
434+ # Verify original metadata is preserved
435+ result = await Test .select (conn , order_by = "id" )
436+ assert result [0 ].metadata == "meta1"
437+ assert result [1 ].metadata == "meta2"
438+ assert result [2 ].metadata == "meta3"
439+
440+ # Now change a non-ignored field - should trigger update
441+ # The metadata will be updated too (it's only ignored for comparison)
442+ new_data [0 ] = Test (1 , "Alice Updated" , 10 , "still_different" )
443+ c , u , d = await Test .replace_multiple (conn , new_data , where = [])
444+ assert not c
445+ assert len (u ) == 1 # Should update because name changed
446+ assert not d
447+
448+ # Verify update happened - metadata gets updated along with other fields
449+ result = await Test .select (conn , where = sql ("id = 1" ))
450+ assert result [0 ].name == "Alice Updated"
451+ assert result [0 ].metadata == "still_different" # Updated along with name
452+
453+
454+ async def test_replace_multiple_replace_ignore_with_force_update (conn ):
455+ """Test that force_update overrides replace_ignore."""
456+
457+ @dataclass (order = True )
458+ class Test (ModelBase , table_name = "test" , primary_key = "id" ):
459+ id : int
460+ name : str
461+ metadata : Annotated [str , ColumnInfo (replace_ignore = True )]
462+
463+ await conn .execute (* Test .create_table_sql ())
464+
465+ # Insert initial data
466+ data = [Test (1 , "Alice" , "meta1" ), Test (2 , "Bob" , "meta2" )]
467+ await Test .insert_multiple (conn , data )
468+
469+ # Replace with different metadata, using force_update
470+ new_data = [Test (1 , "Alice" , "new_meta1" ), Test (2 , "Bob" , "new_meta2" )]
471+ c , u , d = await Test .replace_multiple (
472+ conn , new_data , where = [], force_update = {"metadata" }
473+ )
474+ assert not c
475+ assert len (u ) == 2 # Should update because force_update overrides replace_ignore
476+ assert not d
477+
478+ # Verify metadata was updated
479+ result = await Test .select (conn , order_by = "id" )
480+ assert result [0 ].metadata == "new_meta1"
481+ assert result [1 ].metadata == "new_meta2"
482+
483+
484+ async def test_replace_multiple_replace_ignore_with_insert_only (conn ):
485+ """Test interaction between replace_ignore and insert_only."""
486+
487+ @dataclass (order = True )
488+ class Test (ModelBase , table_name = "test" , primary_key = "id" ):
489+ id : int
490+ name : str
491+ # Both replace_ignore and insert_only
492+ created_at : Annotated [str , ColumnInfo (replace_ignore = True , insert_only = True )]
493+ # Only replace_ignore
494+ metadata : Annotated [str , ColumnInfo (replace_ignore = True )]
495+
496+ await conn .execute (* Test .create_table_sql ())
497+
498+ # Insert initial data
499+ data = [Test (1 , "Alice" , "2023-01-01" , "meta1" )]
500+ await Test .insert_multiple (conn , data )
501+
502+ # Try to replace with different created_at and metadata
503+ new_data = [Test (1 , "Alice" , "2023-01-02" , "meta2" )]
504+ c , u , d = await Test .replace_multiple (conn , new_data , where = [])
505+ assert not c
506+ assert not u # No update because both fields are ignored
507+ assert not d
508+
509+ # Verify original values preserved
510+ result = await Test .select (conn )
511+ assert result [0 ].created_at == "2023-01-01"
512+ assert result [0 ].metadata == "meta1"
513+
514+ # Change name - should trigger update
515+ # created_at is preserved (insert_only), metadata is updated (only ignored for comparison)
516+ new_data = [Test (1 , "Alice Updated" , "2023-01-03" , "meta3" )]
517+ c , u , d = await Test .replace_multiple (conn , new_data , where = [])
518+ assert not c
519+ assert len (u ) == 1
520+ assert not d
521+
522+ # Verify update happened
523+ result = await Test .select (conn )
524+ assert result [0 ].name == "Alice Updated"
525+ assert result [0 ].created_at == "2023-01-01" # Preserved (insert_only)
526+ assert result [0 ].metadata == "meta3" # Updated (only ignored for comparison)
527+
528+
529+ async def test_replace_multiple_replace_ignore_partial_match (conn ):
530+ """Test replace_ignore when only some records match."""
531+
532+ @dataclass (order = True )
533+ class Test (ModelBase , table_name = "test" , primary_key = "id" ):
534+ id : int
535+ category : str
536+ value : int
537+ metadata : Annotated [str , ColumnInfo (replace_ignore = True )]
538+
539+ await conn .execute (* Test .create_table_sql ())
540+
541+ # Insert data with different categories
542+ data = [
543+ Test (1 , "A" , 10 , "meta1" ),
544+ Test (2 , "A" , 20 , "meta2" ),
545+ Test (3 , "B" , 30 , "meta3" ),
546+ ]
547+ await Test .insert_multiple (conn , data )
548+
549+ # Replace only category A with different metadata
550+ new_data = [
551+ Test (1 , "A" , 10 , "new_meta1" ),
552+ Test (2 , "A" , 25 , "new_meta2" ), # value changed
553+ ]
554+ c , u , d = await Test .replace_multiple (conn , new_data , where = sql ("category = 'A'" ))
555+ assert not c
556+ assert len (u ) == 1 # Only id=2 should update (value changed)
557+ assert not d # Category B record not affected by where clause
558+
559+ # Verify results
560+ result = await Test .select (conn , order_by = "id" )
561+ assert len (result ) == 3
562+ assert result [0 ].metadata == "meta1" # Unchanged (no update happened)
563+ assert result [0 ].value == 10
564+ assert result [1 ].metadata == "new_meta2" # Updated along with value
565+ assert result [1 ].value == 25 # Updated
566+ assert result [2 ] == data [2 ] # Category B unchanged
0 commit comments