diff --git a/Sources/Bodega/DiskStorageEngine.swift b/Sources/Bodega/DiskStorageEngine.swift index 22934cf..f9b22ef 100644 --- a/Sources/Bodega/DiskStorageEngine.swift +++ b/Sources/Bodega/DiskStorageEngine.swift @@ -124,6 +124,8 @@ public actor DiskStorageEngine: StorageEngine { } /// Checks whether a value with a key is persisted. + /// + /// This implementation provides `O(1)` checking for the key's existence. /// - Parameter key: The key to check for existence. /// - Returns: If the key exists the function returns true, false if it does not. public func keyExists(_ key: CacheKey) -> Bool { diff --git a/Sources/Bodega/SQLiteStorageEngine.swift b/Sources/Bodega/SQLiteStorageEngine.swift index a066e42..31069e6 100644 --- a/Sources/Bodega/SQLiteStorageEngine.swift +++ b/Sources/Bodega/SQLiteStorageEngine.swift @@ -228,6 +228,24 @@ public actor SQLiteStorageEngine: StorageEngine { return false } } + + /// Filters the provided keys to return only the ones that exist in the engine + /// - Parameter keys: The list of keys to check for existence. + /// - Returns: An array of keys that exist. This value is always a subset of the `keys` passed in. + public func keysExist(_ keys: [CacheKey]) async -> [CacheKey] { + do { + let rawKeys = keys.map(\.rawValue) + + let query = Self.storageTable + .select(Self.expressions.keyRow) + .filter(rawKeys.contains(Self.expressions.keyRow)) + + return try self.connection.prepare(query) + .map({ CacheKey(verbatim: $0[Self.expressions.keyRow]) }) + } catch { + return [] + } + } /// Iterates through the database to find the total number of `Data` items. /// - Returns: The file/key count. diff --git a/Sources/Bodega/StorageEngine.swift b/Sources/Bodega/StorageEngine.swift index b366cff..f3a39f7 100644 --- a/Sources/Bodega/StorageEngine.swift +++ b/Sources/Bodega/StorageEngine.swift @@ -29,6 +29,7 @@ public protocol StorageEngine: Actor { func removeAllData() async throws func keyExists(_ key: CacheKey) async -> Bool + func keysExist(_ keys: [CacheKey]) async -> [CacheKey] func keyCount() async -> Int func allKeys() async -> [CacheKey] @@ -94,6 +95,15 @@ extension StorageEngine { return await self.allKeys().contains(key) } + /// Filters the provided keys to return only the ones that exist in the engine + /// - Parameter keys: The list of keys to check for existence. + /// - Returns: An array of keys that exist. This value is always a subset of the `keys` passed in. + public func keysExist(_ keys: [CacheKey]) async -> [CacheKey] { + let allKeys = await self.allKeys() + let keySet = Set(allKeys) + return keys.filter { keySet.contains($0) } + } + /// Read the number of keys located in the ``StorageEngine``. /// - Returns: The number of keys located in the ``StorageEngine`` public func keyCount() async -> Int { diff --git a/Tests/BodegaTests/DiskStorageEngineTests.swift b/Tests/BodegaTests/DiskStorageEngineTests.swift index 31941e3..0116273 100644 --- a/Tests/BodegaTests/DiskStorageEngineTests.swift +++ b/Tests/BodegaTests/DiskStorageEngineTests.swift @@ -217,6 +217,16 @@ final class DiskStorageEngineTests: XCTestCase { let cacheKeyExistsAfterRemovingData = await storage.keyExists(Self.testCacheKey) XCTAssertFalse(cacheKeyExistsAfterRemovingData) } + + func testKeysExist() async throws { + let allKeys = Self.storedKeysAndData.map(\.key) + [Self.testCacheKey] + let noKeysExistBeforeAddingData = await storage.keysExist(allKeys) + XCTAssertTrue(noKeysExistBeforeAddingData.isEmpty) + + try await storage.write(Self.testData, key: Self.testCacheKey) + let someKeysExist = await storage.keysExist(allKeys) + XCTAssertEqual(someKeysExist, [Self.testCacheKey]) + } func testAllKeys() async throws { try await self.writeItemsToDisk(count: 10) diff --git a/Tests/BodegaTests/SQLiteStorageEngineTests.swift b/Tests/BodegaTests/SQLiteStorageEngineTests.swift index 431ee31..ca5d080 100644 --- a/Tests/BodegaTests/SQLiteStorageEngineTests.swift +++ b/Tests/BodegaTests/SQLiteStorageEngineTests.swift @@ -216,6 +216,16 @@ final class SQLiteStorageEngineTests: XCTestCase { let cacheKeyExistsAfterRemovingData = await storage.keyExists(Self.testCacheKey) XCTAssertFalse(cacheKeyExistsAfterRemovingData) } + + func testKeysExist() async throws { + let allKeys = Self.storedKeysAndData.map(\.key) + [Self.testCacheKey] + let noKeysExistBeforeAddingData = await storage.keysExist(allKeys) + XCTAssertTrue(noKeysExistBeforeAddingData.isEmpty) + + try await storage.write(Self.testData, key: Self.testCacheKey) + let someKeysExist = await storage.keysExist(allKeys) + XCTAssertEqual(someKeysExist, [Self.testCacheKey]) + } func testAllKeys() async throws { try await self.writeItemsToDatabase(count: 10)