DB optimizations

This commit is contained in:
Justin Mazzocchi 2020-09-27 22:16:02 -07:00
parent 7172dc5e45
commit 6d3c66d251
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C

View file

@ -12,29 +12,29 @@ public enum IdentityDatabaseError: Error {
} }
public struct IdentityDatabase { public struct IdentityDatabase {
private let databaseQueue: DatabaseQueue private let databaseWriter: DatabaseWriter
public init(inMemory: Bool, keychain: Keychain.Type) throws { public init(inMemory: Bool, keychain: Keychain.Type) throws {
if inMemory { if inMemory {
databaseQueue = DatabaseQueue() databaseWriter = DatabaseQueue()
} else { } else {
let path = try FileManager.default.databaseDirectoryURL(name: Self.name).path let path = try FileManager.default.databaseDirectoryURL(name: Self.name).path
var configuration = Configuration() var configuration = Configuration()
configuration.prepareDatabase { configuration.prepareDatabase {
try $0.usePassphrase(try Secrets.databaseKey(identityID: nil, keychain: keychain)) try $0.usePassphrase(Secrets.databaseKey(identityID: nil, keychain: keychain))
} }
databaseQueue = try DatabaseQueue(path: path, configuration: configuration) databaseWriter = try DatabasePool(path: path, configuration: configuration)
} }
try Self.migrate(databaseQueue) try migrate()
} }
} }
public extension IdentityDatabase { public extension IdentityDatabase {
func createIdentity(id: UUID, url: URL, authenticated: Bool, pending: Bool) -> AnyPublisher<Never, Error> { func createIdentity(id: UUID, url: URL, authenticated: Bool, pending: Bool) -> AnyPublisher<Never, Error> {
databaseQueue.writePublisher( databaseWriter.writePublisher(
updates: IdentityRecord( updates: IdentityRecord(
id: id, id: id,
url: url, url: url,
@ -51,13 +51,13 @@ public extension IdentityDatabase {
} }
func deleteIdentity(id: UUID) -> AnyPublisher<Never, Error> { func deleteIdentity(id: UUID) -> AnyPublisher<Never, Error> {
databaseQueue.writePublisher(updates: IdentityRecord.filter(Column("id") == id).deleteAll) databaseWriter.writePublisher(updates: IdentityRecord.filter(Column("id") == id).deleteAll)
.ignoreOutput() .ignoreOutput()
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func updateLastUsedAt(identityID: UUID) -> AnyPublisher<Never, Error> { func updateLastUsedAt(identityID: UUID) -> AnyPublisher<Never, Error> {
databaseQueue.writePublisher { databaseWriter.writePublisher {
try IdentityRecord try IdentityRecord
.filter(Column("id") == identityID) .filter(Column("id") == identityID)
.updateAll($0, Column("lastUsedAt").set(to: Date())) .updateAll($0, Column("lastUsedAt").set(to: Date()))
@ -67,7 +67,7 @@ public extension IdentityDatabase {
} }
func updateInstance(_ instance: Instance, forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> { func updateInstance(_ instance: Instance, forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> {
databaseQueue.writePublisher { databaseWriter.writePublisher {
try Identity.Instance( try Identity.Instance(
uri: instance.uri, uri: instance.uri,
streamingAPI: instance.urls.streamingApi, streamingAPI: instance.urls.streamingApi,
@ -83,7 +83,7 @@ public extension IdentityDatabase {
} }
func updateAccount(_ account: Account, forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> { func updateAccount(_ account: Account, forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> {
databaseQueue.writePublisher( databaseWriter.writePublisher(
updates: Identity.Account( updates: Identity.Account(
id: account.id, id: account.id,
identityID: identityID, identityID: identityID,
@ -101,7 +101,7 @@ public extension IdentityDatabase {
} }
func confirmIdentity(id: UUID) -> AnyPublisher<Never, Error> { func confirmIdentity(id: UUID) -> AnyPublisher<Never, Error> {
databaseQueue.writePublisher { databaseWriter.writePublisher {
try IdentityRecord try IdentityRecord
.filter(Column("id") == id) .filter(Column("id") == id)
.updateAll($0, Column("pending").set(to: false)) .updateAll($0, Column("pending").set(to: false))
@ -112,7 +112,7 @@ public extension IdentityDatabase {
func updatePreferences(_ preferences: Mastodon.Preferences, func updatePreferences(_ preferences: Mastodon.Preferences,
forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> { forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> {
databaseQueue.writePublisher { databaseWriter.writePublisher {
guard let storedPreferences = try IdentityRecord.filter(Column("id") == identityID) guard let storedPreferences = try IdentityRecord.filter(Column("id") == identityID)
.fetchOne($0)? .fetchOne($0)?
.preferences else { .preferences else {
@ -127,7 +127,7 @@ public extension IdentityDatabase {
func updatePreferences(_ preferences: Identity.Preferences, func updatePreferences(_ preferences: Identity.Preferences,
forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> { forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> {
databaseQueue.writePublisher(updates: Self.writePreferences(preferences, id: identityID)) databaseWriter.writePublisher(updates: Self.writePreferences(preferences, id: identityID))
.ignoreOutput() .ignoreOutput()
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
@ -135,7 +135,7 @@ public extension IdentityDatabase {
func updatePushSubscription(alerts: PushSubscription.Alerts, func updatePushSubscription(alerts: PushSubscription.Alerts,
deviceToken: Data? = nil, deviceToken: Data? = nil,
forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> { forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> {
databaseQueue.writePublisher { databaseWriter.writePublisher {
let data = try IdentityRecord.databaseJSONEncoder(for: "pushSubscriptionAlerts").encode(alerts) let data = try IdentityRecord.databaseJSONEncoder(for: "pushSubscriptionAlerts").encode(alerts)
try IdentityRecord try IdentityRecord
@ -159,7 +159,7 @@ public extension IdentityDatabase {
.identityResultRequest .identityResultRequest
.fetchOne) .fetchOne)
.removeDuplicates() .removeDuplicates()
.publisher(in: databaseQueue, scheduling: immediate ? .immediate : .async(onQueue: .main)) .publisher(in: databaseWriter, scheduling: immediate ? .immediate : .async(onQueue: .main))
.tryMap { .tryMap {
guard let result = $0 else { throw IdentityDatabaseError.identityNotFound } guard let result = $0 else { throw IdentityDatabaseError.identityNotFound }
@ -175,7 +175,7 @@ public extension IdentityDatabase {
.identityResultRequest .identityResultRequest
.fetchAll) .fetchAll)
.removeDuplicates() .removeDuplicates()
.publisher(in: databaseQueue) .publisher(in: databaseWriter)
.map { $0.map(Identity.init(result:)) } .map { $0.map(Identity.init(result:)) }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
@ -189,7 +189,7 @@ public extension IdentityDatabase {
.limit(9) .limit(9)
.fetchAll) .fetchAll)
.removeDuplicates() .removeDuplicates()
.publisher(in: databaseQueue) .publisher(in: databaseWriter)
.map { $0.map(Identity.init(result:)) } .map { $0.map(Identity.init(result:)) }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
@ -197,12 +197,12 @@ public extension IdentityDatabase {
func immediateMostRecentlyUsedIdentityIDObservation() -> AnyPublisher<UUID?, Error> { func immediateMostRecentlyUsedIdentityIDObservation() -> AnyPublisher<UUID?, Error> {
ValueObservation.tracking(IdentityRecord.select(Column("id")).order(Column("lastUsedAt").desc).fetchOne) ValueObservation.tracking(IdentityRecord.select(Column("id")).order(Column("lastUsedAt").desc).fetchOne)
.removeDuplicates() .removeDuplicates()
.publisher(in: databaseQueue, scheduling: .immediate) .publisher(in: databaseWriter, scheduling: .immediate)
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func identitiesWithOutdatedDeviceTokens(deviceToken: Data) -> AnyPublisher<[Identity], Error> { func identitiesWithOutdatedDeviceTokens(deviceToken: Data) -> AnyPublisher<[Identity], Error> {
databaseQueue.readPublisher( databaseWriter.readPublisher(
value: IdentityRecord value: IdentityRecord
.order(Column("lastUsedAt").desc) .order(Column("lastUsedAt").desc)
.identityResultRequest .identityResultRequest
@ -214,9 +214,9 @@ public extension IdentityDatabase {
} }
private extension IdentityDatabase { private extension IdentityDatabase {
private static let name = "Identity" static let name = "Identity"
private static func writePreferences(_ preferences: Identity.Preferences, id: UUID) -> (Database) throws -> Void { static func writePreferences(_ preferences: Identity.Preferences, id: UUID) -> (Database) throws -> Void {
{ {
let data = try IdentityRecord.databaseJSONEncoder(for: "preferences").encode(preferences) let data = try IdentityRecord.databaseJSONEncoder(for: "preferences").encode(preferences)
@ -226,7 +226,7 @@ private extension IdentityDatabase {
} }
} }
private static func migrate(_ writer: DatabaseWriter) throws { func migrate() throws {
var migrator = DatabaseMigrator() var migrator = DatabaseMigrator()
migrator.registerMigration("createIdentities") { db in migrator.registerMigration("createIdentities") { db in
@ -238,13 +238,12 @@ private extension IdentityDatabase {
} }
try db.create(table: "identityRecord", ifNotExists: true) { t in try db.create(table: "identityRecord", ifNotExists: true) { t in
t.column("id", .text).notNull().primaryKey(onConflict: .replace) t.column("id", .text).indexed().notNull().primaryKey(onConflict: .replace)
t.column("url", .text).notNull() t.column("url", .text).notNull()
t.column("authenticated", .boolean).notNull() t.column("authenticated", .boolean).notNull()
t.column("pending", .boolean).notNull() t.column("pending", .boolean).notNull()
t.column("lastUsedAt", .datetime).notNull() t.column("lastUsedAt", .datetime).notNull()
t.column("instanceURI", .text) t.column("instanceURI", .text)
.indexed()
.references("instance", column: "uri") .references("instance", column: "uri")
t.column("preferences", .blob).notNull() t.column("preferences", .blob).notNull()
t.column("pushSubscriptionAlerts", .blob).notNull() t.column("pushSubscriptionAlerts", .blob).notNull()
@ -253,9 +252,7 @@ private extension IdentityDatabase {
try db.create(table: "account", ifNotExists: true) { t in try db.create(table: "account", ifNotExists: true) { t in
t.column("id", .text).notNull().primaryKey(onConflict: .replace) t.column("id", .text).notNull().primaryKey(onConflict: .replace)
t.column("identityID", .text) t.column("identityID", .text).notNull()
.notNull()
.indexed()
.references("identityRecord", column: "id", onDelete: .cascade) .references("identityRecord", column: "id", onDelete: .cascade)
t.column("username", .text).notNull() t.column("username", .text).notNull()
t.column("displayName", .text).notNull() t.column("displayName", .text).notNull()
@ -268,6 +265,6 @@ private extension IdentityDatabase {
} }
} }
try migrator.migrate(writer) try migrator.migrate(databaseWriter)
} }
} }