SqliteStatement.swift
109 lignes · 3420 octets
import Foundation import SQLite3 private let sqliteTransient = unsafeBitCast(-1, to: sqlite3_destructor_type.self) /// A single value bound to a `?` placeholder. enum SqliteValue { case null case int(Int64) case real(Double) case text(String) case blob(Data) static func optText(_ value: String?) -> SqliteValue { value.map { .text($0) } ?? .null } static func optInt(_ value: Int64?) -> SqliteValue { value.map { .int($0) } ?? .null } static func bool(_ value: Bool) -> SqliteValue { .int(value ? 1 : 0) } /// Room semantics for `@PrimaryKey(autoGenerate = true)`: 0 means "not set", /// bound as NULL so SQLite assigns the next rowid. static func rowId(_ id: Int64) -> SqliteValue { id == 0 ? .null : .int(id) } } /// Column accessors for the current row of a stepped statement. struct SqliteRow { let stmt: OpaquePointer func int64(_ index: Int32) -> Int64 { sqlite3_column_int64(stmt, index) } func int64OrNil(_ index: Int32) -> Int64? { isNull(index) ? nil : sqlite3_column_int64(stmt, index) } func int(_ index: Int32) -> Int { Int(sqlite3_column_int64(stmt, index)) } func bool(_ index: Int32) -> Bool { sqlite3_column_int64(stmt, index) != 0 } func text(_ index: Int32) -> String { textOrNil(index) ?? "" } func textOrNil(_ index: Int32) -> String? { guard let cString = sqlite3_column_text(stmt, index) else { return nil } return String(cString: cString) } private func isNull(_ index: Int32) -> Bool { sqlite3_column_type(stmt, index) == SQLITE_NULL } } /// Prepared-statement wrapper; finalizes on deinit. final class SqliteStatement { private let db: OpaquePointer private let stmt: OpaquePointer init(db: OpaquePointer, sql: String) throws { var stmt: OpaquePointer? guard sqlite3_prepare_v2(db, sql, -1, &stmt, nil) == SQLITE_OK, let stmt else { throw DatabaseError.last(db, while: "prepare: \(sql)") } self.db = db self.stmt = stmt } deinit { sqlite3_finalize(stmt) } func bind(_ values: [SqliteValue]) throws { for (offset, value) in values.enumerated() { let index = Int32(offset + 1) let rc: Int32 switch value { case .null: rc = sqlite3_bind_null(stmt, index) case .int(let n): rc = sqlite3_bind_int64(stmt, index, n) case .real(let d): rc = sqlite3_bind_double(stmt, index, d) case .text(let s): rc = sqlite3_bind_text(stmt, index, s, -1, sqliteTransient) case .blob(let data): rc = data.withUnsafeBytes { buffer in sqlite3_bind_blob(stmt, index, buffer.baseAddress, Int32(data.count), sqliteTransient) } } guard rc == SQLITE_OK else { throw DatabaseError.last(db, while: "bind parameter \(index)") } } } /// Steps once; true when a result row is available, false when done. func step() throws -> Bool { switch sqlite3_step(stmt) { case SQLITE_ROW: return true case SQLITE_DONE: return false default: throw DatabaseError.last(db, while: "step") } } var row: SqliteRow { SqliteRow(stmt: stmt) } }
GitRust