Refactor metadata tables
This commit is contained in:
@@ -1,29 +0,0 @@
|
||||
import Foundation
|
||||
import SQLite
|
||||
|
||||
enum Metadata {
|
||||
|
||||
static func allKeys(in database: Database) throws -> [Int : Key] {
|
||||
try Key.readAll(in: database)
|
||||
}
|
||||
|
||||
static func createTables(in database: Connection) throws {
|
||||
try Value.createTable(in: database)
|
||||
try Key.createTable(in: database)
|
||||
}
|
||||
|
||||
static func metadata(for workoutId: Int, in database: Connection, keyMap: [Int : Key]) throws -> [Key : Value] {
|
||||
return try Value.metadata(for: workoutId, in: database).reduce(into: [:]) { dict, entry in
|
||||
guard let key = keyMap[entry.keyId] else {
|
||||
print("No '\(entry.keyId)' in table 'metadata_keys'")
|
||||
return
|
||||
}
|
||||
dict[key] = entry.value
|
||||
}
|
||||
}
|
||||
|
||||
static func insert(_ value: Value, for key: Key, of workoutId: Int, in database: Connection) throws {
|
||||
let keyId = try Metadata.Key.hasKey(key, in: database) ?? Metadata.Key.insert(key: key, in: database)
|
||||
try Value.insert(value, of: workoutId, for: keyId, in: database)
|
||||
}
|
||||
}
|
@@ -1,38 +0,0 @@
|
||||
import Foundation
|
||||
import SQLite
|
||||
|
||||
extension Metadata.Key {
|
||||
|
||||
private static let table = Table("metadata_keys")
|
||||
|
||||
private static let columnId = Expression<Int>("ROWID")
|
||||
|
||||
private static let columnKey = Expression<String>("key")
|
||||
|
||||
static func key(for keyId: Int, in database: Connection) throws -> String {
|
||||
try database.prepare(table.filter(columnId == keyId).limit(1)).map { $0[columnKey] }.first!
|
||||
}
|
||||
|
||||
static func readAll(in database: Connection) throws -> [Int : Self] {
|
||||
try database.prepare(table).reduce(into: [:]) { dict, row in
|
||||
dict[row[columnId]] = .init(rawValue: row[columnKey])
|
||||
}
|
||||
}
|
||||
|
||||
static func createTable(in database: Connection) throws {
|
||||
//try database.execute("CREATE TABLE metadata_keys (ROWID INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT UNIQUE)")
|
||||
try database.run(table.create { table in
|
||||
table.column(columnId, primaryKey: .autoincrement)
|
||||
table.column(columnKey, unique: true)
|
||||
})
|
||||
}
|
||||
|
||||
static func hasKey(_ key: Self, in database: Connection) throws -> Int? {
|
||||
try database.prepare(table.filter(columnKey == key.rawValue).limit(1)).map { $0[columnId] }.first
|
||||
}
|
||||
|
||||
static func insert(key: Self, in database: Connection) throws -> Int {
|
||||
Int(try database.run(table.insert(columnKey <- key.rawValue)))
|
||||
}
|
||||
|
||||
}
|
@@ -1,5 +1,9 @@
|
||||
import Foundation
|
||||
|
||||
enum Metadata {
|
||||
|
||||
}
|
||||
|
||||
extension Metadata {
|
||||
|
||||
enum Key {
|
||||
@@ -80,7 +84,7 @@ extension Metadata {
|
||||
|
||||
extension Metadata.Key: RawRepresentable {
|
||||
|
||||
init?(rawValue: String) {
|
||||
init(rawValue: String) {
|
||||
switch rawValue {
|
||||
case "HKWasUserEntered": self = .wasUserEntered
|
||||
case "HKHeartRateSensorLocation": self = .heartRateSensorLocation
|
||||
|
@@ -1,124 +0,0 @@
|
||||
import Foundation
|
||||
import SQLite
|
||||
|
||||
extension Metadata.Value {
|
||||
|
||||
private static let table = Table("metadata_values")
|
||||
|
||||
private static let columnRowId = Expression<Int>("ROW_ID")
|
||||
|
||||
private static let columnKeyId = Expression<Int?>("key_id")
|
||||
|
||||
private static let columnObjectId = Expression<Int?>("object_id")
|
||||
|
||||
private static let columnValueType = Expression<Int>("value_type")
|
||||
|
||||
private static let columnStringValue = Expression<String?>("string_value")
|
||||
|
||||
private static let columnNumericalValue = Expression<Double?>("numerical_value")
|
||||
|
||||
private static let columnDateValue = Expression<Double?>("date_value")
|
||||
|
||||
private static let columnDataValue = Expression<Data?>("data_value")
|
||||
|
||||
private static func readAll(in database: Connection) throws -> [Self] {
|
||||
try database.prepare(table).map(from)
|
||||
}
|
||||
|
||||
static func metadata(for workoutId: Int, in database: Connection) throws -> [Self] {
|
||||
try database.prepare(table.filter(columnObjectId == workoutId)).map(from)
|
||||
}
|
||||
|
||||
static func metadata(for workoutId: Int, in database: Connection) throws -> [(keyId: Int, value: Self)] {
|
||||
try database.prepare(table.filter(columnObjectId == workoutId)).compactMap { row in
|
||||
guard let keyId = row[columnKeyId] else {
|
||||
print("Found 'key_id == NULL' for metadata value of workout \(workoutId)")
|
||||
return nil
|
||||
}
|
||||
return (keyId, from(row: row))
|
||||
}
|
||||
}
|
||||
|
||||
static func createTable(in database: Connection) throws {
|
||||
//try database.execute("CREATE TABLE metadata_values (ROWID INTEGER PRIMARY KEY AUTOINCREMENT, key_id INTEGER, object_id INTEGER, value_type INTEGER NOT NULL DEFAULT 0, string_value TEXT, numerical_value REAL, date_value REAL, data_value BLOB)")
|
||||
try database.run(table.create { table in
|
||||
table.column(columnRowId, primaryKey: .autoincrement)
|
||||
table.column(columnKeyId)
|
||||
table.column(columnObjectId)
|
||||
table.column(columnValueType, defaultValue: 0)
|
||||
table.column(columnStringValue)
|
||||
table.column(columnNumericalValue)
|
||||
table.column(columnDateValue)
|
||||
table.column(columnDataValue)
|
||||
})
|
||||
}
|
||||
|
||||
static func insert(_ element: Self, of workoutId: Int, for keyId: Int, in database: Connection) throws {
|
||||
try database.run(table.insert(
|
||||
columnKeyId <- keyId,
|
||||
columnObjectId <- workoutId,
|
||||
columnValueType <- element.valueType.rawValue,
|
||||
columnStringValue <- element.stringValue,
|
||||
columnNumericalValue <- element.numericalValue,
|
||||
columnDateValue <- element.dateValue,
|
||||
columnDataValue <- element.dataValue
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
private extension Metadata.Value {
|
||||
|
||||
var stringValue: String? {
|
||||
if case let .string(value) = self {
|
||||
return value
|
||||
}
|
||||
if case let .numerical(_, unit) = self {
|
||||
return unit
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var numericalValue: Double? {
|
||||
if case let .number(value) = self {
|
||||
return value
|
||||
}
|
||||
if case let .numerical(value: value, unit: _) = self {
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var dateValue: Double? {
|
||||
if case let .date(value: date) = self {
|
||||
return date.timeIntervalSinceReferenceDate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var dataValue: Data? {
|
||||
if case let .data(data) = self {
|
||||
return data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Metadata.Value {
|
||||
|
||||
static func from(row: Row) -> Self {
|
||||
let valueType = ValueType(rawValue: row[columnValueType])!
|
||||
switch valueType {
|
||||
case .string:
|
||||
return .string(value: row[columnStringValue]!)
|
||||
case .number:
|
||||
return .number(value: row[columnNumericalValue]!)
|
||||
case .date:
|
||||
return .date(value: .init(timeIntervalSinceReferenceDate: row[columnDateValue]!))
|
||||
case .numerical:
|
||||
return .numerical(value: row[columnNumericalValue]!, unit: row[columnStringValue]!)
|
||||
case .data:
|
||||
return .data(value: row[columnDataValue]!)
|
||||
}
|
||||
}
|
||||
}
|
44
HealthImport/Model/Tables/MetadataKeysTable.swift
Normal file
44
HealthImport/Model/Tables/MetadataKeysTable.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
import Foundation
|
||||
import SQLite
|
||||
|
||||
struct MetadataKeysTable {
|
||||
|
||||
private let database: Connection
|
||||
|
||||
init(database: Connection) {
|
||||
self.database = database
|
||||
}
|
||||
|
||||
let table = Table("metadata_keys")
|
||||
|
||||
let rowId = Expression<Int>("ROWID")
|
||||
|
||||
let key = Expression<String>("key")
|
||||
|
||||
func key(for keyId: Int, in database: Connection) throws -> String {
|
||||
try database.pluck(table.filter(rowId == keyId)).map { $0[key] }!
|
||||
}
|
||||
|
||||
func all() throws -> [Int : Metadata.Key] {
|
||||
try database.prepare(table).reduce(into: [:]) { dict, row in
|
||||
dict[row[rowId]] = .init(rawValue: row[key])
|
||||
}
|
||||
}
|
||||
|
||||
func create() throws {
|
||||
//try database.execute("CREATE TABLE metadata_keys (ROWID INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT UNIQUE)")
|
||||
try database.run(table.create { table in
|
||||
table.column(rowId, primaryKey: .autoincrement)
|
||||
table.column(key, unique: true)
|
||||
})
|
||||
}
|
||||
|
||||
func hasKey(_ key: Metadata.Key) throws -> Int? {
|
||||
try database.pluck(table.filter(self.key == key.rawValue)).map { $0[rowId] }
|
||||
}
|
||||
|
||||
func insert(key: Metadata.Key) throws -> Int {
|
||||
Int(try database.run(table.insert(self.key <- key.rawValue)))
|
||||
}
|
||||
|
||||
}
|
42
HealthImport/Model/Tables/MetadataTables.swift
Normal file
42
HealthImport/Model/Tables/MetadataTables.swift
Normal file
@@ -0,0 +1,42 @@
|
||||
import Foundation
|
||||
import SQLite
|
||||
|
||||
struct MetadataTables {
|
||||
|
||||
private let database: Connection
|
||||
|
||||
let values: MetadataValuesTable
|
||||
|
||||
let keys: MetadataKeysTable
|
||||
|
||||
init(database: Connection) {
|
||||
self.database = database
|
||||
self.keys = .init(database: database)
|
||||
self.values = .init(database: database)
|
||||
}
|
||||
|
||||
func create() throws {
|
||||
try values.create()
|
||||
try keys.create()
|
||||
}
|
||||
|
||||
func metadata(for workoutId: Int) throws -> [Metadata.Key : Metadata.Value] {
|
||||
// Keys: rowId -> String
|
||||
|
||||
let selection = values.table
|
||||
.select(values.table[*], keys.table[keys.key])
|
||||
.filter(values.objectId == workoutId)
|
||||
.join(.leftOuter, keys.table, on: values.table[values.keyId] == keys.table[keys.rowId])
|
||||
|
||||
return try database.prepare(selection).reduce(into: [:]) { dict, row in
|
||||
let key = Metadata.Key(rawValue: row[keys.key])
|
||||
let value = values.from(row: row)
|
||||
dict[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
func insert(_ value: Metadata.Value, for key: Metadata.Key, of workoutId: Int) throws {
|
||||
let keyId = try keys.hasKey(key) ?? keys.insert(key: key)
|
||||
try values.insert(value, of: workoutId, for: keyId)
|
||||
}
|
||||
}
|
126
HealthImport/Model/Tables/MetadataValuesTable.swift
Normal file
126
HealthImport/Model/Tables/MetadataValuesTable.swift
Normal file
@@ -0,0 +1,126 @@
|
||||
import Foundation
|
||||
import SQLite
|
||||
|
||||
struct MetadataValuesTable {
|
||||
|
||||
private let database: Connection
|
||||
|
||||
init(database: Connection) {
|
||||
self.database = database
|
||||
}
|
||||
|
||||
let table = Table("metadata_values")
|
||||
|
||||
let rowId = Expression<Int>("ROW_ID")
|
||||
|
||||
let keyId = Expression<Int?>("key_id")
|
||||
|
||||
let objectId = Expression<Int?>("object_id")
|
||||
|
||||
let valueType = Expression<Int>("value_type")
|
||||
|
||||
let stringValue = Expression<String?>("string_value")
|
||||
|
||||
let numericalValue = Expression<Double?>("numerical_value")
|
||||
|
||||
let dateValue = Expression<Double?>("date_value")
|
||||
|
||||
let dataValue = Expression<Data?>("data_value")
|
||||
|
||||
func all() throws -> [Metadata.Value] {
|
||||
try database.prepare(table).map(from)
|
||||
}
|
||||
|
||||
func metadata(for workoutId: Int) throws -> [Metadata.Value] {
|
||||
try database.prepare(table.filter(objectId == workoutId)).map(from)
|
||||
}
|
||||
|
||||
func metadata(for workoutId: Int) throws -> [(keyId: Int, value: Metadata.Value)] {
|
||||
try database.prepare(table.filter(objectId == workoutId)).compactMap { row in
|
||||
guard let keyId = row[keyId] else {
|
||||
print("Found 'key_id == NULL' for metadata value of workout \(workoutId)")
|
||||
return nil
|
||||
}
|
||||
return (keyId, from(row: row))
|
||||
}
|
||||
}
|
||||
|
||||
func from(row: Row) -> Metadata.Value {
|
||||
let valueType = Metadata.Value.ValueType(rawValue: row[valueType])!
|
||||
switch valueType {
|
||||
case .string:
|
||||
return .string(value: row[stringValue]!)
|
||||
case .number:
|
||||
return .number(value: row[numericalValue]!)
|
||||
case .date:
|
||||
return .date(value: .init(timeIntervalSinceReferenceDate: row[dateValue]!))
|
||||
case .numerical:
|
||||
return .numerical(value: row[numericalValue]!, unit: row[stringValue]!)
|
||||
case .data:
|
||||
return .data(value: row[dataValue]!)
|
||||
}
|
||||
}
|
||||
|
||||
func create() throws {
|
||||
//try database.execute("CREATE TABLE metadata_values (ROWID INTEGER PRIMARY KEY AUTOINCREMENT, key_id INTEGER, object_id INTEGER, value_type INTEGER NOT NULL DEFAULT 0, string_value TEXT, numerical_value REAL, date_value REAL, data_value BLOB)")
|
||||
try database.run(table.create { table in
|
||||
table.column(rowId, primaryKey: .autoincrement)
|
||||
table.column(keyId)
|
||||
table.column(objectId)
|
||||
table.column(valueType, defaultValue: 0)
|
||||
table.column(stringValue)
|
||||
table.column(numericalValue)
|
||||
table.column(dateValue)
|
||||
table.column(dataValue)
|
||||
})
|
||||
}
|
||||
|
||||
func insert(_ element: Metadata.Value, of workoutId: Int, for keyId: Int) throws {
|
||||
try database.run(table.insert(
|
||||
self.keyId <- keyId,
|
||||
objectId <- workoutId,
|
||||
valueType <- element.valueType.rawValue,
|
||||
stringValue <- element.stringValue,
|
||||
numericalValue <- element.numericalValue,
|
||||
dateValue <- element.dateValue,
|
||||
dataValue <- element.dataValue
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
private extension Metadata.Value {
|
||||
|
||||
var stringValue: String? {
|
||||
if case let .string(value) = self {
|
||||
return value
|
||||
}
|
||||
if case let .numerical(_, unit) = self {
|
||||
return unit
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var numericalValue: Double? {
|
||||
if case let .number(value) = self {
|
||||
return value
|
||||
}
|
||||
if case let .numerical(value: value, unit: _) = self {
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var dateValue: Double? {
|
||||
if case let .date(value: date) = self {
|
||||
return date.timeIntervalSinceReferenceDate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var dataValue: Data? {
|
||||
if case let .data(data) = self {
|
||||
return data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
@@ -10,10 +10,13 @@ struct WorkoutsTable {
|
||||
|
||||
let activities: WorkoutActivitiesTable
|
||||
|
||||
let metadata: MetadataTables
|
||||
|
||||
init(database: Connection) {
|
||||
self.database = database
|
||||
self.events = .init(database: database)
|
||||
self.activities = .init(database: database)
|
||||
self.metadata = .init(database: database)
|
||||
}
|
||||
|
||||
let table = Table("workouts")
|
||||
@@ -37,14 +40,12 @@ struct WorkoutsTable {
|
||||
let condenserDate = Expression<Double?>("condenser_date")
|
||||
|
||||
func workouts() throws -> [Workout] {
|
||||
let metadataKeys = try Metadata.allKeys(in: database)
|
||||
|
||||
return try database.prepare(table).map { row in
|
||||
let id = row[dataId]
|
||||
|
||||
let events = try events.events(for: id, in: database)
|
||||
let activities = try activities.activities(for: id)
|
||||
let metadata = try Metadata.metadata(for: id, in: database, keyMap: metadataKeys)
|
||||
let metadata = try metadata.metadata(for: id)
|
||||
return .init(
|
||||
id: id,
|
||||
totalDistance: row[totalDistance],
|
||||
@@ -94,7 +95,7 @@ struct WorkoutsTable {
|
||||
}
|
||||
|
||||
for (key, value) in element.metadata {
|
||||
try Metadata.insert(value, for: key, of: dataId, in: database)
|
||||
try metadata.insert(value, for: key, of: dataId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user