@@ -492,8 +492,7 @@ final class AppDatabaseUpdater: AppDatabaseUpdaterProtocol {
492492 }
493493
494494 /// Persists areas and their entity relationships for a server.
495- /// Uses a single asyncWrite transaction for batching, replaces existing rows, and deletes stale ones.
496- /// For simplicity and speed, we upsert via `save(onConflict: .replace)`; deeper diffing can be added if needed.
495+ /// Deletes all existing areas for the server and inserts fresh data in a single transaction.
497496 private func saveAreasToDatabase(
498497 areas: [ HAAreasRegistryResponse ] ,
499498 areasAndEntities: [ String : Set < String > ] ,
@@ -519,33 +518,18 @@ final class AppDatabaseUpdater: AppDatabaseUpdaterProtocol {
519518 return result
520519 } . value
521520
522- // Nothing to persist; keep going (delete pass below might still remove stale rows).
523- if appAreas. isEmpty {
524- Current . Log. verbose ( " No areas to save for server \( serverId) " )
525- }
526-
527521 do {
528522 let dbTimer = ProfilingTimer ( " Step 5.2.2: Database write transaction " )
529523 try await withCheckedThrowingContinuation { ( continuation: CheckedContinuation < Void , Error > ) in
530- // Database writes are already async and happen on GRDB's background queue
531524 Current . database ( ) . asyncWrite { db in
532- let existingAreaIds = try AppArea
525+ // Delete all existing areas for this server
526+ try AppArea
533527 . filter ( Column ( DatabaseTables . AppArea. serverId. rawValue) == serverId)
534- . fetchAll ( db) . map ( \ . id )
528+ . deleteAll ( db)
535529
536- // Insert or update new areas
530+ // Insert fresh areas
537531 for area in appAreas {
538- try area. save ( db, onConflict: . replace)
539- }
540-
541- // Delete areas that no longer exist
542- let newAreaIds = areas. map { " \( serverId) - \( $0. areaId) " }
543- let areaIdsToDelete = existingAreaIds. filter { !newAreaIds. contains ( $0) }
544-
545- if !areaIdsToDelete. isEmpty {
546- try AppArea
547- . filter ( areaIdsToDelete. contains ( Column ( DatabaseTables . AppArea. id. rawValue) ) )
548- . deleteAll ( db)
532+ try area. insert ( db)
549533 }
550534 } completion: { _, result in
551535 switch result {
@@ -573,8 +557,8 @@ final class AppDatabaseUpdater: AppDatabaseUpdaterProtocol {
573557 }
574558 }
575559
576- /// Persists the entity registry list-for-display for a server with batched writes and stale deletions .
577- /// Builds the payload with a streaming loop to reduce intermediate allocations vs filter+map .
560+ /// Persists the entity registry list-for-display for a server.
561+ /// Deletes all existing records for the server and inserts fresh data in a single transaction .
578562 private func saveEntityRegistryListForDisplay( _ response: EntityRegistryListForDisplay , serverId: String ) async {
579563 // Check for cancellation before starting database work
580564 guard !isUpdateCancelled( ) else {
@@ -606,28 +590,15 @@ final class AppDatabaseUpdater: AppDatabaseUpdaterProtocol {
606590 continuation. resume ( throwing: CancellationError ( ) )
607591 return
608592 }
609- // Note: we batch entities into memory before this write. This is a trade-off for simpler, atomic
610- // updates;
611- // if memory usage becomes an issue for very large datasets, consider a streaming or chunked approach.
612593 Current . database ( ) . asyncWrite { [ entitiesListForDisplay] db in
613- // Get existing IDs for this server
614- let existingIds = try AppEntityRegistryListForDisplay
594+ // Delete all existing records for this server
595+ try AppEntityRegistryListForDisplay
615596 . filter ( Column ( DatabaseTables . AppEntityRegistryListForDisplay. serverId. rawValue) == serverId)
616- . fetchAll ( db)
617- . map ( \. id)
597+ . deleteAll ( db)
618598
619- // Insert or update new records
599+ // Insert fresh records
620600 for record in entitiesListForDisplay {
621- try record. save ( db, onConflict: . replace)
622- }
623-
624- // Delete records that no longer exist
625- let newIds = entitiesListForDisplay. map ( \. id)
626- let idsToDelete = existingIds. filter { !newIds. contains ( $0) }
627-
628- if !idsToDelete. isEmpty {
629- try AppEntityRegistryListForDisplay
630- . deleteAll ( db, keys: idsToDelete)
601+ try record. insert ( db)
631602 }
632603 } completion: { _, result in
633604 switch result {
@@ -654,7 +625,8 @@ final class AppDatabaseUpdater: AppDatabaseUpdaterProtocol {
654625 }
655626 }
656627
657- /// Persists the entity registry for a server using a single transaction and differential deletes.
628+ /// Persists the entity registry for a server.
629+ /// Deletes all existing records for the server and inserts fresh data in a single transaction.
658630 private func saveEntityRegistry( _ registryEntries: [ EntityRegistryEntry ] , serverId: String ) async {
659631 // If cancelled before touching the DB, bail out early to avoid unnecessary work.
660632 guard !isUpdateCancelled( ) else {
@@ -677,24 +649,14 @@ final class AppDatabaseUpdater: AppDatabaseUpdaterProtocol {
677649 return
678650 }
679651 Current . database ( ) . asyncWrite { db in
680- // Get existing unique IDs for this server
681- let existingIds = try AppEntityRegistry
652+ // Delete all existing registry entries for this server
653+ try AppEntityRegistry
682654 . filter ( Column ( DatabaseTables . EntityRegistry. serverId. rawValue) == serverId)
683- . fetchAll ( db)
684- . map ( \. id)
655+ . deleteAll ( db)
685656
686- // Insert or update new registry entries
657+ // Insert fresh registry entries
687658 for registry in appEntityRegistries {
688- try registry. save ( db, onConflict: . replace)
689- }
690-
691- // Delete registry entries that no longer exist
692- let newIds = appEntityRegistries. map ( \. id)
693- let idsToDelete = existingIds. filter { !newIds. contains ( $0) }
694-
695- if !idsToDelete. isEmpty {
696- try AppEntityRegistry
697- . deleteAll ( db, keys: idsToDelete)
659+ try registry. insert ( db)
698660 }
699661 } completion: { _, result in
700662 switch result {
@@ -724,7 +686,8 @@ final class AppDatabaseUpdater: AppDatabaseUpdaterProtocol {
724686 }
725687 }
726688
727- /// Persists the device registry for a server using a single transaction and differential deletes.
689+ /// Persists the device registry for a server.
690+ /// Deletes all existing records for the server and inserts fresh data in a single transaction.
728691 private func saveDeviceRegistry( _ registryEntries: [ DeviceRegistryEntry ] , serverId: String ) async {
729692 // If cancelled before touching the DB, bail out early to avoid unnecessary work.
730693 guard !isUpdateCancelled( ) else {
@@ -747,24 +710,14 @@ final class AppDatabaseUpdater: AppDatabaseUpdaterProtocol {
747710 return
748711 }
749712 Current . database ( ) . asyncWrite { db in
750- // Get existing device IDs for this server
751- let existingIds = try AppDeviceRegistry
713+ // Delete all existing device registry entries for this server
714+ try AppDeviceRegistry
752715 . filter ( Column ( DatabaseTables . DeviceRegistry. serverId. rawValue) == serverId)
753- . fetchAll ( db)
754- . map ( \. id)
716+ . deleteAll ( db)
755717
756- // Insert or update new registry entries
718+ // Insert fresh registry entries
757719 for registry in appDeviceRegistries {
758- try registry. save ( db, onConflict: . replace)
759- }
760-
761- // Delete registry entries that no longer exist
762- let newIds = appDeviceRegistries. map ( \. id)
763- let idsToDelete = existingIds. filter { !newIds. contains ( $0) }
764-
765- if !idsToDelete. isEmpty {
766- try AppDeviceRegistry
767- . deleteAll ( db, keys: idsToDelete)
720+ try registry. insert ( db)
768721 }
769722 } completion: { _, result in
770723 switch result {
0 commit comments