Как сохранить и получить «Измерение» из SQLite - PullRequest
0 голосов
/ 20 марта 2020

Может кто-нибудь помочь мне понять, как сохранить и извлечь Dimension из SQLite в Swift? Dimension соответствует NSSecureCoding, поэтому я полагаю, что смогу сериализовать его.

Здесь я сохраняю измерение (похоже, работает)

func add(value: Int, unit: Dimension) throws -> SQLMeasurement {
    let insertSql = "INSERT INTO Measurements (value, unit) VALUES (?,?);"
    let insertStatement = try database.prepareStatement(sql: insertSql)
    guard sqlite3_bind_int(insertStatement, 1, Int32(value)) == SQLITE_OK else { fatalError() }
    guard let dimensionData = try? NSKeyedArchiver.archivedData(withRootObject: unit, requiringSecureCoding: false) else { fatalError() }
    print(dimensionData.base64EncodedString())
    let bindResult = sqlite3_bind_blob(insertStatement, 2, dimensionData.base64EncodedString(), Int32(dimensionData.count), SQLITE_TRANSIENT)
    let result = sqlite3_step(insertStatement)
    let lastId = database.lastId()
    sqlite3_finalize(insertStatement)
    return SQLMeasurement(id: lastId, db: database)
}

Вот где Я должен извлечь это. (Сбой с кодом ошибки:)

func unit() -> Dimension {
    let sql = "SELECT unit FROM Measurements WHERE id = ?"
    let queryStatement = try! db.prepareStatement(sql: sql) //TODO: Remove Bang!
    guard sqlite3_bind_int(queryStatement, 1, Int32(id)) == SQLITE_OK else { fatalError() }
    guard sqlite3_step(queryStatement) == SQLITE_ROW else { fatalError() }
    guard let dataBlob = sqlite3_column_blob(queryStatement, 0) else { fatalError() }
    let dataBlobLength = sqlite3_column_bytes(queryStatement, 0)
    let data = Data(bytes: dataBlob, count: Int(dataBlobLength))
    do {
        let result = try NSKeyedUnarchiver.unarchivedObject(ofClass: Dimension.self, from: data)
        return result!
    }
    catch { print("unarchive error: \(error)") }
    //let dimension2 = dimension(data: data)
    sqlite3_finalize(queryStatement)
    return UnitPower.kilowatts //FIXME:
}

Вот код ошибки из запроса разархивирования:

unarchive error: Error Domain=NSCocoaErrorDomain Code=4864 "*** -[NSKeyedUnarchiver _initForReadingFromData:error:throwLegacyExceptions:]: incomprehensible archive version (-1)" UserInfo={NSDebugDescription=*** -[NSKeyedUnarchiver _initForReadingFromData:error:throwLegacyExceptions:]: incomprehensible archive version (-1)}

Вот ошибка при разархивировании:

enter image description here

Вот база данных sqlite, подтверждающая сохранение как работающее:

enter image description here

Эта игровая площадка работает:

import UIKit

func dimension4(string64: String) -> Dimension {
    guard let data = Data(base64Encoded: string64) else { fatalError() }
    guard let dimension2 = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Dimension else { fatalError() }
    return dimension2
}

func dimension(data: Data) -> Dimension {
    guard let dimension2 = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Dimension else { fatalError() }
    return dimension2
}

let dimension = UnitMass.grams

guard let dimensionData = try? NSKeyedArchiver.archivedData(withRootObject: dimension, requiringSecureCoding: false) else { fatalError() }
let dimensionFromData = dimension(data: dimensionData)
print("dimensionFromData: \(dimensionFromData.description)")

let stringData = dimensionData.base64EncodedString()
let dimensionFromString64 = dimension4(string64: stringData)
print("dimensionFromString64: \(dimensionFromString64.description)")

ФИНАЛЬНЫЙ ОТВЕТ

import Foundation
import SQLite3

class SQLMeasurements
{
    let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)  //FIXME: Why do i need this to work?

    private let database: SQLiteDatabase
    var index: Int = 1 //SQL indexes start at 1...TODO: violating the mutability principle

    init(database: SQLiteDatabase) {
        self.database = database
        let sql = """
        CREATE TABLE Measurements(
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            value Number(6) NOT NULL,
            unit BLOB NOT NULL
        );
        """
        do { try database.createTable(sql: sql) }
        catch { print(database.errorMessage) }
    }

    func add(value: Int, unit: Dimension) throws -> SQLMeasurement {
        let sql = "INSERT INTO Measurements (value, unit) VALUES (?,?);"
        let insertStatement = try database.prepareStatement(sql: sql)
        defer { sqlite3_finalize(insertStatement) } //Free Memory
        guard sqlite3_bind_int(insertStatement, 1, Int32(value)) == SQLITE_OK else { fatalError() }
        guard let dimensionData = try? NSKeyedArchiver.archivedData(withRootObject: unit, requiringSecureCoding: false) else { fatalError() }
        let rc = dimensionData.withUnsafeBytes {
            sqlite3_bind_blob(insertStatement, 2, $0.baseAddress, Int32(dimensionData.count), SQLITE_TRANSIENT)
        }
        guard rc == SQLITE_OK else { fatalError() }
        guard sqlite3_step(insertStatement) == SQLITE_DONE else { fatalError() }
        let lastId = database.lastId()
        return SQLMeasurement(id: lastId, db: database)
    }

