import Foundation enum ItemCollectionItemsOperation { case replaceItems } struct ItemCollectionItemReverseIndexReference: Equatable, Hashable, ReverseIndexReference { let collectionId: ItemCollectionId let itemIndex: ItemCollectionItemIndex static func <(lhs: ItemCollectionItemReverseIndexReference, rhs: ItemCollectionItemReverseIndexReference) -> Bool { if lhs.collectionId != rhs.collectionId { return lhs.collectionId < rhs.collectionId } else { return lhs.itemIndex < rhs.itemIndex } } static func decodeArray(_ buffer: MemoryBuffer) -> [ItemCollectionItemReverseIndexReference] { assert(buffer.length % (4 + 8 + 4 + 8) == 0) var references: [ItemCollectionItemReverseIndexReference] = [] references.reserveCapacity(buffer.length % (4 + 8 + 4 + 8)) withExtendedLifetime(buffer, { let readBuffer = ReadBuffer(memoryBufferNoCopy: buffer) for _ in 0 ..< buffer.length / (4 + 8 + 4 + 8) { var collectionIdNamespace: Int32 = 0 var collectionIdId: Int64 = 0 var indexIdIndex: Int32 = 0 var indexIdId: Int64 = 0 readBuffer.read(&collectionIdNamespace, offset: 0, length: 4) readBuffer.read(&collectionIdId, offset: 0, length: 8) readBuffer.read(&indexIdIndex, offset: 0, length: 4) readBuffer.read(&indexIdId, offset: 0, length: 8) references.append(ItemCollectionItemReverseIndexReference(collectionId: ItemCollectionId(namespace: collectionIdNamespace, id: collectionIdId), itemIndex: ItemCollectionItemIndex(index: indexIdIndex, id: indexIdId))) } }) return references } static func encodeArray(_ array: [ItemCollectionItemReverseIndexReference]) -> MemoryBuffer { let buffer = WriteBuffer() for reference in array { var collectionIdNamespace: Int32 = reference.collectionId.namespace var collectionIdId: Int64 = reference.collectionId.id var indexIdIndex: Int32 = reference.itemIndex.index var indexIdId: Int64 = reference.itemIndex.id buffer.write(&collectionIdNamespace, offset: 0, length: 4) buffer.write(&collectionIdId, offset: 0, length: 8) buffer.write(&indexIdIndex, offset: 0, length: 4) buffer.write(&indexIdId, offset: 0, length: 8) } return buffer } } final class ItemCollectionItemTable: Table { static func tableSpec(_ id: Int32) -> ValueBoxTable { return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false) } private let reverseIndexTable: ReverseIndexReferenceTable private let sharedKey = ValueBoxKey(length: 4 + 8 + 4 + 8) init(valueBox: ValueBox, table: ValueBoxTable, useCaches: Bool, reverseIndexTable: ReverseIndexReferenceTable) { self.reverseIndexTable = reverseIndexTable super.init(valueBox: valueBox, table: table, useCaches: useCaches) } private func key(collectionId: ItemCollectionId, index: ItemCollectionItemIndex) -> ValueBoxKey { self.sharedKey.setInt32(0, value: collectionId.namespace) self.sharedKey.setInt64(4, value: collectionId.id) self.sharedKey.setInt32(4 + 8, value: index.index) self.sharedKey.setInt64(4 + 8 + 4, value: index.id) return self.sharedKey } private func lowerBound(namespace: ItemCollectionId.Namespace) -> ValueBoxKey { let key = ValueBoxKey(length: 4) key.setInt32(0, value: namespace) return key } private func upperBound(namespace: ItemCollectionId.Namespace) -> ValueBoxKey { let key = ValueBoxKey(length: 4) key.setInt32(0, value: namespace) return key.successor } private func lowerBound(collectionId: ItemCollectionId) -> ValueBoxKey { let key = ValueBoxKey(length: 4 + 8) key.setInt32(0, value: collectionId.namespace) key.setInt64(4, value: collectionId.id) return key } private func upperBound(collectionId: ItemCollectionId) -> ValueBoxKey { let key = ValueBoxKey(length: 4 + 8) key.setInt32(0, value: collectionId.namespace) key.setInt64(4, value: collectionId.id) return key.successor } func lowerItems(collectionId: ItemCollectionId, itemIndex: ItemCollectionItemIndex, count: Int) -> [ItemCollectionItem] { var items: [ItemCollectionItem] = [] self.valueBox.range(self.table, start: self.key(collectionId: collectionId, index: itemIndex), end: self.lowerBound(collectionId: collectionId), values: { _, value in if let item = PostboxDecoder(buffer: value).decodeRootObject() as? ItemCollectionItem { items.append(item) } else { assertionFailure() } return true }, limit: count) return items } func itemCount(collectionId: ItemCollectionId) -> Int32 { var count: Int32 = 0 self.valueBox.range(self.table, start: self.upperBound(collectionId: collectionId), end: self.lowerBound(collectionId: collectionId), keys: { key in let itemIndex = ItemCollectionItemIndex(index: key.getInt32(4 + 8), id: key.getInt64(4 + 8 + 4)) count = itemIndex.index + 1 return true }, limit: 1) return count } func higherItems(collectionId: ItemCollectionId, itemIndex: ItemCollectionItemIndex, count: Int) -> [ItemCollectionItem] { var items: [ItemCollectionItem] = [] self.valueBox.range(self.table, start: self.key(collectionId: collectionId, index: itemIndex), end: self.upperBound(collectionId: collectionId), values: { _, value in if let item = PostboxDecoder(buffer: value).decodeRootObject() as? ItemCollectionItem { items.append(item) } else { assertionFailure() } return true }, limit: count) return items } func getItems(namespace: ItemCollectionId.Namespace) -> [ItemCollectionId: [ItemCollectionItem]] { var items: [ItemCollectionId: [ItemCollectionItem]] = [:] self.valueBox.range(self.table, start: self.lowerBound(namespace: namespace), end: self.upperBound(namespace: namespace), values: { key, value in let collectionId = ItemCollectionId(namespace: namespace, id: key.getInt64(4)) //let itemIndex = ItemCollectionItemIndex(index: key.getInt32(4 + 8), id: key.getInt64(4 + 8 + 4)) if let item = PostboxDecoder(buffer: value).decodeRootObject() as? ItemCollectionItem { if items[collectionId] != nil { items[collectionId]!.append(item) } else { items[collectionId] = [item] } } else { assertionFailure() } return true }, limit: 0) return items } func collectionItems(collectionId: ItemCollectionId) -> [ItemCollectionItem] { var items: [ItemCollectionItem] = [] self.valueBox.range(self.table, start: self.lowerBound(collectionId: collectionId), end: self.upperBound(collectionId: collectionId), values: { key, value in //let itemIndex = ItemCollectionItemIndex(index: key.getInt32(4 + 8), id: key.getInt64(4 + 8 + 4)) if let item = PostboxDecoder(buffer: value).decodeRootObject() as? ItemCollectionItem { items.append(item) } else { assertionFailure() } return true }, limit: 0) return items } func replaceItems(collectionId: ItemCollectionId, items: [ItemCollectionItem]) { let updatedIndices = Set(items.map({ $0.index })) var itemByIndex: [ItemCollectionItemIndex: ItemCollectionItem] = [:] for item in items { itemByIndex[item.index] = item } var currentIndices = Set() var removedIndexKeys: [ItemCollectionItemIndex: [MemoryBuffer]] = [:] self.valueBox.range(self.table, start: self.lowerBound(collectionId: collectionId), end: self.upperBound(collectionId: collectionId), values: { key, value in let itemIndex = ItemCollectionItemIndex(index: key.getInt32(4 + 8), id: key.getInt64(4 + 8 + 4)) currentIndices.insert(itemIndex) if !updatedIndices.contains(itemIndex) { if let item = PostboxDecoder(buffer: value).decodeRootObject() as? ItemCollectionItem { if !item.indexKeys.isEmpty { removedIndexKeys[itemIndex] = item.indexKeys } } else { assertionFailure() } } return true }, limit: 0) let addedIndices = updatedIndices.subtracting(currentIndices) let removedIndices = currentIndices.subtracting(updatedIndices) for index in removedIndices { self.valueBox.remove(self.table, key: self.key(collectionId: collectionId, index: index), secure: false) if let indexKeys = removedIndexKeys[index] { self.reverseIndexTable.remove(namespace: ReverseIndexNamespace(collectionId.namespace), reference: ItemCollectionItemReverseIndexReference(collectionId: collectionId, itemIndex: index), tokens: indexKeys.map({ ValueBoxKey($0) })) } } let sharedEncoder = PostboxEncoder() for index in addedIndices { let item = itemByIndex[index]! sharedEncoder.reset() sharedEncoder.encodeRootObject(item) withExtendedLifetime(sharedEncoder, { self.valueBox.set(self.table, key: self.key(collectionId: collectionId, index: index), value: sharedEncoder.readBufferNoCopy()) }) if !item.indexKeys.isEmpty { self.reverseIndexTable.add(namespace: ReverseIndexNamespace(collectionId.namespace), reference: ItemCollectionItemReverseIndexReference(collectionId: collectionId, itemIndex: index), tokens: item.indexKeys.map({ ValueBoxKey($0) })) } } } func searchIndexedItems(namespace: ItemCollectionId.Namespace, query: ItemCollectionSearchQuery) -> [ItemCollectionId: [ItemCollectionItem]] { let references: [ItemCollectionItemReverseIndexReference] switch query { case let .exact(token): references = self.reverseIndexTable.exactReferences(namespace: ReverseIndexNamespace(namespace), token: token) case let .matching(tokens): references = Array(self.reverseIndexTable.matchingReferences(namespace: ReverseIndexNamespace(namespace), tokens: tokens)) case let .any(tokens): references = Array(self.reverseIndexTable.matchingReferences(namespace: ReverseIndexNamespace(namespace), tokens: tokens, union: true)) } var resultsByCollectionId: [ItemCollectionId: [(ItemCollectionItemIndex, ItemCollectionItem)]] = [:] for reference in references { if let value = self.valueBox.get(self.table, key: self.key(collectionId: reference.collectionId, index: reference.itemIndex)), let item = PostboxDecoder(buffer: value).decodeRootObject() as? ItemCollectionItem { if resultsByCollectionId[reference.collectionId] == nil { resultsByCollectionId[reference.collectionId] = [(reference.itemIndex, item)] } else { resultsByCollectionId[reference.collectionId]!.append((reference.itemIndex, item)) } } else { assertionFailure() } } var results: [ItemCollectionId: [ItemCollectionItem]] = [:] for collectionId in resultsByCollectionId.keys { results[collectionId] = resultsByCollectionId[collectionId]!.sorted(by: { $0.0 < $1.0 }).map({ $0.1 }) } return results } override func clearMemoryCache() { } override func beforeCommit() { } }