// // Copyright 2019 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation public import GRDB public protocol SDSModel: TSYapDatabaseObject, SDSIdentifiableModel { var sdsTableName: String { get } func asRecord() -> SDSRecord var serializer: SDSSerializer { get } func anyInsert(transaction: DBWriteTransaction) static var table: SDSTableMetadata { get } } // MARK: - public extension SDSModel { func sdsSave(saveMode: SDSSaveMode, transaction tx: DBWriteTransaction) { guard shouldBeSaved else { Logger.warn("Skipping save of: \(type(of: self))") return } switch saveMode { case .insert: anyWillInsert(with: tx) case .update: anyWillUpdate(with: tx) } let record = asRecord() record.sdsSave(saveMode: saveMode, transaction: tx) switch saveMode { case .insert: anyDidInsert(with: tx) case .update: anyDidUpdate(with: tx) } } func sdsRemove(transaction tx: DBWriteTransaction) { guard shouldBeSaved else { // Skipping remove. return } anyWillRemove(with: tx) // Don't use a record to delete the record; // asRecord() is expensive. let sql = """ DELETE FROM \(sdsTableName) WHERE uniqueId == ? """ failIfThrows { try tx.database.execute( sql: sql, arguments: [uniqueId], ) } anyDidRemove(with: tx) } } // MARK: - public extension TableRecord { static func ows_fetchCount(_ db: Database) -> UInt { do { let result = try fetchCount(db) guard result >= 0 else { owsFailDebug("Invalid result: \(result)") return 0 } guard result <= UInt.max else { owsFailDebug("Invalid result: \(result)") return UInt.max } return UInt(result) } catch { owsFailDebug("Read failed: \(error)") return 0 } } } // MARK: - Cursors public protocol SDSCursor { associatedtype Model: SDSModel mutating func next() throws -> Model? } public struct SDSMappedCursor { fileprivate var cursor: Cursor fileprivate let transform: (Cursor.Model) throws -> Element? public mutating func next() throws -> Element? { while let next = try cursor.next() { if let transformed = try transform(next) { return transformed } } return nil } } public extension SDSCursor { func map(transform: @escaping (Model) throws -> Element) -> SDSMappedCursor { return compactMap(transform: transform) } func compactMap(transform: @escaping (Model) throws -> Element?) -> SDSMappedCursor { return SDSMappedCursor(cursor: self, transform: transform) } }