@@ -828,139 +828,227 @@ async fn db_sync_apply_internal(
828828 } ;
829829
830830 let local_students = load_students ( & local_conn) . await ?;
831+ let remote_students = load_students ( & remote_conn) . await ?;
831832 let local_reasons = load_reasons ( & local_conn) . await ?;
833+ let remote_reasons = load_reasons ( & remote_conn) . await ?;
832834 let local_tags = load_tags ( & local_conn) . await ?;
835+ let remote_tags = load_tags ( & remote_conn) . await ?;
833836 let local_events = load_events ( & local_conn) . await ?;
837+ let remote_events = load_events ( & remote_conn) . await ?;
834838 let local_reward_settings = load_reward_settings ( & local_conn) . await ?;
839+ let remote_reward_settings = load_reward_settings ( & remote_conn) . await ?;
835840 let local_reward_redemptions = load_reward_redemptions ( & local_conn) . await ?;
841+ let remote_reward_redemptions = load_reward_redemptions ( & remote_conn) . await ?;
836842 let local_pairs = load_student_tag_pairs ( & local_conn) . await ?;
837843 let remote_pairs = load_student_tag_pairs ( & remote_conn) . await ?;
838844
839- let ( preferred, target) = if strategy == ConflictStrategy :: KeepLocal {
840- ( & local_conn, & remote_conn)
841- } else {
842- ( & remote_conn, & local_conn)
843- } ;
844-
845845 let mut synced_records = 0usize ;
846846 let mut resolved_conflicts = 0usize ;
847847
848- for student in local_students. values ( ) {
849- if upsert_student ( & remote_conn, student) . await ? {
850- synced_records += 1 ;
851- }
852- }
853- let remote_students_after = load_students ( & remote_conn) . await ?;
854- for student in remote_students_after. values ( ) {
855- if upsert_student ( & local_conn, student) . await ? {
856- synced_records += 1 ;
857- }
858- }
859- for reason in local_reasons. values ( ) {
860- if upsert_reason ( & remote_conn, reason) . await ? {
861- synced_records += 1 ;
862- }
863- }
864- let remote_reasons_after = load_reasons ( & remote_conn) . await ?;
865- for reason in remote_reasons_after. values ( ) {
866- if upsert_reason ( & local_conn, reason) . await ? {
867- synced_records += 1 ;
868- }
869- }
870- for tag in local_tags. values ( ) {
871- if upsert_tag ( & remote_conn, tag) . await ? {
872- synced_records += 1 ;
873- }
874- }
875- let remote_tags_after = load_tags ( & remote_conn) . await ?;
876- for tag in remote_tags_after. values ( ) {
877- if upsert_tag ( & local_conn, tag) . await ? {
878- synced_records += 1 ;
879- }
880- }
881- for event in local_events. values ( ) {
882- if upsert_event ( & remote_conn, event) . await ? {
883- synced_records += 1 ;
848+ // Deterministic per-key merge:
849+ // 1) local_only => remote
850+ // 2) remote_only => local
851+ // 3) conflict => resolve by strategy (winner -> loser)
852+ let student_keys: std:: collections:: HashSet < String > = local_students
853+ . keys ( )
854+ . chain ( remote_students. keys ( ) )
855+ . cloned ( )
856+ . collect ( ) ;
857+ for key in student_keys {
858+ match ( local_students. get ( & key) , remote_students. get ( & key) ) {
859+ ( Some ( local) , Some ( remote) ) if local != remote => {
860+ let changed = if strategy == ConflictStrategy :: KeepLocal {
861+ upsert_student ( & remote_conn, local) . await ?
862+ } else {
863+ upsert_student ( & local_conn, remote) . await ?
864+ } ;
865+ if changed {
866+ resolved_conflicts += 1 ;
867+ }
868+ }
869+ ( Some ( local) , None ) => {
870+ if upsert_student ( & remote_conn, local) . await ? {
871+ synced_records += 1 ;
872+ }
873+ }
874+ ( None , Some ( remote) ) => {
875+ if upsert_student ( & local_conn, remote) . await ? {
876+ synced_records += 1 ;
877+ }
878+ }
879+ _ => { }
884880 }
885881 }
886- let remote_events_after = load_events ( & remote_conn) . await ?;
887- for event in remote_events_after. values ( ) {
888- if upsert_event ( & local_conn, event) . await ? {
889- synced_records += 1 ;
882+
883+ let reason_keys: std:: collections:: HashSet < String > = local_reasons
884+ . keys ( )
885+ . chain ( remote_reasons. keys ( ) )
886+ . cloned ( )
887+ . collect ( ) ;
888+ for key in reason_keys {
889+ match ( local_reasons. get ( & key) , remote_reasons. get ( & key) ) {
890+ ( Some ( local) , Some ( remote) ) if local != remote => {
891+ let changed = if strategy == ConflictStrategy :: KeepLocal {
892+ upsert_reason ( & remote_conn, local) . await ?
893+ } else {
894+ upsert_reason ( & local_conn, remote) . await ?
895+ } ;
896+ if changed {
897+ resolved_conflicts += 1 ;
898+ }
899+ }
900+ ( Some ( local) , None ) => {
901+ if upsert_reason ( & remote_conn, local) . await ? {
902+ synced_records += 1 ;
903+ }
904+ }
905+ ( None , Some ( remote) ) => {
906+ if upsert_reason ( & local_conn, remote) . await ? {
907+ synced_records += 1 ;
908+ }
909+ }
910+ _ => { }
890911 }
891912 }
892- for reward in local_reward_settings. values ( ) {
893- if upsert_reward_setting ( & remote_conn, reward) . await ? {
894- synced_records += 1 ;
913+
914+ let tag_keys: std:: collections:: HashSet < String > = local_tags
915+ . keys ( )
916+ . chain ( remote_tags. keys ( ) )
917+ . cloned ( )
918+ . collect ( ) ;
919+ for key in tag_keys {
920+ match ( local_tags. get ( & key) , remote_tags. get ( & key) ) {
921+ ( Some ( local) , Some ( remote) ) if local != remote => {
922+ let changed = if strategy == ConflictStrategy :: KeepLocal {
923+ upsert_tag ( & remote_conn, local) . await ?
924+ } else {
925+ upsert_tag ( & local_conn, remote) . await ?
926+ } ;
927+ if changed {
928+ resolved_conflicts += 1 ;
929+ }
930+ }
931+ ( Some ( local) , None ) => {
932+ if upsert_tag ( & remote_conn, local) . await ? {
933+ synced_records += 1 ;
934+ }
935+ }
936+ ( None , Some ( remote) ) => {
937+ if upsert_tag ( & local_conn, remote) . await ? {
938+ synced_records += 1 ;
939+ }
940+ }
941+ _ => { }
895942 }
896943 }
897- let remote_reward_settings_after = load_reward_settings ( & remote_conn) . await ?;
898- for reward in remote_reward_settings_after. values ( ) {
899- if upsert_reward_setting ( & local_conn, reward) . await ? {
900- synced_records += 1 ;
944+
945+ let event_keys: std:: collections:: HashSet < String > = local_events
946+ . keys ( )
947+ . chain ( remote_events. keys ( ) )
948+ . cloned ( )
949+ . collect ( ) ;
950+ for key in event_keys {
951+ match ( local_events. get ( & key) , remote_events. get ( & key) ) {
952+ ( Some ( local) , Some ( remote) ) if local != remote => {
953+ let changed = if strategy == ConflictStrategy :: KeepLocal {
954+ upsert_event ( & remote_conn, local) . await ?
955+ } else {
956+ upsert_event ( & local_conn, remote) . await ?
957+ } ;
958+ if changed {
959+ resolved_conflicts += 1 ;
960+ }
961+ }
962+ ( Some ( local) , None ) => {
963+ if upsert_event ( & remote_conn, local) . await ? {
964+ synced_records += 1 ;
965+ }
966+ }
967+ ( None , Some ( remote) ) => {
968+ if upsert_event ( & local_conn, remote) . await ? {
969+ synced_records += 1 ;
970+ }
971+ }
972+ _ => { }
901973 }
902974 }
903- for redemption in local_reward_redemptions. values ( ) {
904- if upsert_reward_redemption ( & remote_conn, redemption) . await ? {
905- synced_records += 1 ;
975+
976+ let reward_setting_keys: std:: collections:: HashSet < String > = local_reward_settings
977+ . keys ( )
978+ . chain ( remote_reward_settings. keys ( ) )
979+ . cloned ( )
980+ . collect ( ) ;
981+ for key in reward_setting_keys {
982+ match (
983+ local_reward_settings. get ( & key) ,
984+ remote_reward_settings. get ( & key) ,
985+ ) {
986+ ( Some ( local) , Some ( remote) ) if local != remote => {
987+ let changed = if strategy == ConflictStrategy :: KeepLocal {
988+ upsert_reward_setting ( & remote_conn, local) . await ?
989+ } else {
990+ upsert_reward_setting ( & local_conn, remote) . await ?
991+ } ;
992+ if changed {
993+ resolved_conflicts += 1 ;
994+ }
995+ }
996+ ( Some ( local) , None ) => {
997+ if upsert_reward_setting ( & remote_conn, local) . await ? {
998+ synced_records += 1 ;
999+ }
1000+ }
1001+ ( None , Some ( remote) ) => {
1002+ if upsert_reward_setting ( & local_conn, remote) . await ? {
1003+ synced_records += 1 ;
1004+ }
1005+ }
1006+ _ => { }
9061007 }
9071008 }
908- let remote_reward_redemptions_after = load_reward_redemptions ( & remote_conn) . await ?;
909- for redemption in remote_reward_redemptions_after. values ( ) {
910- if upsert_reward_redemption ( & local_conn, redemption) . await ? {
911- synced_records += 1 ;
1009+
1010+ let redemption_keys: std:: collections:: HashSet < String > = local_reward_redemptions
1011+ . keys ( )
1012+ . chain ( remote_reward_redemptions. keys ( ) )
1013+ . cloned ( )
1014+ . collect ( ) ;
1015+ for key in redemption_keys {
1016+ match (
1017+ local_reward_redemptions. get ( & key) ,
1018+ remote_reward_redemptions. get ( & key) ,
1019+ ) {
1020+ ( Some ( local) , Some ( remote) ) if local != remote => {
1021+ let changed = if strategy == ConflictStrategy :: KeepLocal {
1022+ upsert_reward_redemption ( & remote_conn, local) . await ?
1023+ } else {
1024+ upsert_reward_redemption ( & local_conn, remote) . await ?
1025+ } ;
1026+ if changed {
1027+ resolved_conflicts += 1 ;
1028+ }
1029+ }
1030+ ( Some ( local) , None ) => {
1031+ if upsert_reward_redemption ( & remote_conn, local) . await ? {
1032+ synced_records += 1 ;
1033+ }
1034+ }
1035+ ( None , Some ( remote) ) => {
1036+ if upsert_reward_redemption ( & local_conn, remote) . await ? {
1037+ synced_records += 1 ;
1038+ }
1039+ }
1040+ _ => { }
9121041 }
9131042 }
9141043
915- for pair in local_pairs. union ( & remote_pairs) {
916- if ensure_student_tag_pair ( & local_conn, pair) . await ? {
917- synced_records += 1 ;
918- }
1044+ for pair in local_pairs. difference ( & remote_pairs) {
9191045 if ensure_student_tag_pair ( & remote_conn, pair) . await ? {
9201046 synced_records += 1 ;
9211047 }
9221048 }
923-
924- let preferred_students = load_students ( preferred) . await ?;
925- for student in preferred_students. values ( ) {
926- if upsert_student ( target, student) . await ? {
927- resolved_conflicts += 1 ;
928- }
929- }
930- let preferred_reasons = load_reasons ( preferred) . await ?;
931- for reason in preferred_reasons. values ( ) {
932- if upsert_reason ( target, reason) . await ? {
933- resolved_conflicts += 1 ;
934- }
935- }
936- let preferred_tags = load_tags ( preferred) . await ?;
937- for tag in preferred_tags. values ( ) {
938- if upsert_tag ( target, tag) . await ? {
939- resolved_conflicts += 1 ;
940- }
941- }
942- let preferred_events = load_events ( preferred) . await ?;
943- for event in preferred_events. values ( ) {
944- if upsert_event ( target, event) . await ? {
945- resolved_conflicts += 1 ;
946- }
947- }
948- let preferred_reward_settings = load_reward_settings ( preferred) . await ?;
949- for reward in preferred_reward_settings. values ( ) {
950- if upsert_reward_setting ( target, reward) . await ? {
951- resolved_conflicts += 1 ;
952- }
953- }
954- let preferred_reward_redemptions = load_reward_redemptions ( preferred) . await ?;
955- for redemption in preferred_reward_redemptions. values ( ) {
956- if upsert_reward_redemption ( target, redemption) . await ? {
957- resolved_conflicts += 1 ;
958- }
959- }
960- let preferred_pairs = load_student_tag_pairs ( preferred) . await ?;
961- for pair in & preferred_pairs {
962- if ensure_student_tag_pair ( target, pair) . await ? {
963- resolved_conflicts += 1 ;
1049+ for pair in remote_pairs. difference ( & local_pairs) {
1050+ if ensure_student_tag_pair ( & local_conn, pair) . await ? {
1051+ synced_records += 1 ;
9641052 }
9651053 }
9661054
0 commit comments