///

import Foundation
import SQLite3

class SQLMeasurement
{
    private let db: SQLiteDatabase
    let id: Int

    init(id: Int, db: SQLiteDatabase) {    //FIXME: Construct w value and unit
        self.db = db
        self.id = id
    }

    func value() -> Int {
        let sql = "SELECT value FROM Measurements WHERE id = ?"
        let queryStatement = try! db.prepareStatement(sql: sql) //TODO: Remove Bang!
        guard sqlite3_bind_int(queryStatement, 1, Int32(id)) == SQLITE_OK else { fatalError() }
        guard sqlite3_step(queryStatement) == SQLITE_ROW else { fatalError() }
        let queryResultCol1 = sqlite3_column_int(queryStatement, 0)
        sqlite3_finalize(queryStatement)
        return Int(queryResultCol1)
    }

    func unit() -> Dimension {
        let sql = "SELECT unit FROM Measurements WHERE id = ?"
        guard let queryStatement = try? db.prepareStatement(sql: sql) else { fatalError() }
        defer { sqlite3_finalize(queryStatement) }
        guard sqlite3_bind_int(queryStatement, 1, Int32(id)) == SQLITE_OK else { fatalError() }
        guard sqlite3_step(queryStatement) == SQLITE_ROW else { fatalError() }
        guard let dataBlob = sqlite3_column_blob(queryStatement, 0) else { fatalError() }
        let dataBlobLength = sqlite3_column_bytes(queryStatement, 0)
        let data = Data(bytes: dataBlob, count: Int(dataBlobLength))
        guard let unit = try? NSKeyedUnarchiver.unarchivedObject(ofClass: Dimension.self, from: data) else { fatalError() }
        return unit
    }`

////

import XCTest
@testable import FitWrench

class MeasurementsTests: XCTestCase
{
    var measurements: SQLMeasurements!

    override func setUp() {
        let db = SQLiteDatabase()
        self.measurements = SQLMeasurements(database: db)
    }
    override func tearDown() { }

    func testAddMeasurement() {
        do {
            let measurement = try measurements.add(value: 90, unit: UnitPower.watts)
            XCTAssertEqual(measurement.value(), 90)
            XCTAssertEqual(measurement.unit(), UnitPower.watts)
        }
        catch {
            print(error.localizedDescription)
            XCTFail()
        }
    }

    func testRemoveMeasurement() { XCTAssertEqual(99, 100) }
}`

1 Ответ

1 голос
/ 21 марта 2020

Несколько замечаний:

  1. Связывание:

    Документация сообщает нам:

    Если четвертый параметр sqlite3_bind_blob() отрицателен, тогда поведение не определено.

    В исходной версии вашего вопроса (которую вы сейчас изменили), используйте -1 для этого четвертого параметра. Вы должны использовать dimensionData.count для этого четвертого параметра. В отличие от обработки строк, при отсутствии количества байтов sqlite3_bind_blob() не может узнать, сколько двоичных данных существует.

    Также обратите внимание, что третьим параметром sqlite3_bind_blob() должен быть dimensionData сам по себе, а не в base64 его представление. Например, используйте withUnsafeBytes, чтобы получить baseAddress, который мы можем предоставить sqlite3_bind_blob:

    let rc = dimensionData.withUnsafeBytes {
        sqlite3_bind_blob(statement, 1, $0.baseAddress, Int32(dimensionData.count), SQLITE_TRANSIENT)
    }
    guard rc == SQLITE_OK else {
        ...
    }
    
  2. Получение:

    sqlite3_column_blob и sqlite3_column_bytes оба используют индекс 2. Это должно быть 0, потому что BLOB является первым столбцом в наборе результатов (и потому что, в отличие от API связывания sqlite3_bind_xxx, функции sqlite3_column_xxx используют индекс на основе 0) , Как документация гласит:

    Крайний левый столбец набора результатов имеет индекс 0.

  3. Разархивирование:

    Чтобы разархивировать, вы разархивируете с data напрямую:

    let result = try NSKeyedUnarchiver.unarchivedObject(ofClass: Dimension.self, from: data)
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...