import Foundation enum OrderedItemListOperation { case replace([OrderedItemListEntry]) case addOrMoveToFirstPosition(OrderedItemListEntry, Int?) case remove(MemoryBuffer) case update(MemoryBuffer, CodableEntry) } private enum OrderedItemListKeyNamespace: UInt8 { case indexToId = 0 case idToIndex = 1 } final class OrderedItemListTable: Table { static func tableSpec(_ id: Int32) -> ValueBoxTable { return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false) } private let indexTable: OrderedItemListIndexTable init(valueBox: ValueBox, table: ValueBoxTable, useCaches: Bool, indexTable: OrderedItemListIndexTable) { self.indexTable = indexTable super.init(valueBox: valueBox, table: table, useCaches: useCaches) } private func keyIndexToId(collectionId: Int32, itemIndex: UInt32) -> ValueBoxKey { let key = ValueBoxKey(length: 1 + 4 + 4) key.setUInt8(0, value: OrderedItemListKeyNamespace.indexToId.rawValue) key.setInt32(1, value: collectionId) key.setUInt32(1 + 4, value: itemIndex) return key } private func keyIndexToIdLowerBound(collectionId: Int32) -> ValueBoxKey { let key = ValueBoxKey(length: 1 + 4 + 4) key.setUInt8(0, value: OrderedItemListKeyNamespace.indexToId.rawValue) key.setInt32(1, value: collectionId) return key } private func keyIndexToIdUpperBound(collectionId: Int32) -> ValueBoxKey { let key = ValueBoxKey(length: 1 + 4 + 4) key.setUInt8(0, value: OrderedItemListKeyNamespace.indexToId.rawValue) key.setInt32(1, value: collectionId) return key.successor } private func keyIdToIndex(collectionId: Int32, id: MemoryBuffer) -> ValueBoxKey { let key = ValueBoxKey(length: 1 + 4 + id.length) key.setUInt8(0, value: OrderedItemListKeyNamespace.idToIndex.rawValue) key.setInt32(1, value: collectionId) memcpy(key.memory.advanced(by: 1 + 4), id.memory, id.length) return key } private func keyIdToIndexLowerBound(collectionId: Int32) -> ValueBoxKey { let key = ValueBoxKey(length: 1 + 4) key.setUInt8(0, value: OrderedItemListKeyNamespace.idToIndex.rawValue) key.setInt32(1, value: collectionId) return key } private func keyIdToIndexUpperBound(collectionId: Int32) -> ValueBoxKey { let key = ValueBoxKey(length: 1 + 4) key.setUInt8(0, value: OrderedItemListKeyNamespace.idToIndex.rawValue) key.setInt32(1, value: collectionId) return key.successor } func getItemIds(collectionId: Int32) -> [MemoryBuffer] { var itemIds: [MemoryBuffer] = [] self.valueBox.range(self.table, start: self.keyIndexToId(collectionId: collectionId, itemIndex: 0).predecessor, end: self.keyIndexToId(collectionId: collectionId, itemIndex: UInt32.max), values: { key, value in assert(key.getUInt8(0) == OrderedItemListKeyNamespace.indexToId.rawValue) itemIds.append(value) return true }, limit: 0) return itemIds } private func getIndex(collectionId: Int32, id: MemoryBuffer) -> UInt32? { if let value = self.valueBox.get(self.table, key: self.keyIdToIndex(collectionId: collectionId, id: id)) { var index: UInt32 = 0 value.read(&index, offset: 0, length: 4) return index } else { return nil } } func getItems(collectionId: Int32) -> [OrderedItemListEntry] { var currentIds: [MemoryBuffer] = [] self.valueBox.range(self.table, start: self.keyIndexToId(collectionId: collectionId, itemIndex: 0).predecessor, end: self.keyIndexToId(collectionId: collectionId, itemIndex: UInt32.max), values: { _, value in currentIds.append(value) return true }, limit: 0) var items: [OrderedItemListEntry] = [] for id in currentIds { if let contents = self.indexTable.get(collectionId: collectionId, id: id) { items.append(OrderedItemListEntry(id: id, contents: contents)) } else { assertionFailure() } } return items } func getItem(collectionId: Int32, itemId: MemoryBuffer) -> OrderedItemListEntry? { if let contents = self.indexTable.get(collectionId: collectionId, id: itemId) { return OrderedItemListEntry(id: itemId, contents: contents) } else { return nil } } func updateItem(collectionId: Int32, itemId: MemoryBuffer, item: CodableEntry, operations: inout [Int32: [OrderedItemListOperation]]) { if let _ = self.indexTable.get(collectionId: collectionId, id: itemId) { self.indexTable.set(collectionId: collectionId, id: itemId, content: item) if operations[collectionId] == nil { operations[collectionId] = [] } operations[collectionId]!.append(.update(itemId, item)) } } func replaceItems(collectionId: Int32, items: [OrderedItemListEntry], operations: inout [Int32: [OrderedItemListOperation]]) { if operations[collectionId] == nil { operations[collectionId] = [.replace(items)] } else { operations[collectionId]!.append(.replace(items)) } var currentIds: [MemoryBuffer] = [] var currentIndices: [UInt32] = [] self.valueBox.range(self.table, start: self.keyIndexToId(collectionId: collectionId, itemIndex: 0).predecessor, end: self.keyIndexToId(collectionId: collectionId, itemIndex: UInt32.max), values: { key, value in currentIndices.append(key.getUInt32(1 + 4)) currentIds.append(value) return true }, limit: 0) for index in currentIndices { self.valueBox.remove(self.table, key: self.keyIndexToId(collectionId: collectionId, itemIndex: index), secure: false) } for id in currentIds { self.valueBox.remove(self.table, key: self.keyIdToIndex(collectionId: collectionId, id: id), secure: false) self.indexTable.remove(collectionId: collectionId, id: id) } assert(Set(items.map({ $0.id.makeData() })).count == items.count) for i in 0 ..< items.count { self.valueBox.set(self.table, key: self.keyIndexToId(collectionId: collectionId, itemIndex: UInt32(i)), value: items[i].id) var indexValue: UInt32 = UInt32(i) self.valueBox.set(self.table, key: self.keyIdToIndex(collectionId: collectionId, id: items[i].id), value: MemoryBuffer(memory: &indexValue, capacity: 4, length: 4, freeWhenDone: false)) self.indexTable.set(collectionId: collectionId, id: items[i].id, content: items[i].contents) } #if ((arch(i386) || arch(x86_64)) && os(iOS)) || DEBUG assert(self.testIntegrity(collectionId: collectionId)) #endif } func addItemOrMoveToFirstPosition(collectionId: Int32, item: OrderedItemListEntry, removeTailIfCountExceeds: Int?, operations: inout [Int32: [OrderedItemListOperation]]) { if operations[collectionId] == nil { operations[collectionId] = [.addOrMoveToFirstPosition(item, removeTailIfCountExceeds)] } else { operations[collectionId]!.append(.addOrMoveToFirstPosition(item, removeTailIfCountExceeds)) } if let index = self.getIndex(collectionId: collectionId, id: item.id), index == 0 { self.indexTable.set(collectionId: collectionId, id: item.id, content: item.contents) return } var orderedIds = self.getItemIds(collectionId: collectionId) let offsetUntilIndex: Int if let index = orderedIds.firstIndex(of: item.id) { offsetUntilIndex = index self.valueBox.remove(self.table, key: self.keyIndexToId(collectionId: collectionId, itemIndex: UInt32(index)), secure: false) } else { if let removeTailIfCountExceeds = removeTailIfCountExceeds, orderedIds.count + 1 > removeTailIfCountExceeds { self.indexTable.remove(collectionId: collectionId, id: orderedIds[orderedIds.count - 1]) self.valueBox.remove(self.table, key: self.keyIdToIndex(collectionId: collectionId, id: orderedIds[orderedIds.count - 1]), secure: false) self.valueBox.remove(self.table, key: self.keyIndexToId(collectionId: collectionId, itemIndex: UInt32(orderedIds.count - 1)), secure: false) orderedIds.removeLast() } offsetUntilIndex = orderedIds.count } self.indexTable.set(collectionId: collectionId, id: item.id, content: item.contents) for i in 0 ..< offsetUntilIndex { var updatedIndex: UInt32 = UInt32(i + 1) self.valueBox.set(self.table, key: self.keyIdToIndex(collectionId: collectionId, id: orderedIds[i]), value: MemoryBuffer(memory: &updatedIndex, capacity: 4, length: 4, freeWhenDone: false)) self.valueBox.set(self.table, key: self.keyIndexToId(collectionId: collectionId, itemIndex: updatedIndex), value: orderedIds[i]) } self.valueBox.set(self.table, key: self.keyIndexToId(collectionId: collectionId, itemIndex: 0), value: item.id) var itemIndex: UInt32 = 0 self.valueBox.set(self.table, key: self.keyIdToIndex(collectionId: collectionId, id: item.id), value: MemoryBuffer(memory: &itemIndex, capacity: 4, length: 4, freeWhenDone: false)) #if ((arch(i386) || arch(x86_64)) && os(iOS)) || DEBUG assert(self.testIntegrity(collectionId: collectionId)) #endif } func remove(collectionId: Int32, itemId: MemoryBuffer, operations: inout [Int32: [OrderedItemListOperation]]) { if let index = self.getIndex(collectionId: collectionId, id: itemId) { let orderedIds = self.getItemIds(collectionId: collectionId) if !orderedIds.isEmpty { self.valueBox.remove(self.table, key: self.keyIdToIndex(collectionId: collectionId, id: itemId), secure: false) self.valueBox.remove(self.table, key: self.keyIndexToId(collectionId: collectionId, itemIndex: index), secure: false) self.valueBox.remove(self.table, key: self.keyIdToIndex(collectionId: collectionId, id: orderedIds[orderedIds.count - 1]), secure: false) self.valueBox.remove(self.table, key: self.keyIndexToId(collectionId: collectionId, itemIndex: UInt32(orderedIds.count - 1)), secure: false) for i in (Int(index) + 1) ..< orderedIds.count { var updatedIndex: UInt32 = UInt32(i - 1) self.valueBox.set(self.table, key: self.keyIdToIndex(collectionId: collectionId, id: orderedIds[i]), value: MemoryBuffer(memory: &updatedIndex, capacity: 4, length: 4, freeWhenDone: false)) self.valueBox.set(self.table, key: self.keyIndexToId(collectionId: collectionId, itemIndex: updatedIndex), value: orderedIds[i]) } if operations[collectionId] == nil { operations[collectionId] = [] } operations[collectionId]!.append(.remove(itemId)) } self.indexTable.remove(collectionId: collectionId, id: itemId) } #if ((arch(i386) || arch(x86_64)) && os(iOS)) || DEBUG assert(self.testIntegrity(collectionId: collectionId)) #endif } func testIntegrity(collectionId: Int32) -> Bool { let orderedIds = self.getItemIds(collectionId: collectionId).map { ValueBoxKey($0) } let existingIds = Set(orderedIds) let indexIds = Set(self.indexTable.getAllItemIds(collectionId: collectionId).map { ValueBoxKey($0) }) if indexIds != existingIds { return false } var allIndexedKeys: [ValueBoxKey] = [] var allIndices: [UInt32] = [] self.valueBox.range(self.table, start: self.keyIdToIndex(collectionId: collectionId, id: MemoryBuffer()), end: self.keyIdToIndex(collectionId: collectionId + 1, id: MemoryBuffer()), values: { key, value in let id = MemoryBuffer(memory: malloc(key.length - 5)!, capacity: key.length - 5, length: key.length - 5, freeWhenDone: true) memcpy(id.memory, key.memory.advanced(by: 5), key.length - 5) allIndexedKeys.append(ValueBoxKey(id)) var index: UInt32 = 0 value.read(&index, offset: 0, length: 4) allIndices.append(index) return true }, limit: 0) if Set(allIndexedKeys) != existingIds { print("\(allIndexedKeys) != \(existingIds)") return false } if allIndices.sorted() != Array((0 ..< orderedIds.count).map { UInt32($0) }) { return false } return true } }