2024-09-04 05:01:33 +00:00
|
|
|
|
//
|
|
|
|
|
|
// Database.swift
|
|
|
|
|
|
// GuruAnalytics_iOS
|
|
|
|
|
|
//
|
|
|
|
|
|
// Created by mayue on 2022/11/4.
|
|
|
|
|
|
// Copyright © 2022 Guru Network Limited. All rights reserved.
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
|
import RxSwift
|
|
|
|
|
|
import RxCocoa
|
|
|
|
|
|
import FMDB
|
|
|
|
|
|
|
|
|
|
|
|
internal class Database {
|
|
|
|
|
|
|
|
|
|
|
|
typealias PropertyName = GuruAnalytics.PropertyName
|
|
|
|
|
|
|
|
|
|
|
|
enum TableName: String, CaseIterable {
|
|
|
|
|
|
case event = "event"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private let dbIOQueue = DispatchQueue.init(label: "com.guru.analytics.db.io.queue", qos: .userInitiated)
|
|
|
|
|
|
|
|
|
|
|
|
private let dbQueueRelay = BehaviorRelay<FMDatabaseQueue?>(value: nil)
|
|
|
|
|
|
private let bag = DisposeBag()
|
|
|
|
|
|
|
|
|
|
|
|
/// 更新数据库表结构后,需要更新数据库版本
|
|
|
|
|
|
private let currentDBVersion = DBVersionHistory.v_3
|
|
|
|
|
|
|
|
|
|
|
|
private var dbVersion: Database.DBVersionHistory {
|
|
|
|
|
|
get {
|
|
|
|
|
|
if let v = UserDefaults.defaults?.value(forKey: UserDefaults.dbVersionKey) as? String,
|
|
|
|
|
|
let dbV = Database.DBVersionHistory.init(rawValue: v) {
|
|
|
|
|
|
return dbV
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return .initialVersion
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
set {
|
|
|
|
|
|
UserDefaults.defaults?.set(newValue.rawValue, forKey: UserDefaults.dbVersionKey)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal init() {
|
|
|
|
|
|
|
|
|
|
|
|
dbIOQueue.async { [weak self] in
|
|
|
|
|
|
|
|
|
|
|
|
guard let `self` = self else { return }
|
|
|
|
|
|
|
|
|
|
|
|
let applicationSupportPath = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory,
|
|
|
|
|
|
.userDomainMask,
|
|
|
|
|
|
true).last! + "/GuruAnalytics"
|
|
|
|
|
|
|
|
|
|
|
|
if !FileManager.default.fileExists(atPath: applicationSupportPath) {
|
|
|
|
|
|
do {
|
|
|
|
|
|
try FileManager.default.createDirectory(atPath: applicationSupportPath, withIntermediateDirectories: true)
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
assertionFailure("create db path error: \(error)")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let dbPath = applicationSupportPath + "/analytics.db"
|
|
|
|
|
|
let queue = FMDatabaseQueue(url: URL(fileURLWithPath: dbPath))!
|
|
|
|
|
|
cdPrint("database path: \(queue.path ?? "")")
|
|
|
|
|
|
|
|
|
|
|
|
self.createEventTable(in: queue)
|
|
|
|
|
|
.filter { $0 }
|
|
|
|
|
|
.flatMap { _ in
|
|
|
|
|
|
self.migrateDB(in: queue).asMaybe()
|
|
|
|
|
|
}
|
|
|
|
|
|
.flatMap({ _ in
|
|
|
|
|
|
self.resetAllTransitionStatus(in: queue).asMaybe()
|
|
|
|
|
|
})
|
|
|
|
|
|
.subscribe(onSuccess: { _ in
|
|
|
|
|
|
self.dbQueueRelay.accept(queue)
|
|
|
|
|
|
})
|
|
|
|
|
|
.disposed(by: self.bag)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal extension Database {
|
|
|
|
|
|
|
|
|
|
|
|
func addEventRecords(_ events: Entity.EventRecord) -> Single<Void> {
|
|
|
|
|
|
cdPrint(#function)
|
|
|
|
|
|
return mapTransactionToSingle { (db) in
|
|
|
|
|
|
try db.executeUpdate(events.insertSql(to: TableName.event.rawValue), values: nil)
|
|
|
|
|
|
}
|
|
|
|
|
|
.do(onSuccess: { [weak self] (_) in
|
|
|
|
|
|
guard let `self` = self else { return }
|
|
|
|
|
|
NotificationCenter.default.post(name: self.tableUpdateNotification(TableName.event.rawValue), object: nil)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func fetchEventRecordsToUpload(limit: Int) -> Single<[Entity.EventRecord]> {
|
|
|
|
|
|
return mapTransactionToSingle { (db) in
|
|
|
|
|
|
let querySQL: String =
|
|
|
|
|
|
"""
|
|
|
|
|
|
SELECT * FROM \(TableName.event.rawValue)
|
|
|
|
|
|
WHERE \(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) IS NULL
|
|
|
|
|
|
OR \(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) != \(Entity.EventRecord.TransitionStatus.instransition.rawValue)
|
|
|
|
|
|
ORDER BY \(Entity.EventRecord.CodingKeys.priority.rawValue) ASC, \(Entity.EventRecord.CodingKeys.timestamp.rawValue) ASC
|
|
|
|
|
|
LIMIT \(limit)
|
|
|
|
|
|
"""
|
|
|
|
|
|
cdPrint(#function + "query sql: \(querySQL)")
|
|
|
|
|
|
let results = try db.executeQuery(querySQL, values: nil) //[ASC | DESC]
|
|
|
|
|
|
var t: [Entity.EventRecord] = []
|
|
|
|
|
|
while results.next() {
|
|
|
|
|
|
guard let recordId = results.string(forColumnIndex: 0),
|
|
|
|
|
|
let eventName = results.string(forColumnIndex: 1),
|
|
|
|
|
|
let eventJson = results.string(forColumnIndex: 2) else {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let priority: Int = results.columnIsNull(Entity.EventRecord.CodingKeys.priority.rawValue) ?
|
|
|
|
|
|
Entity.EventRecord.Priority.DEFAULT.rawValue : Int(results.int(forColumn: Entity.EventRecord.CodingKeys.priority.rawValue))
|
|
|
|
|
|
|
|
|
|
|
|
let ts: Int = results.columnIsNull(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) ?
|
|
|
|
|
|
Entity.EventRecord.TransitionStatus.idle.rawValue : Int(results.int(forColumn: Entity.EventRecord.CodingKeys.transitionStatus.rawValue))
|
|
|
|
|
|
|
|
|
|
|
|
let record = Entity.EventRecord(recordId: recordId, eventName: eventName, eventJson: eventJson,
|
|
|
|
|
|
timestamp: results.longLongInt(forColumn: Entity.EventRecord.CodingKeys.timestamp.rawValue),
|
|
|
|
|
|
priority: priority, transitionStatus: ts)
|
|
|
|
|
|
t.append(record)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
results.close()
|
|
|
|
|
|
|
|
|
|
|
|
try t.forEach { record in
|
|
|
|
|
|
let updateSQL =
|
|
|
|
|
|
"""
|
|
|
|
|
|
UPDATE \(TableName.event.rawValue)
|
|
|
|
|
|
SET \(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) = \(Entity.EventRecord.TransitionStatus.instransition.rawValue)
|
|
|
|
|
|
WHERE \(Entity.EventRecord.CodingKeys.recordId.rawValue) = '\(record.recordId)'
|
|
|
|
|
|
"""
|
|
|
|
|
|
try db.executeUpdate(updateSQL, values: nil)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return t
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func deleteEventRecords(_ recordIds: [String]) -> Single<Void> {
|
|
|
|
|
|
guard !recordIds.isEmpty else {
|
|
|
|
|
|
return .just(())
|
|
|
|
|
|
}
|
|
|
|
|
|
cdPrint(#function + "\(recordIds)")
|
|
|
|
|
|
return mapTransactionToSingle { db in
|
|
|
|
|
|
try recordIds.forEach { item in
|
|
|
|
|
|
try db.executeUpdate("DELETE FROM \(TableName.event.rawValue) WHERE \(Entity.EventRecord.CodingKeys.recordId.rawValue) = '\(item)'", values: nil)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.do(onSuccess: { [weak self] (_) in
|
|
|
|
|
|
guard let `self` = self else { return }
|
|
|
|
|
|
NotificationCenter.default.post(name: self.tableUpdateNotification(TableName.event.rawValue), object: nil)
|
|
|
|
|
|
}, onError: { error in
|
|
|
|
|
|
cdPrint("\(#function) error: \(error)")
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func removeOutdatedEventRecords(earlierThan: Int64) -> Single<Void> {
|
|
|
|
|
|
return mapTransactionToSingle { db in
|
|
|
|
|
|
let sql = """
|
|
|
|
|
|
DELETE FROM \(TableName.event.rawValue)
|
|
|
|
|
|
WHERE \(Entity.EventRecord.CodingKeys.timestamp.rawValue) < \(earlierThan)
|
|
|
|
|
|
"""
|
|
|
|
|
|
try db.executeUpdate(sql, values: nil)
|
|
|
|
|
|
}
|
|
|
|
|
|
.do(onSuccess: { [weak self] (_) in
|
|
|
|
|
|
guard let `self` = self else { return }
|
|
|
|
|
|
NotificationCenter.default.post(name: self.tableUpdateNotification(TableName.event.rawValue), object: nil)
|
|
|
|
|
|
}, onError: { error in
|
|
|
|
|
|
cdPrint("\(#function) error: \(error)")
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-23 02:53:09 +00:00
|
|
|
|
func fetchOutdatedEventRecords(earlierThan: Int64) -> Single<[Entity.EventRecord]> {
|
|
|
|
|
|
return mapTransactionToSingle { (db) in
|
|
|
|
|
|
let querySQL: String =
|
|
|
|
|
|
"""
|
|
|
|
|
|
SELECT * FROM \(TableName.event.rawValue)
|
|
|
|
|
|
WHERE \(Entity.EventRecord.CodingKeys.timestamp.rawValue) < \(earlierThan)
|
|
|
|
|
|
"""
|
|
|
|
|
|
cdPrint(#function + "query sql: \(querySQL)")
|
|
|
|
|
|
let results = try db.executeQuery(querySQL, values: nil) //[ASC | DESC]
|
|
|
|
|
|
var t: [Entity.EventRecord] = []
|
|
|
|
|
|
while results.next() {
|
|
|
|
|
|
guard let recordId = results.string(forColumnIndex: 0),
|
|
|
|
|
|
let eventName = results.string(forColumnIndex: 1),
|
|
|
|
|
|
let eventJson = results.string(forColumnIndex: 2) else {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let priority: Int = results.columnIsNull(Entity.EventRecord.CodingKeys.priority.rawValue) ?
|
|
|
|
|
|
Entity.EventRecord.Priority.DEFAULT.rawValue : Int(results.int(forColumn: Entity.EventRecord.CodingKeys.priority.rawValue))
|
|
|
|
|
|
|
|
|
|
|
|
let ts: Int = results.columnIsNull(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) ?
|
|
|
|
|
|
Entity.EventRecord.TransitionStatus.idle.rawValue : Int(results.int(forColumn: Entity.EventRecord.CodingKeys.transitionStatus.rawValue))
|
|
|
|
|
|
|
|
|
|
|
|
let record = Entity.EventRecord(recordId: recordId, eventName: eventName, eventJson: eventJson,
|
|
|
|
|
|
timestamp: results.longLongInt(forColumn: Entity.EventRecord.CodingKeys.timestamp.rawValue),
|
|
|
|
|
|
priority: priority, transitionStatus: ts)
|
|
|
|
|
|
t.append(record)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
results.close()
|
|
|
|
|
|
return t
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-04 05:01:33 +00:00
|
|
|
|
func resetTransitionStatus(for recordIds: [String]) -> Single<Void> {
|
|
|
|
|
|
guard !recordIds.isEmpty else {
|
|
|
|
|
|
return .just(())
|
|
|
|
|
|
}
|
|
|
|
|
|
cdPrint(#function + "\(recordIds)")
|
|
|
|
|
|
return mapTransactionToSingle { db in
|
|
|
|
|
|
try recordIds.forEach { item in
|
|
|
|
|
|
let updateSQL =
|
|
|
|
|
|
"""
|
|
|
|
|
|
UPDATE \(TableName.event.rawValue)
|
|
|
|
|
|
SET \(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) = \(Entity.EventRecord.TransitionStatus.idle.rawValue)
|
|
|
|
|
|
WHERE \(Entity.EventRecord.CodingKeys.recordId.rawValue) = '\(item)'
|
|
|
|
|
|
"""
|
|
|
|
|
|
try db.executeUpdate(updateSQL, values: nil)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.do(onSuccess: { [weak self] (_) in
|
|
|
|
|
|
guard let `self` = self else { return }
|
|
|
|
|
|
NotificationCenter.default.post(name: self.tableUpdateNotification(TableName.event.rawValue), object: nil)
|
|
|
|
|
|
}, onError: { error in
|
|
|
|
|
|
cdPrint("\(#function) error: \(error)")
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func uploadableEventRecordCount() -> Single<Int> {
|
|
|
|
|
|
return mapTransactionToSingle { db in
|
|
|
|
|
|
let querySQL =
|
|
|
|
|
|
"""
|
|
|
|
|
|
SELECT count(*) as Count FROM \(TableName.event.rawValue)
|
|
|
|
|
|
WHERE \(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) IS NULL
|
|
|
|
|
|
OR \(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) != \(Entity.EventRecord.TransitionStatus.instransition.rawValue)
|
|
|
|
|
|
"""
|
|
|
|
|
|
let result = try db.executeQuery(querySQL, values: nil)
|
|
|
|
|
|
var count = 0
|
|
|
|
|
|
while result.next() {
|
|
|
|
|
|
count = Int(result.int(forColumn: "Count"))
|
|
|
|
|
|
}
|
|
|
|
|
|
result.parentDB = nil
|
|
|
|
|
|
result.close()
|
|
|
|
|
|
return count
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func uploadableEventRecordCountOb() -> Observable<Int> {
|
|
|
|
|
|
return NotificationCenter.default.rx.notification(tableUpdateNotification(TableName.event.rawValue))
|
|
|
|
|
|
.startWith(Notification(name: tableUpdateNotification(TableName.event.rawValue)))
|
|
|
|
|
|
.flatMap({ [weak self] (_) -> Observable<Int> in
|
|
|
|
|
|
guard let `self` = self else {
|
|
|
|
|
|
return Observable.empty()
|
|
|
|
|
|
}
|
|
|
|
|
|
return self.uploadableEventRecordCount().asObservable()
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func hasFgEventRecord() -> Single<Bool> {
|
|
|
|
|
|
return mapTransactionToSingle { db in
|
|
|
|
|
|
let querySQL =
|
|
|
|
|
|
"""
|
|
|
|
|
|
SELECT count(*) as Count FROM \(TableName.event.rawValue)
|
|
|
|
|
|
WHERE \(Entity.EventRecord.CodingKeys.eventName.rawValue) == '\(GuruAnalytics.fgEvent.name)'
|
|
|
|
|
|
"""
|
|
|
|
|
|
let result = try db.executeQuery(querySQL, values: nil)
|
|
|
|
|
|
var count = 0
|
|
|
|
|
|
while result.next() {
|
|
|
|
|
|
count = Int(result.int(forColumn: "Count"))
|
|
|
|
|
|
}
|
|
|
|
|
|
result.parentDB = nil
|
|
|
|
|
|
result.close()
|
|
|
|
|
|
return count > 0
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private extension Database {
|
|
|
|
|
|
func createEventTable(in queue: FMDatabaseQueue) -> Single<Bool> {
|
|
|
|
|
|
return mapTransactionToSingle(queue: queue) { db in
|
|
|
|
|
|
db.executeStatements(Entity.EventRecord.createTableSql(with: TableName.event.rawValue))
|
|
|
|
|
|
}
|
|
|
|
|
|
.do(onSuccess: { result in
|
|
|
|
|
|
cdPrint("createEventTable result: \(result)")
|
|
|
|
|
|
}, onError: { error in
|
|
|
|
|
|
cdPrint("createEventTable error: \(error)")
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func mapTransactionToSingle<T>(_ transaction: @escaping ((FMDatabase) throws -> T)) -> Single<T> {
|
|
|
|
|
|
return dbQueueRelay.compactMap({ $0 })
|
|
|
|
|
|
.take(1)
|
|
|
|
|
|
.asSingle()
|
|
|
|
|
|
.flatMap { [unowned self] queue -> Single<T> in
|
|
|
|
|
|
return self.mapTransactionToSingle(queue: queue, transaction)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func mapTransactionToSingle<T>(queue: FMDatabaseQueue, _ transaction: @escaping ((FMDatabase) throws -> T)) -> Single<T> {
|
|
|
|
|
|
return Single<T>.create { [weak self] (subscriber) -> Disposable in
|
|
|
|
|
|
self?.dbIOQueue.async {
|
|
|
|
|
|
queue.inDeferredTransaction { (db, rollback) in
|
|
|
|
|
|
do {
|
|
|
|
|
|
let data = try transaction(db)
|
|
|
|
|
|
subscriber(.success(data))
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
rollback.pointee = true
|
|
|
|
|
|
cdPrint("inDeferredTransaction failed: \(error.localizedDescription)")
|
|
|
|
|
|
subscriber(.failure(error))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return Disposables.create()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func tableUpdateNotification(_ tableName: String) -> Notification.Name {
|
|
|
|
|
|
return Notification.Name("Guru.Analytics.DB.Table.update-\(tableName)")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func migrateDB(in queue: FMDatabaseQueue) -> Single<Void> {
|
|
|
|
|
|
|
|
|
|
|
|
return mapTransactionToSingle(queue: queue) { [weak self] db in
|
|
|
|
|
|
|
|
|
|
|
|
guard let `self` = self else { return }
|
|
|
|
|
|
|
|
|
|
|
|
while let nextVersion = self.dbVersion.nextVersion,
|
|
|
|
|
|
self.dbVersion < self.currentDBVersion {
|
|
|
|
|
|
switch nextVersion {
|
|
|
|
|
|
case .v_1:
|
|
|
|
|
|
()
|
|
|
|
|
|
case .v_2:
|
|
|
|
|
|
/// v_1 -> v_2
|
|
|
|
|
|
/// event表增加priority列
|
|
|
|
|
|
if !db.columnExists(Entity.EventRecord.CodingKeys.priority.rawValue, inTableWithName: TableName.event.rawValue) {
|
|
|
|
|
|
db.executeStatements("""
|
|
|
|
|
|
ALTER TABLE \(TableName.event.rawValue)
|
|
|
|
|
|
ADD \(Entity.EventRecord.CodingKeys.priority.rawValue) Integer DEFAULT \(Entity.EventRecord.Priority.DEFAULT.rawValue)
|
|
|
|
|
|
""")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case .v_3:
|
|
|
|
|
|
/// v_2 -> v_3
|
|
|
|
|
|
/// event表增加transitionStatus列
|
|
|
|
|
|
if !db.columnExists(Entity.EventRecord.CodingKeys.transitionStatus.rawValue, inTableWithName: TableName.event.rawValue) {
|
|
|
|
|
|
db.executeStatements("""
|
|
|
|
|
|
ALTER TABLE \(TableName.event.rawValue)
|
|
|
|
|
|
ADD \(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) Integer DEFAULT \(Entity.EventRecord.TransitionStatus.idle.rawValue)
|
|
|
|
|
|
""")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
self.dbVersion = nextVersion
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
.do(onError: { error in
|
|
|
|
|
|
cdPrint("migrate db error: \(error)")
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func resetAllTransitionStatus(in queue: FMDatabaseQueue) -> Single<Void> {
|
|
|
|
|
|
return mapTransactionToSingle(queue: queue) { db in
|
|
|
|
|
|
let updateSQL =
|
|
|
|
|
|
"""
|
|
|
|
|
|
UPDATE \(TableName.event.rawValue)
|
|
|
|
|
|
SET \(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) = \(Entity.EventRecord.TransitionStatus.idle.rawValue)
|
|
|
|
|
|
"""
|
|
|
|
|
|
try db.executeUpdate(updateSQL, values: nil)
|
|
|
|
|
|
}
|
|
|
|
|
|
.do(onSuccess: { [weak self] (_) in
|
|
|
|
|
|
guard let `self` = self else { return }
|
|
|
|
|
|
NotificationCenter.default.post(name: self.tableUpdateNotification(TableName.event.rawValue), object: nil)
|
|
|
|
|
|
}, onError: { error in
|
|
|
|
|
|
cdPrint("\(#function) error: \(error)")
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fileprivate extension Array where Element == String {
|
|
|
|
|
|
|
|
|
|
|
|
var joinedStringForSQL: String {
|
|
|
|
|
|
return self.map { "'\($0)'" }.joined(separator: ",")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private extension Database {
|
|
|
|
|
|
|
|
|
|
|
|
enum DBVersionHistory: String, Comparable {
|
|
|
|
|
|
case v_1
|
|
|
|
|
|
case v_2
|
|
|
|
|
|
case v_3
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
extension Database.DBVersionHistory {
|
|
|
|
|
|
|
|
|
|
|
|
static func < (lhs: Database.DBVersionHistory, rhs: Database.DBVersionHistory) -> Bool {
|
|
|
|
|
|
return lhs.versionNumber < rhs.versionNumber
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var versionNumber: Int {
|
|
|
|
|
|
return Int(String(self.rawValue.split(separator: "_")[1])) ?? 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var nextVersion: Self? {
|
|
|
|
|
|
return .init(rawValue: "v_\(versionNumber + 1)")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static let initialVersion: Self = .v_1
|
|
|
|
|
|
}
|