From 3f7ab1d55aa6f26e71b711e774f60b289a1367dd Mon Sep 17 00:00:00 2001 From: Paolo Musolino Date: Mon, 7 Oct 2024 16:28:35 +0200 Subject: [PATCH 01/13] - Add a set of metadata IDs for quick lookup in `handleProductCustomFields` - Update comment to clarify the removal process of custom fields in `handleProductCustomFields` --- Yosemite/Yosemite/Stores/ProductStore.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Yosemite/Yosemite/Stores/ProductStore.swift b/Yosemite/Yosemite/Stores/ProductStore.swift index db603fb07d4..5a0a4557f20 100644 --- a/Yosemite/Yosemite/Stores/ProductStore.swift +++ b/Yosemite/Yosemite/Stores/ProductStore.swift @@ -1162,6 +1162,9 @@ extension ProductStore { /// Updates, inserts, or prunes the provided `storageProduct`'s custom fields using the provided `readOnlyProduct`'s custom fields /// private func handleProductCustomFields(_ readOnlyProduct: Networking.Product, _ storageProduct: Storage.Product, _ storage: StorageType) { + // Create a set of metadata IDs from the read-only product for quick lookup + let readOnlyMetadataIDs = Set(readOnlyProduct.customFields.map { $0.metadataID }) + // Upsert the `customFields` from the `readOnlyProduct` readOnlyProduct.customFields.forEach { readOnlyCustomField in if let existingStorageMetaData = storage.loadProductMetaData(siteID: readOnlyProduct.siteID, @@ -1175,9 +1178,9 @@ extension ProductStore { } } - // Now, remove any objects that exist in `storageProduct.customFields` but not in `readOnlyProduct.customFields` + // Remove any objects that exist in `storageProduct.customFields` but not in `readOnlyProduct.customFields` storageProduct.customFields?.forEach { storageCustomField in - if readOnlyProduct.customFields.first(where: { $0.metadataID == storageCustomField.metadataID } ) == nil { + if !readOnlyMetadataIDs.contains(storageCustomField.metadataID) { storageProduct.removeFromCustomFields(storageCustomField) storage.deleteObject(storageCustomField) } From 8a2e6f2ebc154440b0bc2d2988b67c33542c227a Mon Sep 17 00:00:00 2001 From: Paolo Musolino Date: Mon, 7 Oct 2024 16:34:40 +0200 Subject: [PATCH 02/13] - Adds batch writing process for multiple custom fields in `ProductStore.swift` - Creates a new array to store new metadata objects before adding to `storageProduct` --- Yosemite/Yosemite/Stores/ProductStore.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Yosemite/Yosemite/Stores/ProductStore.swift b/Yosemite/Yosemite/Stores/ProductStore.swift index 5a0a4557f20..994eb28b477 100644 --- a/Yosemite/Yosemite/Stores/ProductStore.swift +++ b/Yosemite/Yosemite/Stores/ProductStore.swift @@ -1165,6 +1165,8 @@ extension ProductStore { // Create a set of metadata IDs from the read-only product for quick lookup let readOnlyMetadataIDs = Set(readOnlyProduct.customFields.map { $0.metadataID }) + var newStorageMetaDataArray: [Storage.MetaData] = [] + // Upsert the `customFields` from the `readOnlyProduct` readOnlyProduct.customFields.forEach { readOnlyCustomField in if let existingStorageMetaData = storage.loadProductMetaData(siteID: readOnlyProduct.siteID, @@ -1174,10 +1176,15 @@ extension ProductStore { } else { let newStorageMetaData = storage.insertNewObject(ofType: Storage.MetaData.self) newStorageMetaData.update(with: readOnlyCustomField) - storageProduct.addToCustomFields(newStorageMetaData) + newStorageMetaDataArray.append(newStorageMetaData) } } + // Batch writing process of multiple custom fields + if !newStorageMetaDataArray.isEmpty { + storageProduct.addToCustomFields(NSSet(array: newStorageMetaDataArray)) + } + // Remove any objects that exist in `storageProduct.customFields` but not in `readOnlyProduct.customFields` storageProduct.customFields?.forEach { storageCustomField in if !readOnlyMetadataIDs.contains(storageCustomField.metadataID) { From ea1ea139b41b6dddd3aa846db546ce9fec269a97 Mon Sep 17 00:00:00 2001 From: Paolo Musolino Date: Mon, 7 Oct 2024 16:43:54 +0200 Subject: [PATCH 03/13] - Remove redundant removal of storage custom fields not present in read-only product - Update logic to only handle new custom fields from read-only product --- Yosemite/Yosemite/Stores/ProductStore.swift | 23 ++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/Yosemite/Yosemite/Stores/ProductStore.swift b/Yosemite/Yosemite/Stores/ProductStore.swift index 994eb28b477..0fba98190ca 100644 --- a/Yosemite/Yosemite/Stores/ProductStore.swift +++ b/Yosemite/Yosemite/Stores/ProductStore.swift @@ -1165,33 +1165,28 @@ extension ProductStore { // Create a set of metadata IDs from the read-only product for quick lookup let readOnlyMetadataIDs = Set(readOnlyProduct.customFields.map { $0.metadataID }) + // Remove any objects that exist in `storageProduct.customFields` but not in `readOnlyProduct.customFields` + storageProduct.customFields?.forEach { storageCustomField in + if !readOnlyMetadataIDs.contains(storageCustomField.metadataID) { + storageProduct.removeFromCustomFields(storageCustomField) + storage.deleteObject(storageCustomField) + } + } + var newStorageMetaDataArray: [Storage.MetaData] = [] // Upsert the `customFields` from the `readOnlyProduct` readOnlyProduct.customFields.forEach { readOnlyCustomField in - if let existingStorageMetaData = storage.loadProductMetaData(siteID: readOnlyProduct.siteID, - productID: storageProduct.productID, - metadataID: readOnlyCustomField.metadataID) { - existingStorageMetaData.update(with: readOnlyCustomField) - } else { let newStorageMetaData = storage.insertNewObject(ofType: Storage.MetaData.self) newStorageMetaData.update(with: readOnlyCustomField) newStorageMetaDataArray.append(newStorageMetaData) - } + } // Batch writing process of multiple custom fields if !newStorageMetaDataArray.isEmpty { storageProduct.addToCustomFields(NSSet(array: newStorageMetaDataArray)) } - - // Remove any objects that exist in `storageProduct.customFields` but not in `readOnlyProduct.customFields` - storageProduct.customFields?.forEach { storageCustomField in - if !readOnlyMetadataIDs.contains(storageCustomField.metadataID) { - storageProduct.removeFromCustomFields(storageCustomField) - storage.deleteObject(storageCustomField) - } - } } } From da14c253b47e6bce13275d573a56959d353805ed Mon Sep 17 00:00:00 2001 From: Paolo Musolino Date: Mon, 7 Oct 2024 16:47:32 +0200 Subject: [PATCH 04/13] - Improve indentation and readability in ProductStore.swift - Streamline logic for removing custom fields that are not in read-only product --- Yosemite/Yosemite/Stores/ProductStore.swift | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Yosemite/Yosemite/Stores/ProductStore.swift b/Yosemite/Yosemite/Stores/ProductStore.swift index 0fba98190ca..343d63ba810 100644 --- a/Yosemite/Yosemite/Stores/ProductStore.swift +++ b/Yosemite/Yosemite/Stores/ProductStore.swift @@ -1166,21 +1166,21 @@ extension ProductStore { let readOnlyMetadataIDs = Set(readOnlyProduct.customFields.map { $0.metadataID }) // Remove any objects that exist in `storageProduct.customFields` but not in `readOnlyProduct.customFields` - storageProduct.customFields?.forEach { storageCustomField in - if !readOnlyMetadataIDs.contains(storageCustomField.metadataID) { - storageProduct.removeFromCustomFields(storageCustomField) - storage.deleteObject(storageCustomField) - } - } - + storageProduct.customFields?.forEach { storageCustomField in + if !readOnlyMetadataIDs.contains(storageCustomField.metadataID) { + storageProduct.removeFromCustomFields(storageCustomField) + storage.deleteObject(storageCustomField) + } + } + var newStorageMetaDataArray: [Storage.MetaData] = [] // Upsert the `customFields` from the `readOnlyProduct` readOnlyProduct.customFields.forEach { readOnlyCustomField in - let newStorageMetaData = storage.insertNewObject(ofType: Storage.MetaData.self) - newStorageMetaData.update(with: readOnlyCustomField) - newStorageMetaDataArray.append(newStorageMetaData) - + let newStorageMetaData = storage.insertNewObject(ofType: Storage.MetaData.self) + newStorageMetaData.update(with: readOnlyCustomField) + newStorageMetaDataArray.append(newStorageMetaData) + } // Batch writing process of multiple custom fields From 5d765a9f475f27b3a780157a1ff9a161734e3f4b Mon Sep 17 00:00:00 2001 From: Paolo Musolino Date: Mon, 7 Oct 2024 18:06:22 +0200 Subject: [PATCH 05/13] Refactor Order Custom Fields Handling in `OrdersUpsertUseCase` - Optimize custom fields handling in OrdersUpsertUseCase.swift - Replace forEach with a set lookup to improve performance - Introduce batch writing for new custom fields to reduce database calls --- .../Stores/Order/OrdersUpsertUseCase.swift | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift b/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift index 94a1a5463e1..4a49e969abf 100644 --- a/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift +++ b/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift @@ -304,26 +304,30 @@ struct OrdersUpsertUseCase { /// Updates, inserts, or prunes the provided `storageOrder`'s custom fields using the provided `readOnlyOrder`'s custom fields /// private func handleOrderCustomFields(_ readOnlyOrder: Networking.Order, _ storageOrder: Storage.Order, _ storage: StorageType) { - // Upsert the `customFields` from the `readOnlyOrder` - readOnlyOrder.customFields.forEach { readOnlyCustomField in - if let existingStorageMetaData = storage.loadOrderMetaData(siteID: readOnlyOrder.siteID, - orderID: storageOrder.orderID, - metadataID: readOnlyCustomField.metadataID) { - existingStorageMetaData.update(with: readOnlyCustomField) - } else { - let newStorageMetaData = storage.insertNewObject(ofType: Storage.MetaData.self) - newStorageMetaData.update(with: readOnlyCustomField) - storageOrder.addToCustomFields(newStorageMetaData) - } - } + // Create a set of metadata IDs from the read-only order for quick lookup + let readOnlyMetadataIDs = Set(readOnlyOrder.customFields.map { $0.metadataID }) - // Now, remove any objects that exist in `storageOrder.customFields` but not in `readOnlyOrder.customFields` + // Remove any objects that exist in `storageOrder.customFields` but not in `readOnlyOrder.customFields` storageOrder.customFields?.forEach { storageCustomField in - if readOnlyOrder.customFields.first(where: { $0.metadataID == storageCustomField.metadataID } ) == nil { + if !readOnlyMetadataIDs.contains(storageCustomField.metadataID) { storageOrder.removeFromCustomFields(storageCustomField) storage.deleteObject(storageCustomField) } } + + var newStorageMetaDataArray: [Storage.MetaData] = [] + + // Upsert the `customFields` from the `readOnlyOrder` + readOnlyOrder.customFields.forEach { readOnlyCustomField in + let newStorageMetaData = storage.insertNewObject(ofType: Storage.MetaData.self) + newStorageMetaData.update(with: readOnlyCustomField) + newStorageMetaDataArray.append(newStorageMetaData) + } + + // Batch writing process of multiple custom fields + if !newStorageMetaDataArray.isEmpty { + storageOrder.addToCustomFields(NSSet(array: newStorageMetaDataArray)) + } } /// Updates, inserts, or prunes the provided `storageOrder`'s applied gift cards using the provided `readOnlyOrder`'s custom fields From 0bfa18aa7f84ef0a65ab5371988c89b700dba0fa Mon Sep 17 00:00:00 2001 From: Huong Do Date: Mon, 7 Oct 2024 23:40:21 +0700 Subject: [PATCH 06/13] Remove all stored custom fields for --- .../Stores/Order/OrdersUpsertUseCase.swift | 14 +++++--------- Yosemite/Yosemite/Stores/ProductStore.swift | 15 +++++---------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift b/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift index 4a49e969abf..29495d17f22 100644 --- a/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift +++ b/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift @@ -309,19 +309,15 @@ struct OrdersUpsertUseCase { // Remove any objects that exist in `storageOrder.customFields` but not in `readOnlyOrder.customFields` storageOrder.customFields?.forEach { storageCustomField in - if !readOnlyMetadataIDs.contains(storageCustomField.metadataID) { - storageOrder.removeFromCustomFields(storageCustomField) - storage.deleteObject(storageCustomField) - } + storageOrder.removeFromCustomFields(storageCustomField) + storage.deleteObject(storageCustomField) } - var newStorageMetaDataArray: [Storage.MetaData] = [] - - // Upsert the `customFields` from the `readOnlyOrder` - readOnlyOrder.customFields.forEach { readOnlyCustomField in + // Create `customFields` objects from the `readOnlyOrder` + let newStorageMetaDataArray = readOnlyOrder.customFields.map { readOnlyCustomField in let newStorageMetaData = storage.insertNewObject(ofType: Storage.MetaData.self) newStorageMetaData.update(with: readOnlyCustomField) - newStorageMetaDataArray.append(newStorageMetaData) + return newStorageMetaData } // Batch writing process of multiple custom fields diff --git a/Yosemite/Yosemite/Stores/ProductStore.swift b/Yosemite/Yosemite/Stores/ProductStore.swift index 343d63ba810..2ca134b5b5c 100644 --- a/Yosemite/Yosemite/Stores/ProductStore.swift +++ b/Yosemite/Yosemite/Stores/ProductStore.swift @@ -1167,20 +1167,15 @@ extension ProductStore { // Remove any objects that exist in `storageProduct.customFields` but not in `readOnlyProduct.customFields` storageProduct.customFields?.forEach { storageCustomField in - if !readOnlyMetadataIDs.contains(storageCustomField.metadataID) { - storageProduct.removeFromCustomFields(storageCustomField) - storage.deleteObject(storageCustomField) - } + storageProduct.removeFromCustomFields(storageCustomField) + storage.deleteObject(storageCustomField) } - var newStorageMetaDataArray: [Storage.MetaData] = [] - - // Upsert the `customFields` from the `readOnlyProduct` - readOnlyProduct.customFields.forEach { readOnlyCustomField in + // Create `customFields` objects from the `readOnlyProduct` + let newStorageMetaDataArray = readOnlyProduct.customFields.map { readOnlyCustomField in let newStorageMetaData = storage.insertNewObject(ofType: Storage.MetaData.self) newStorageMetaData.update(with: readOnlyCustomField) - newStorageMetaDataArray.append(newStorageMetaData) - + return newStorageMetaData } // Batch writing process of multiple custom fields From 1ed6186571a07909bd6ea46d040a750bf502a796 Mon Sep 17 00:00:00 2001 From: Paolo Musolino Date: Tue, 8 Oct 2024 18:16:48 +0200 Subject: [PATCH 07/13] update: release-notes 20.6 --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 0e9601d259e..6563fd48b60 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -7,6 +7,7 @@ 20.6 ----- +- [**] Users can experience orders not loading or loading slowly if they have a large number of custom fields. This has been fixed now. [https://github.com/woocommerce/woocommerce-ios/pull/14110] - [*] Dashboard: Cards are now displayed in 2 columns on large screen sizes. [https://github.com/woocommerce/woocommerce-ios/pull/13983] - [*] Jetpack setup: fixed issue checking site info after activating Jetpack for sites with Jetpack connection package. [https://github.com/woocommerce/woocommerce-ios/pull/13993] - [*] Blaze: Campaign status is now updated immediately after being canceled. [https://github.com/woocommerce/woocommerce-ios/pull/13992] From 9d34b5c8b8214eb7147ba806b809da1222700c96 Mon Sep 17 00:00:00 2001 From: Paolo Musolino Date: Tue, 8 Oct 2024 18:30:45 +0200 Subject: [PATCH 08/13] fix: comments --- Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift | 2 +- Yosemite/Yosemite/Stores/ProductStore.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift b/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift index 29495d17f22..8c8b765d238 100644 --- a/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift +++ b/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift @@ -307,7 +307,7 @@ struct OrdersUpsertUseCase { // Create a set of metadata IDs from the read-only order for quick lookup let readOnlyMetadataIDs = Set(readOnlyOrder.customFields.map { $0.metadataID }) - // Remove any objects that exist in `storageOrder.customFields` but not in `readOnlyOrder.customFields` + // Remove any objects that exist in `storageOrder.customFields` regarding a specific order. storageOrder.customFields?.forEach { storageCustomField in storageOrder.removeFromCustomFields(storageCustomField) storage.deleteObject(storageCustomField) diff --git a/Yosemite/Yosemite/Stores/ProductStore.swift b/Yosemite/Yosemite/Stores/ProductStore.swift index 2ca134b5b5c..3a30e4942d9 100644 --- a/Yosemite/Yosemite/Stores/ProductStore.swift +++ b/Yosemite/Yosemite/Stores/ProductStore.swift @@ -1165,7 +1165,7 @@ extension ProductStore { // Create a set of metadata IDs from the read-only product for quick lookup let readOnlyMetadataIDs = Set(readOnlyProduct.customFields.map { $0.metadataID }) - // Remove any objects that exist in `storageProduct.customFields` but not in `readOnlyProduct.customFields` + // Remove any objects that exist in `storageProduct.customFields` regarding a specific product. storageProduct.customFields?.forEach { storageCustomField in storageProduct.removeFromCustomFields(storageCustomField) storage.deleteObject(storageCustomField) From d51ee596fb1c3c0533cacb338e2eb755c3c3bb8e Mon Sep 17 00:00:00 2001 From: Paolo Musolino Date: Tue, 8 Oct 2024 21:48:43 +0200 Subject: [PATCH 09/13] cleanup unused code --- Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift | 2 -- Yosemite/Yosemite/Stores/ProductStore.swift | 2 -- 2 files changed, 4 deletions(-) diff --git a/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift b/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift index 8c8b765d238..0578cd7105a 100644 --- a/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift +++ b/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift @@ -304,8 +304,6 @@ struct OrdersUpsertUseCase { /// Updates, inserts, or prunes the provided `storageOrder`'s custom fields using the provided `readOnlyOrder`'s custom fields /// private func handleOrderCustomFields(_ readOnlyOrder: Networking.Order, _ storageOrder: Storage.Order, _ storage: StorageType) { - // Create a set of metadata IDs from the read-only order for quick lookup - let readOnlyMetadataIDs = Set(readOnlyOrder.customFields.map { $0.metadataID }) // Remove any objects that exist in `storageOrder.customFields` regarding a specific order. storageOrder.customFields?.forEach { storageCustomField in diff --git a/Yosemite/Yosemite/Stores/ProductStore.swift b/Yosemite/Yosemite/Stores/ProductStore.swift index 3a30e4942d9..f2284f644a2 100644 --- a/Yosemite/Yosemite/Stores/ProductStore.swift +++ b/Yosemite/Yosemite/Stores/ProductStore.swift @@ -1162,8 +1162,6 @@ extension ProductStore { /// Updates, inserts, or prunes the provided `storageProduct`'s custom fields using the provided `readOnlyProduct`'s custom fields /// private func handleProductCustomFields(_ readOnlyProduct: Networking.Product, _ storageProduct: Storage.Product, _ storage: StorageType) { - // Create a set of metadata IDs from the read-only product for quick lookup - let readOnlyMetadataIDs = Set(readOnlyProduct.customFields.map { $0.metadataID }) // Remove any objects that exist in `storageProduct.customFields` regarding a specific product. storageProduct.customFields?.forEach { storageCustomField in From dd9ca2356ceb1927721114c57c770a18409939ed Mon Sep 17 00:00:00 2001 From: Paolo Musolino Date: Wed, 9 Oct 2024 01:38:47 +0200 Subject: [PATCH 10/13] Add test for handling large number of custom fields in orders and products - Modify `OrdersUpsertUseCaseTests.swift` to include a new test case for processing 5000 custom fields without deadlock. --- .../Order/OrdersUpsertUseCaseTests.swift | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/Yosemite/YosemiteTests/Stores/Order/OrdersUpsertUseCaseTests.swift b/Yosemite/YosemiteTests/Stores/Order/OrdersUpsertUseCaseTests.swift index 8e779935a3a..635b60207f8 100644 --- a/Yosemite/YosemiteTests/Stores/Order/OrdersUpsertUseCaseTests.swift +++ b/Yosemite/YosemiteTests/Stores/Order/OrdersUpsertUseCaseTests.swift @@ -2,8 +2,7 @@ import XCTest import Fakes import Storage -import Networking - +@testable import Networking @testable import Yosemite final class OrdersUpsertUseCaseTests: XCTestCase { @@ -262,6 +261,32 @@ final class OrdersUpsertUseCaseTests: XCTestCase { XCTAssertEqual(storageCustomField.toReadOnly(), customField) } + + func test_it_handles_large_number_of_custom_fields_for_order_and_product_without_deadlock_in_small_amount_of_time() throws { + // Given + let customFields = (1...2500).map { MetaData(metadataID: $0, key: "Key\($0)", value: "Value\($0)") } + let order = makeOrder().copy(siteID: 3, orderID: 98, customFields: customFields) + let product = Product.fake().copy(siteID: 3, productID: 99, customFields: customFields) + let backgroundContext = storageManager.writerDerivedStorage + let orderUseCase = OrdersUpsertUseCase(storage: backgroundContext) + let dispatcher = Dispatcher() + let network = MockNetwork() + let productStore = ProductStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + + // When + DispatchQueue.global(qos: .background).async { + orderUseCase.upsert([order]) + productStore.upsertStoredProducts(readOnlyProducts: [product], in: backgroundContext) + backgroundContext.saveIfNeeded() + } + + // Then + + self.waitUntil { + self.viewStorage.countObjects(ofType: Storage.MetaData.self) == 5000 + } + } + func test_it_persists_order_gift_card_in_storage() throws { // Given let giftCard = OrderGiftCard(giftCardID: 2, code: "SU9F-MGB5-KS5V-EZFT", amount: 20) From 5c03e7892c14c409cdb80d97271041ca31b79091 Mon Sep 17 00:00:00 2001 From: Paolo Musolino Date: Wed, 9 Oct 2024 01:39:32 +0200 Subject: [PATCH 11/13] update: removed extra space --- .../YosemiteTests/Stores/Order/OrdersUpsertUseCaseTests.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Yosemite/YosemiteTests/Stores/Order/OrdersUpsertUseCaseTests.swift b/Yosemite/YosemiteTests/Stores/Order/OrdersUpsertUseCaseTests.swift index 635b60207f8..18b89938c12 100644 --- a/Yosemite/YosemiteTests/Stores/Order/OrdersUpsertUseCaseTests.swift +++ b/Yosemite/YosemiteTests/Stores/Order/OrdersUpsertUseCaseTests.swift @@ -261,7 +261,6 @@ final class OrdersUpsertUseCaseTests: XCTestCase { XCTAssertEqual(storageCustomField.toReadOnly(), customField) } - func test_it_handles_large_number_of_custom_fields_for_order_and_product_without_deadlock_in_small_amount_of_time() throws { // Given let customFields = (1...2500).map { MetaData(metadataID: $0, key: "Key\($0)", value: "Value\($0)") } @@ -281,7 +280,6 @@ final class OrdersUpsertUseCaseTests: XCTestCase { } // Then - self.waitUntil { self.viewStorage.countObjects(ofType: Storage.MetaData.self) == 5000 } From 3cc83fdf4435a910761b0ebae87ec27c8c667f36 Mon Sep 17 00:00:00 2001 From: Paolo Musolino Date: Wed, 9 Oct 2024 19:50:46 +0200 Subject: [PATCH 12/13] Refactor custom fields handling in Orders and Products - Update `OrdersUpsertUseCase.swift` to upsert custom fields from `readOnlyOrder`. - Update `ProductStore.swift` to upsert custom fields from `readOnlyProduct`. - Remove redundant custom fields from both `storageOrder` and `storageProduct` based on `readOnlyOrder` and `readOnlyProduct`. --- .../Stores/Order/OrdersUpsertUseCase.swift | 31 ++++++++++--------- Yosemite/Yosemite/Stores/ProductStore.swift | 31 ++++++++++--------- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift b/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift index 0578cd7105a..d6e4d673b9d 100644 --- a/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift +++ b/Yosemite/Yosemite/Stores/Order/OrdersUpsertUseCase.swift @@ -304,23 +304,24 @@ struct OrdersUpsertUseCase { /// Updates, inserts, or prunes the provided `storageOrder`'s custom fields using the provided `readOnlyOrder`'s custom fields /// private func handleOrderCustomFields(_ readOnlyOrder: Networking.Order, _ storageOrder: Storage.Order, _ storage: StorageType) { - - // Remove any objects that exist in `storageOrder.customFields` regarding a specific order. - storageOrder.customFields?.forEach { storageCustomField in - storageOrder.removeFromCustomFields(storageCustomField) - storage.deleteObject(storageCustomField) - } - - // Create `customFields` objects from the `readOnlyOrder` - let newStorageMetaDataArray = readOnlyOrder.customFields.map { readOnlyCustomField in - let newStorageMetaData = storage.insertNewObject(ofType: Storage.MetaData.self) - newStorageMetaData.update(with: readOnlyCustomField) - return newStorageMetaData + let storedMetaData = storageOrder.customFields + // Upsert the `customFields` from the `readOnlyOrder` + readOnlyOrder.customFields.forEach { readOnlyCustomField in + if let existingStorageMetaData = storedMetaData?.first(where: { $0.metadataID == readOnlyCustomField.metadataID }) { + existingStorageMetaData.update(with: readOnlyCustomField) + } else { + let newStorageMetaData = storage.insertNewObject(ofType: Storage.MetaData.self) + newStorageMetaData.update(with: readOnlyCustomField) + storageOrder.addToCustomFields(newStorageMetaData) + } } - // Batch writing process of multiple custom fields - if !newStorageMetaDataArray.isEmpty { - storageOrder.addToCustomFields(NSSet(array: newStorageMetaDataArray)) + // Now, remove any objects that exist in `storageOrder.customFields` but not in `readOnlyOrder.customFields` + storageOrder.customFields?.forEach { storageCustomField in + if readOnlyOrder.customFields.first(where: { $0.metadataID == storageCustomField.metadataID } ) == nil { + storageOrder.removeFromCustomFields(storageCustomField) + storage.deleteObject(storageCustomField) + } } } diff --git a/Yosemite/Yosemite/Stores/ProductStore.swift b/Yosemite/Yosemite/Stores/ProductStore.swift index f2284f644a2..fbfc38d37d1 100644 --- a/Yosemite/Yosemite/Stores/ProductStore.swift +++ b/Yosemite/Yosemite/Stores/ProductStore.swift @@ -1162,23 +1162,24 @@ extension ProductStore { /// Updates, inserts, or prunes the provided `storageProduct`'s custom fields using the provided `readOnlyProduct`'s custom fields /// private func handleProductCustomFields(_ readOnlyProduct: Networking.Product, _ storageProduct: Storage.Product, _ storage: StorageType) { - - // Remove any objects that exist in `storageProduct.customFields` regarding a specific product. - storageProduct.customFields?.forEach { storageCustomField in - storageProduct.removeFromCustomFields(storageCustomField) - storage.deleteObject(storageCustomField) - } - - // Create `customFields` objects from the `readOnlyProduct` - let newStorageMetaDataArray = readOnlyProduct.customFields.map { readOnlyCustomField in - let newStorageMetaData = storage.insertNewObject(ofType: Storage.MetaData.self) - newStorageMetaData.update(with: readOnlyCustomField) - return newStorageMetaData + let storedMetaData = storageProduct.customFields + // Upsert the `customFields` from the `readOnlyProduct` + readOnlyProduct.customFields.forEach { readOnlyCustomField in + if let existingStorageMetaData = storedMetaData?.first(where: { $0.metadataID == readOnlyCustomField.metadataID }) { + existingStorageMetaData.update(with: readOnlyCustomField) + } else { + let newStorageMetaData = storage.insertNewObject(ofType: Storage.MetaData.self) + newStorageMetaData.update(with: readOnlyCustomField) + storageProduct.addToCustomFields(newStorageMetaData) + } } - // Batch writing process of multiple custom fields - if !newStorageMetaDataArray.isEmpty { - storageProduct.addToCustomFields(NSSet(array: newStorageMetaDataArray)) + // Now, remove any objects that exist in `storageProduct.customFields` but not in `readOnlyProduct.customFields` + storageProduct.customFields?.forEach { storageCustomField in + if readOnlyProduct.customFields.first(where: { $0.metadataID == storageCustomField.metadataID } ) == nil { + storageProduct.removeFromCustomFields(storageCustomField) + storage.deleteObject(storageCustomField) + } } } } From 161f7cd5ba6decc8c5a91f26a3b00ac5f78cffd5 Mon Sep 17 00:00:00 2001 From: Paolo Musolino Date: Thu, 10 Oct 2024 01:09:50 +0200 Subject: [PATCH 13/13] fix: indentation --- .../YosemiteTests/Stores/Order/OrdersUpsertUseCaseTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Yosemite/YosemiteTests/Stores/Order/OrdersUpsertUseCaseTests.swift b/Yosemite/YosemiteTests/Stores/Order/OrdersUpsertUseCaseTests.swift index 18b89938c12..2274e63d6b9 100644 --- a/Yosemite/YosemiteTests/Stores/Order/OrdersUpsertUseCaseTests.swift +++ b/Yosemite/YosemiteTests/Stores/Order/OrdersUpsertUseCaseTests.swift @@ -276,7 +276,7 @@ final class OrdersUpsertUseCaseTests: XCTestCase { DispatchQueue.global(qos: .background).async { orderUseCase.upsert([order]) productStore.upsertStoredProducts(readOnlyProducts: [product], in: backgroundContext) - backgroundContext.saveIfNeeded() + backgroundContext.saveIfNeeded() } // Then