Archived
Push from command line
This commit is contained in:
+60
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// SQLite.swift
|
||||
// https://github.com/stephencelis/SQLite.swift
|
||||
// Copyright © 2014-2015 Stephen Celis.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
public struct Blob {
|
||||
|
||||
public let bytes: [UInt8]
|
||||
|
||||
public init(bytes: [UInt8]) {
|
||||
self.bytes = bytes
|
||||
}
|
||||
|
||||
public init(bytes: UnsafeRawPointer, length: Int) {
|
||||
let i8bufptr = UnsafeBufferPointer(start: bytes.assumingMemoryBound(to: UInt8.self), count: length)
|
||||
self.init(bytes: [UInt8](i8bufptr))
|
||||
}
|
||||
|
||||
public func toHex() -> String {
|
||||
return bytes.map {
|
||||
($0 < 16 ? "0" : "") + String($0, radix: 16, uppercase: false)
|
||||
}.joined(separator: "")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Blob : CustomStringConvertible {
|
||||
|
||||
public var description: String {
|
||||
return "x'\(toHex())'"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Blob : Equatable {
|
||||
|
||||
}
|
||||
|
||||
public func ==(lhs: Blob, rhs: Blob) -> Bool {
|
||||
return lhs.bytes == rhs.bytes
|
||||
}
|
||||
+750
@@ -0,0 +1,750 @@
|
||||
//
|
||||
// SQLite.swift
|
||||
// https://github.com/stephencelis/SQLite.swift
|
||||
// Copyright © 2014-2015 Stephen Celis.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Dispatch
|
||||
#if SQLITE_SWIFT_STANDALONE
|
||||
import sqlite3
|
||||
#elseif SQLITE_SWIFT_SQLCIPHER
|
||||
import SQLCipher
|
||||
#elseif os(Linux)
|
||||
import CSQLite
|
||||
#else
|
||||
import SQLite3
|
||||
#endif
|
||||
|
||||
/// A connection to SQLite.
|
||||
public final class Connection {
|
||||
|
||||
/// The location of a SQLite database.
|
||||
public enum Location {
|
||||
|
||||
/// An in-memory database (equivalent to `.uri(":memory:")`).
|
||||
///
|
||||
/// See: <https://www.sqlite.org/inmemorydb.html#sharedmemdb>
|
||||
case inMemory
|
||||
|
||||
/// A temporary, file-backed database (equivalent to `.uri("")`).
|
||||
///
|
||||
/// See: <https://www.sqlite.org/inmemorydb.html#temp_db>
|
||||
case temporary
|
||||
|
||||
/// A database located at the given URI filename (or path).
|
||||
///
|
||||
/// See: <https://www.sqlite.org/uri.html>
|
||||
///
|
||||
/// - Parameter filename: A URI filename
|
||||
case uri(String)
|
||||
}
|
||||
|
||||
/// An SQL operation passed to update callbacks.
|
||||
public enum Operation {
|
||||
|
||||
/// An INSERT operation.
|
||||
case insert
|
||||
|
||||
/// An UPDATE operation.
|
||||
case update
|
||||
|
||||
/// A DELETE operation.
|
||||
case delete
|
||||
|
||||
fileprivate init(rawValue:Int32) {
|
||||
switch rawValue {
|
||||
case SQLITE_INSERT:
|
||||
self = .insert
|
||||
case SQLITE_UPDATE:
|
||||
self = .update
|
||||
case SQLITE_DELETE:
|
||||
self = .delete
|
||||
default:
|
||||
fatalError("unhandled operation code: \(rawValue)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var handle: OpaquePointer { return _handle! }
|
||||
|
||||
fileprivate var _handle: OpaquePointer? = nil
|
||||
|
||||
/// Initializes a new SQLite connection.
|
||||
///
|
||||
/// - Parameters:
|
||||
///
|
||||
/// - location: The location of the database. Creates a new database if it
|
||||
/// doesn’t already exist (unless in read-only mode).
|
||||
///
|
||||
/// Default: `.inMemory`.
|
||||
///
|
||||
/// - readonly: Whether or not to open the database in a read-only state.
|
||||
///
|
||||
/// Default: `false`.
|
||||
///
|
||||
/// - Returns: A new database connection.
|
||||
public init(_ location: Location = .inMemory, readonly: Bool = false) throws {
|
||||
let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE
|
||||
try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX, nil))
|
||||
queue.setSpecific(key: Connection.queueKey, value: queueContext)
|
||||
}
|
||||
|
||||
/// Initializes a new connection to a database.
|
||||
///
|
||||
/// - Parameters:
|
||||
///
|
||||
/// - filename: The location of the database. Creates a new database if
|
||||
/// it doesn’t already exist (unless in read-only mode).
|
||||
///
|
||||
/// - readonly: Whether or not to open the database in a read-only state.
|
||||
///
|
||||
/// Default: `false`.
|
||||
///
|
||||
/// - Throws: `Result.Error` iff a connection cannot be established.
|
||||
///
|
||||
/// - Returns: A new database connection.
|
||||
public convenience init(_ filename: String, readonly: Bool = false) throws {
|
||||
try self.init(.uri(filename), readonly: readonly)
|
||||
}
|
||||
|
||||
deinit {
|
||||
sqlite3_close(handle)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
/// Whether or not the database was opened in a read-only state.
|
||||
public var readonly: Bool { return sqlite3_db_readonly(handle, nil) == 1 }
|
||||
|
||||
/// The last rowid inserted into the database via this connection.
|
||||
public var lastInsertRowid: Int64 {
|
||||
return sqlite3_last_insert_rowid(handle)
|
||||
}
|
||||
|
||||
/// The last number of changes (inserts, updates, or deletes) made to the
|
||||
/// database via this connection.
|
||||
public var changes: Int {
|
||||
return Int(sqlite3_changes(handle))
|
||||
}
|
||||
|
||||
/// The total number of changes (inserts, updates, or deletes) made to the
|
||||
/// database via this connection.
|
||||
public var totalChanges: Int {
|
||||
return Int(sqlite3_total_changes(handle))
|
||||
}
|
||||
|
||||
// MARK: - Execute
|
||||
|
||||
/// Executes a batch of SQL statements.
|
||||
///
|
||||
/// - Parameter SQL: A batch of zero or more semicolon-separated SQL
|
||||
/// statements.
|
||||
///
|
||||
/// - Throws: `Result.Error` if query execution fails.
|
||||
public func execute(_ SQL: String) throws {
|
||||
_ = try sync { try self.check(sqlite3_exec(self.handle, SQL, nil, nil, nil)) }
|
||||
}
|
||||
|
||||
// MARK: - Prepare
|
||||
|
||||
/// Prepares a single SQL statement (with optional parameter bindings).
|
||||
///
|
||||
/// - Parameters:
|
||||
///
|
||||
/// - statement: A single SQL statement.
|
||||
///
|
||||
/// - bindings: A list of parameters to bind to the statement.
|
||||
///
|
||||
/// - Returns: A prepared statement.
|
||||
public func prepare(_ statement: String, _ bindings: Binding?...) throws -> Statement {
|
||||
if !bindings.isEmpty { return try prepare(statement, bindings) }
|
||||
return try Statement(self, statement)
|
||||
}
|
||||
|
||||
/// Prepares a single SQL statement and binds parameters to it.
|
||||
///
|
||||
/// - Parameters:
|
||||
///
|
||||
/// - statement: A single SQL statement.
|
||||
///
|
||||
/// - bindings: A list of parameters to bind to the statement.
|
||||
///
|
||||
/// - Returns: A prepared statement.
|
||||
public func prepare(_ statement: String, _ bindings: [Binding?]) throws -> Statement {
|
||||
return try prepare(statement).bind(bindings)
|
||||
}
|
||||
|
||||
/// Prepares a single SQL statement and binds parameters to it.
|
||||
///
|
||||
/// - Parameters:
|
||||
///
|
||||
/// - statement: A single SQL statement.
|
||||
///
|
||||
/// - bindings: A dictionary of named parameters to bind to the statement.
|
||||
///
|
||||
/// - Returns: A prepared statement.
|
||||
public func prepare(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement {
|
||||
return try prepare(statement).bind(bindings)
|
||||
}
|
||||
|
||||
// MARK: - Run
|
||||
|
||||
/// Runs a single SQL statement (with optional parameter bindings).
|
||||
///
|
||||
/// - Parameters:
|
||||
///
|
||||
/// - statement: A single SQL statement.
|
||||
///
|
||||
/// - bindings: A list of parameters to bind to the statement.
|
||||
///
|
||||
/// - Throws: `Result.Error` if query execution fails.
|
||||
///
|
||||
/// - Returns: The statement.
|
||||
@discardableResult public func run(_ statement: String, _ bindings: Binding?...) throws -> Statement {
|
||||
return try run(statement, bindings)
|
||||
}
|
||||
|
||||
/// Prepares, binds, and runs a single SQL statement.
|
||||
///
|
||||
/// - Parameters:
|
||||
///
|
||||
/// - statement: A single SQL statement.
|
||||
///
|
||||
/// - bindings: A list of parameters to bind to the statement.
|
||||
///
|
||||
/// - Throws: `Result.Error` if query execution fails.
|
||||
///
|
||||
/// - Returns: The statement.
|
||||
@discardableResult public func run(_ statement: String, _ bindings: [Binding?]) throws -> Statement {
|
||||
return try prepare(statement).run(bindings)
|
||||
}
|
||||
|
||||
/// Prepares, binds, and runs a single SQL statement.
|
||||
///
|
||||
/// - Parameters:
|
||||
///
|
||||
/// - statement: A single SQL statement.
|
||||
///
|
||||
/// - bindings: A dictionary of named parameters to bind to the statement.
|
||||
///
|
||||
/// - Throws: `Result.Error` if query execution fails.
|
||||
///
|
||||
/// - Returns: The statement.
|
||||
@discardableResult public func run(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement {
|
||||
return try prepare(statement).run(bindings)
|
||||
}
|
||||
|
||||
// MARK: - Scalar
|
||||
|
||||
/// Runs a single SQL statement (with optional parameter bindings),
|
||||
/// returning the first value of the first row.
|
||||
///
|
||||
/// - Parameters:
|
||||
///
|
||||
/// - statement: A single SQL statement.
|
||||
///
|
||||
/// - bindings: A list of parameters to bind to the statement.
|
||||
///
|
||||
/// - Returns: The first value of the first row returned.
|
||||
public func scalar(_ statement: String, _ bindings: Binding?...) throws -> Binding? {
|
||||
return try scalar(statement, bindings)
|
||||
}
|
||||
|
||||
/// Runs a single SQL statement (with optional parameter bindings),
|
||||
/// returning the first value of the first row.
|
||||
///
|
||||
/// - Parameters:
|
||||
///
|
||||
/// - statement: A single SQL statement.
|
||||
///
|
||||
/// - bindings: A list of parameters to bind to the statement.
|
||||
///
|
||||
/// - Returns: The first value of the first row returned.
|
||||
public func scalar(_ statement: String, _ bindings: [Binding?]) throws -> Binding? {
|
||||
return try prepare(statement).scalar(bindings)
|
||||
}
|
||||
|
||||
/// Runs a single SQL statement (with optional parameter bindings),
|
||||
/// returning the first value of the first row.
|
||||
///
|
||||
/// - Parameters:
|
||||
///
|
||||
/// - statement: A single SQL statement.
|
||||
///
|
||||
/// - bindings: A dictionary of named parameters to bind to the statement.
|
||||
///
|
||||
/// - Returns: The first value of the first row returned.
|
||||
public func scalar(_ statement: String, _ bindings: [String: Binding?]) throws -> Binding? {
|
||||
return try prepare(statement).scalar(bindings)
|
||||
}
|
||||
|
||||
// MARK: - Transactions
|
||||
|
||||
/// The mode in which a transaction acquires a lock.
|
||||
public enum TransactionMode : String {
|
||||
|
||||
/// Defers locking the database till the first read/write executes.
|
||||
case deferred = "DEFERRED"
|
||||
|
||||
/// Immediately acquires a reserved lock on the database.
|
||||
case immediate = "IMMEDIATE"
|
||||
|
||||
/// Immediately acquires an exclusive lock on all databases.
|
||||
case exclusive = "EXCLUSIVE"
|
||||
|
||||
}
|
||||
|
||||
// TODO: Consider not requiring a throw to roll back?
|
||||
/// Runs a transaction with the given mode.
|
||||
///
|
||||
/// - Note: Transactions cannot be nested. To nest transactions, see
|
||||
/// `savepoint()`, instead.
|
||||
///
|
||||
/// - Parameters:
|
||||
///
|
||||
/// - mode: The mode in which a transaction acquires a lock.
|
||||
///
|
||||
/// Default: `.deferred`
|
||||
///
|
||||
/// - block: A closure to run SQL statements within the transaction.
|
||||
/// The transaction will be committed when the block returns. The block
|
||||
/// must throw to roll the transaction back.
|
||||
///
|
||||
/// - Throws: `Result.Error`, and rethrows.
|
||||
public func transaction(_ mode: TransactionMode = .deferred, block: () throws -> Void) throws {
|
||||
try transaction("BEGIN \(mode.rawValue) TRANSACTION", block, "COMMIT TRANSACTION", or: "ROLLBACK TRANSACTION")
|
||||
}
|
||||
|
||||
// TODO: Consider not requiring a throw to roll back?
|
||||
// TODO: Consider removing ability to set a name?
|
||||
/// Runs a transaction with the given savepoint name (if omitted, it will
|
||||
/// generate a UUID).
|
||||
///
|
||||
/// - SeeAlso: `transaction()`.
|
||||
///
|
||||
/// - Parameters:
|
||||
///
|
||||
/// - savepointName: A unique identifier for the savepoint (optional).
|
||||
///
|
||||
/// - block: A closure to run SQL statements within the transaction.
|
||||
/// The savepoint will be released (committed) when the block returns.
|
||||
/// The block must throw to roll the savepoint back.
|
||||
///
|
||||
/// - Throws: `SQLite.Result.Error`, and rethrows.
|
||||
public func savepoint(_ name: String = UUID().uuidString, block: () throws -> Void) throws {
|
||||
let name = name.quote("'")
|
||||
let savepoint = "SAVEPOINT \(name)"
|
||||
|
||||
try transaction(savepoint, block, "RELEASE \(savepoint)", or: "ROLLBACK TO \(savepoint)")
|
||||
}
|
||||
|
||||
fileprivate func transaction(_ begin: String, _ block: () throws -> Void, _ commit: String, or rollback: String) throws {
|
||||
return try sync {
|
||||
try self.run(begin)
|
||||
do {
|
||||
try block()
|
||||
try self.run(commit)
|
||||
} catch {
|
||||
try self.run(rollback)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Interrupts any long-running queries.
|
||||
public func interrupt() {
|
||||
sqlite3_interrupt(handle)
|
||||
}
|
||||
|
||||
// MARK: - Handlers
|
||||
|
||||
/// The number of seconds a connection will attempt to retry a statement
|
||||
/// after encountering a busy signal (lock).
|
||||
public var busyTimeout: Double = 0 {
|
||||
didSet {
|
||||
sqlite3_busy_timeout(handle, Int32(busyTimeout * 1_000))
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a handler to call after encountering a busy signal (lock).
|
||||
///
|
||||
/// - Parameter callback: This block is executed during a lock in which a
|
||||
/// busy error would otherwise be returned. It’s passed the number of
|
||||
/// times it’s been called for this lock. If it returns `true`, it will
|
||||
/// try again. If it returns `false`, no further attempts will be made.
|
||||
public func busyHandler(_ callback: ((_ tries: Int) -> Bool)?) {
|
||||
guard let callback = callback else {
|
||||
sqlite3_busy_handler(handle, nil, nil)
|
||||
busyHandler = nil
|
||||
return
|
||||
}
|
||||
|
||||
let box: BusyHandler = { callback(Int($0)) ? 1 : 0 }
|
||||
sqlite3_busy_handler(handle, { callback, tries in
|
||||
unsafeBitCast(callback, to: BusyHandler.self)(tries)
|
||||
}, unsafeBitCast(box, to: UnsafeMutableRawPointer.self))
|
||||
busyHandler = box
|
||||
}
|
||||
fileprivate typealias BusyHandler = @convention(block) (Int32) -> Int32
|
||||
fileprivate var busyHandler: BusyHandler?
|
||||
|
||||
/// Sets a handler to call when a statement is executed with the compiled
|
||||
/// SQL.
|
||||
///
|
||||
/// - Parameter callback: This block is invoked when a statement is executed
|
||||
/// with the compiled SQL as its argument.
|
||||
///
|
||||
/// db.trace { SQL in print(SQL) }
|
||||
public func trace(_ callback: ((String) -> Void)?) {
|
||||
#if SQLITE_SWIFT_SQLCIPHER || os(Linux)
|
||||
trace_v1(callback)
|
||||
#else
|
||||
if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
|
||||
trace_v2(callback)
|
||||
} else {
|
||||
trace_v1(callback)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
fileprivate func trace_v1(_ callback: ((String) -> Void)?) {
|
||||
guard let callback = callback else {
|
||||
sqlite3_trace(handle, nil /* xCallback */, nil /* pCtx */)
|
||||
trace = nil
|
||||
return
|
||||
}
|
||||
let box: Trace = { (pointer: UnsafeRawPointer) in
|
||||
callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self)))
|
||||
}
|
||||
sqlite3_trace(handle,
|
||||
{
|
||||
(C: UnsafeMutableRawPointer?, SQL: UnsafePointer<Int8>?) in
|
||||
if let C = C, let SQL = SQL {
|
||||
unsafeBitCast(C, to: Trace.self)(SQL)
|
||||
}
|
||||
},
|
||||
unsafeBitCast(box, to: UnsafeMutableRawPointer.self)
|
||||
)
|
||||
trace = box
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
fileprivate typealias Trace = @convention(block) (UnsafeRawPointer) -> Void
|
||||
fileprivate var trace: Trace?
|
||||
|
||||
/// Registers a callback to be invoked whenever a row is inserted, updated,
|
||||
/// or deleted in a rowid table.
|
||||
///
|
||||
/// - Parameter callback: A callback invoked with the `Operation` (one of
|
||||
/// `.Insert`, `.Update`, or `.Delete`), database name, table name, and
|
||||
/// rowid.
|
||||
public func updateHook(_ callback: ((_ operation: Operation, _ db: String, _ table: String, _ rowid: Int64) -> Void)?) {
|
||||
guard let callback = callback else {
|
||||
sqlite3_update_hook(handle, nil, nil)
|
||||
updateHook = nil
|
||||
return
|
||||
}
|
||||
|
||||
let box: UpdateHook = {
|
||||
callback(
|
||||
Operation(rawValue: $0),
|
||||
String(cString: $1),
|
||||
String(cString: $2),
|
||||
$3
|
||||
)
|
||||
}
|
||||
sqlite3_update_hook(handle, { callback, operation, db, table, rowid in
|
||||
unsafeBitCast(callback, to: UpdateHook.self)(operation, db!, table!, rowid)
|
||||
}, unsafeBitCast(box, to: UnsafeMutableRawPointer.self))
|
||||
updateHook = box
|
||||
}
|
||||
fileprivate typealias UpdateHook = @convention(block) (Int32, UnsafePointer<Int8>, UnsafePointer<Int8>, Int64) -> Void
|
||||
fileprivate var updateHook: UpdateHook?
|
||||
|
||||
/// Registers a callback to be invoked whenever a transaction is committed.
|
||||
///
|
||||
/// - Parameter callback: A callback invoked whenever a transaction is
|
||||
/// committed. If this callback throws, the transaction will be rolled
|
||||
/// back.
|
||||
public func commitHook(_ callback: (() throws -> Void)?) {
|
||||
guard let callback = callback else {
|
||||
sqlite3_commit_hook(handle, nil, nil)
|
||||
commitHook = nil
|
||||
return
|
||||
}
|
||||
|
||||
let box: CommitHook = {
|
||||
do {
|
||||
try callback()
|
||||
} catch {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
sqlite3_commit_hook(handle, { callback in
|
||||
unsafeBitCast(callback, to: CommitHook.self)()
|
||||
}, unsafeBitCast(box, to: UnsafeMutableRawPointer.self))
|
||||
commitHook = box
|
||||
}
|
||||
fileprivate typealias CommitHook = @convention(block) () -> Int32
|
||||
fileprivate var commitHook: CommitHook?
|
||||
|
||||
/// Registers a callback to be invoked whenever a transaction rolls back.
|
||||
///
|
||||
/// - Parameter callback: A callback invoked when a transaction is rolled
|
||||
/// back.
|
||||
public func rollbackHook(_ callback: (() -> Void)?) {
|
||||
guard let callback = callback else {
|
||||
sqlite3_rollback_hook(handle, nil, nil)
|
||||
rollbackHook = nil
|
||||
return
|
||||
}
|
||||
|
||||
let box: RollbackHook = { callback() }
|
||||
sqlite3_rollback_hook(handle, { callback in
|
||||
unsafeBitCast(callback, to: RollbackHook.self)()
|
||||
}, unsafeBitCast(box, to: UnsafeMutableRawPointer.self))
|
||||
rollbackHook = box
|
||||
}
|
||||
fileprivate typealias RollbackHook = @convention(block) () -> Void
|
||||
fileprivate var rollbackHook: RollbackHook?
|
||||
|
||||
/// Creates or redefines a custom SQL function.
|
||||
///
|
||||
/// - Parameters:
|
||||
///
|
||||
/// - function: The name of the function to create or redefine.
|
||||
///
|
||||
/// - argumentCount: The number of arguments that the function takes. If
|
||||
/// `nil`, the function may take any number of arguments.
|
||||
///
|
||||
/// Default: `nil`
|
||||
///
|
||||
/// - deterministic: Whether or not the function is deterministic (_i.e._
|
||||
/// the function always returns the same result for a given input).
|
||||
///
|
||||
/// Default: `false`
|
||||
///
|
||||
/// - block: A block of code to run when the function is called. The block
|
||||
/// is called with an array of raw SQL values mapped to the function’s
|
||||
/// parameters and should return a raw SQL value (or nil).
|
||||
public func createFunction(_ function: String, argumentCount: UInt? = nil, deterministic: Bool = false, _ block: @escaping (_ args: [Binding?]) -> Binding?) {
|
||||
let argc = argumentCount.map { Int($0) } ?? -1
|
||||
let box: Function = { context, argc, argv in
|
||||
let arguments: [Binding?] = (0..<Int(argc)).map { idx in
|
||||
let value = argv![idx]
|
||||
switch sqlite3_value_type(value) {
|
||||
case SQLITE_BLOB:
|
||||
return Blob(bytes: sqlite3_value_blob(value), length: Int(sqlite3_value_bytes(value)))
|
||||
case SQLITE_FLOAT:
|
||||
return sqlite3_value_double(value)
|
||||
case SQLITE_INTEGER:
|
||||
return sqlite3_value_int64(value)
|
||||
case SQLITE_NULL:
|
||||
return nil
|
||||
case SQLITE_TEXT:
|
||||
return String(cString: UnsafePointer(sqlite3_value_text(value)))
|
||||
case let type:
|
||||
fatalError("unsupported value type: \(type)")
|
||||
}
|
||||
}
|
||||
let result = block(arguments)
|
||||
if let result = result as? Blob {
|
||||
sqlite3_result_blob(context, result.bytes, Int32(result.bytes.count), nil)
|
||||
} else if let result = result as? Double {
|
||||
sqlite3_result_double(context, result)
|
||||
} else if let result = result as? Int64 {
|
||||
sqlite3_result_int64(context, result)
|
||||
} else if let result = result as? String {
|
||||
sqlite3_result_text(context, result, Int32(result.count), SQLITE_TRANSIENT)
|
||||
} else if result == nil {
|
||||
sqlite3_result_null(context)
|
||||
} else {
|
||||
fatalError("unsupported result type: \(String(describing: result))")
|
||||
}
|
||||
}
|
||||
var flags = SQLITE_UTF8
|
||||
#if !os(Linux)
|
||||
if deterministic {
|
||||
flags |= SQLITE_DETERMINISTIC
|
||||
}
|
||||
#endif
|
||||
sqlite3_create_function_v2(handle, function, Int32(argc), flags, unsafeBitCast(box, to: UnsafeMutableRawPointer.self), { context, argc, value in
|
||||
let function = unsafeBitCast(sqlite3_user_data(context), to: Function.self)
|
||||
function(context, argc, value)
|
||||
}, nil, nil, nil)
|
||||
if functions[function] == nil { self.functions[function] = [:] }
|
||||
functions[function]?[argc] = box
|
||||
}
|
||||
fileprivate typealias Function = @convention(block) (OpaquePointer?, Int32, UnsafeMutablePointer<OpaquePointer?>?) -> Void
|
||||
fileprivate var functions = [String: [Int: Function]]()
|
||||
|
||||
/// Defines a new collating sequence.
|
||||
///
|
||||
/// - Parameters:
|
||||
///
|
||||
/// - collation: The name of the collation added.
|
||||
///
|
||||
/// - block: A collation function that takes two strings and returns the
|
||||
/// comparison result.
|
||||
public func createCollation(_ collation: String, _ block: @escaping (_ lhs: String, _ rhs: String) -> ComparisonResult) throws {
|
||||
let box: Collation = { (lhs: UnsafeRawPointer, rhs: UnsafeRawPointer) in
|
||||
let lstr = String(cString: lhs.assumingMemoryBound(to: UInt8.self))
|
||||
let rstr = String(cString: rhs.assumingMemoryBound(to: UInt8.self))
|
||||
return Int32(block(lstr, rstr).rawValue)
|
||||
}
|
||||
try check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8,
|
||||
unsafeBitCast(box, to: UnsafeMutableRawPointer.self),
|
||||
{ (callback: UnsafeMutableRawPointer?, _, lhs: UnsafeRawPointer?, _, rhs: UnsafeRawPointer?) in /* xCompare */
|
||||
if let lhs = lhs, let rhs = rhs {
|
||||
return unsafeBitCast(callback, to: Collation.self)(lhs, rhs)
|
||||
} else {
|
||||
fatalError("sqlite3_create_collation_v2 callback called with NULL pointer")
|
||||
}
|
||||
}, nil /* xDestroy */))
|
||||
collations[collation] = box
|
||||
}
|
||||
fileprivate typealias Collation = @convention(block) (UnsafeRawPointer, UnsafeRawPointer) -> Int32
|
||||
fileprivate var collations = [String: Collation]()
|
||||
|
||||
// MARK: - Error Handling
|
||||
|
||||
func sync<T>(_ block: () throws -> T) rethrows -> T {
|
||||
if DispatchQueue.getSpecific(key: Connection.queueKey) == queueContext {
|
||||
return try block()
|
||||
} else {
|
||||
return try queue.sync(execute: block)
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult func check(_ resultCode: Int32, statement: Statement? = nil) throws -> Int32 {
|
||||
guard let error = Result(errorCode: resultCode, connection: self, statement: statement) else {
|
||||
return resultCode
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
fileprivate var queue = DispatchQueue(label: "SQLite.Database", attributes: [])
|
||||
|
||||
fileprivate static let queueKey = DispatchSpecificKey<Int>()
|
||||
|
||||
fileprivate lazy var queueContext: Int = unsafeBitCast(self, to: Int.self)
|
||||
|
||||
}
|
||||
|
||||
extension Connection : CustomStringConvertible {
|
||||
|
||||
public var description: String {
|
||||
return String(cString: sqlite3_db_filename(handle, nil))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Connection.Location : CustomStringConvertible {
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .inMemory:
|
||||
return ":memory:"
|
||||
case .temporary:
|
||||
return ""
|
||||
case .uri(let URI):
|
||||
return URI
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum Result : Error {
|
||||
|
||||
fileprivate static let successCodes: Set = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE]
|
||||
|
||||
/// Represents a SQLite specific [error code](https://sqlite.org/rescode.html)
|
||||
///
|
||||
/// - message: English-language text that describes the error
|
||||
///
|
||||
/// - code: SQLite [error code](https://sqlite.org/rescode.html#primary_result_code_list)
|
||||
///
|
||||
/// - statement: the statement which produced the error
|
||||
case error(message: String, code: Int32, statement: Statement?)
|
||||
|
||||
init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) {
|
||||
guard !Result.successCodes.contains(errorCode) else { return nil }
|
||||
|
||||
let message = String(cString: sqlite3_errmsg(connection.handle))
|
||||
self = .error(message: message, code: errorCode, statement: statement)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Result : CustomStringConvertible {
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case let .error(message, errorCode, statement):
|
||||
if let statement = statement {
|
||||
return "\(message) (\(statement)) (code: \(errorCode))"
|
||||
} else {
|
||||
return "\(message) (code: \(errorCode))"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !SQLITE_SWIFT_SQLCIPHER && !os(Linux)
|
||||
@available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *)
|
||||
extension Connection {
|
||||
fileprivate func trace_v2(_ callback: ((String) -> Void)?) {
|
||||
guard let callback = callback else {
|
||||
// If the X callback is NULL or if the M mask is zero, then tracing is disabled.
|
||||
sqlite3_trace_v2(handle, 0 /* mask */, nil /* xCallback */, nil /* pCtx */)
|
||||
trace = nil
|
||||
return
|
||||
}
|
||||
|
||||
let box: Trace = { (pointer: UnsafeRawPointer) in
|
||||
callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self)))
|
||||
}
|
||||
sqlite3_trace_v2(handle,
|
||||
UInt32(SQLITE_TRACE_STMT) /* mask */,
|
||||
{
|
||||
// A trace callback is invoked with four arguments: callback(T,C,P,X).
|
||||
// The T argument is one of the SQLITE_TRACE constants to indicate why the
|
||||
// callback was invoked. The C argument is a copy of the context pointer.
|
||||
// The P and X arguments are pointers whose meanings depend on T.
|
||||
(T: UInt32, C: UnsafeMutableRawPointer?, P: UnsafeMutableRawPointer?, X: UnsafeMutableRawPointer?) in
|
||||
if let P = P,
|
||||
let expandedSQL = sqlite3_expanded_sql(OpaquePointer(P)) {
|
||||
unsafeBitCast(C, to: Trace.self)(expandedSQL)
|
||||
sqlite3_free(expandedSQL)
|
||||
}
|
||||
return Int32(0) // currently ignored
|
||||
},
|
||||
unsafeBitCast(box, to: UnsafeMutableRawPointer.self) /* pCtx */
|
||||
)
|
||||
trace = box
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import Foundation
|
||||
|
||||
public enum QueryError: Error, CustomStringConvertible {
|
||||
case noSuchTable(name: String)
|
||||
case noSuchColumn(name: String, columns: [String])
|
||||
case ambiguousColumn(name: String, similar: [String])
|
||||
case unexpectedNullValue(name: String)
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .noSuchTable(let name):
|
||||
return "No such table: \(name)"
|
||||
case .noSuchColumn(let name, let columns):
|
||||
return "No such column `\(name)` in columns \(columns)"
|
||||
case .ambiguousColumn(let name, let similar):
|
||||
return "Ambiguous column `\(name)` (please disambiguate: \(similar))"
|
||||
case .unexpectedNullValue(let name):
|
||||
return "Unexpected null value for column `\(name)`"
|
||||
}
|
||||
}
|
||||
}
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
//
|
||||
// SQLite.swift
|
||||
// https://github.com/stephencelis/SQLite.swift
|
||||
// Copyright © 2014-2015 Stephen Celis.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
#if SQLITE_SWIFT_STANDALONE
|
||||
import sqlite3
|
||||
#elseif SQLITE_SWIFT_SQLCIPHER
|
||||
import SQLCipher
|
||||
#elseif os(Linux)
|
||||
import CSQLite
|
||||
#else
|
||||
import SQLite3
|
||||
#endif
|
||||
|
||||
/// A single SQL statement.
|
||||
public final class Statement {
|
||||
|
||||
fileprivate var handle: OpaquePointer? = nil
|
||||
|
||||
fileprivate let connection: Connection
|
||||
|
||||
init(_ connection: Connection, _ SQL: String) throws {
|
||||
self.connection = connection
|
||||
try connection.check(sqlite3_prepare_v2(connection.handle, SQL, -1, &handle, nil))
|
||||
}
|
||||
|
||||
deinit {
|
||||
sqlite3_finalize(handle)
|
||||
}
|
||||
|
||||
public lazy var columnCount: Int = Int(sqlite3_column_count(self.handle))
|
||||
|
||||
public lazy var columnNames: [String] = (0..<Int32(self.columnCount)).map {
|
||||
String(cString: sqlite3_column_name(self.handle, $0))
|
||||
}
|
||||
|
||||
/// A cursor pointing to the current row.
|
||||
public lazy var row: Cursor = Cursor(self)
|
||||
|
||||
/// Binds a list of parameters to a statement.
|
||||
///
|
||||
/// - Parameter values: A list of parameters to bind to the statement.
|
||||
///
|
||||
/// - Returns: The statement object (useful for chaining).
|
||||
public func bind(_ values: Binding?...) -> Statement {
|
||||
return bind(values)
|
||||
}
|
||||
|
||||
/// Binds a list of parameters to a statement.
|
||||
///
|
||||
/// - Parameter values: A list of parameters to bind to the statement.
|
||||
///
|
||||
/// - Returns: The statement object (useful for chaining).
|
||||
public func bind(_ values: [Binding?]) -> Statement {
|
||||
if values.isEmpty { return self }
|
||||
reset()
|
||||
guard values.count == Int(sqlite3_bind_parameter_count(handle)) else {
|
||||
fatalError("\(sqlite3_bind_parameter_count(handle)) values expected, \(values.count) passed")
|
||||
}
|
||||
for idx in 1...values.count { bind(values[idx - 1], atIndex: idx) }
|
||||
return self
|
||||
}
|
||||
|
||||
/// Binds a dictionary of named parameters to a statement.
|
||||
///
|
||||
/// - Parameter values: A dictionary of named parameters to bind to the
|
||||
/// statement.
|
||||
///
|
||||
/// - Returns: The statement object (useful for chaining).
|
||||
public func bind(_ values: [String: Binding?]) -> Statement {
|
||||
reset()
|
||||
for (name, value) in values {
|
||||
let idx = sqlite3_bind_parameter_index(handle, name)
|
||||
guard idx > 0 else {
|
||||
fatalError("parameter not found: \(name)")
|
||||
}
|
||||
bind(value, atIndex: Int(idx))
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
fileprivate func bind(_ value: Binding?, atIndex idx: Int) {
|
||||
if value == nil {
|
||||
sqlite3_bind_null(handle, Int32(idx))
|
||||
} else if let value = value as? Blob {
|
||||
sqlite3_bind_blob(handle, Int32(idx), value.bytes, Int32(value.bytes.count), SQLITE_TRANSIENT)
|
||||
} else if let value = value as? Double {
|
||||
sqlite3_bind_double(handle, Int32(idx), value)
|
||||
} else if let value = value as? Int64 {
|
||||
sqlite3_bind_int64(handle, Int32(idx), value)
|
||||
} else if let value = value as? String {
|
||||
sqlite3_bind_text(handle, Int32(idx), value, -1, SQLITE_TRANSIENT)
|
||||
} else if let value = value as? Int {
|
||||
self.bind(value.datatypeValue, atIndex: idx)
|
||||
} else if let value = value as? Bool {
|
||||
self.bind(value.datatypeValue, atIndex: idx)
|
||||
} else if let value = value {
|
||||
fatalError("tried to bind unexpected value \(value)")
|
||||
}
|
||||
}
|
||||
|
||||
/// - Parameter bindings: A list of parameters to bind to the statement.
|
||||
///
|
||||
/// - Throws: `Result.Error` if query execution fails.
|
||||
///
|
||||
/// - Returns: The statement object (useful for chaining).
|
||||
@discardableResult public func run(_ bindings: Binding?...) throws -> Statement {
|
||||
guard bindings.isEmpty else {
|
||||
return try run(bindings)
|
||||
}
|
||||
|
||||
reset(clearBindings: false)
|
||||
repeat {} while try step()
|
||||
return self
|
||||
}
|
||||
|
||||
/// - Parameter bindings: A list of parameters to bind to the statement.
|
||||
///
|
||||
/// - Throws: `Result.Error` if query execution fails.
|
||||
///
|
||||
/// - Returns: The statement object (useful for chaining).
|
||||
@discardableResult public func run(_ bindings: [Binding?]) throws -> Statement {
|
||||
return try bind(bindings).run()
|
||||
}
|
||||
|
||||
/// - Parameter bindings: A dictionary of named parameters to bind to the
|
||||
/// statement.
|
||||
///
|
||||
/// - Throws: `Result.Error` if query execution fails.
|
||||
///
|
||||
/// - Returns: The statement object (useful for chaining).
|
||||
@discardableResult public func run(_ bindings: [String: Binding?]) throws -> Statement {
|
||||
return try bind(bindings).run()
|
||||
}
|
||||
|
||||
/// - Parameter bindings: A list of parameters to bind to the statement.
|
||||
///
|
||||
/// - Returns: The first value of the first row returned.
|
||||
public func scalar(_ bindings: Binding?...) throws -> Binding? {
|
||||
guard bindings.isEmpty else {
|
||||
return try scalar(bindings)
|
||||
}
|
||||
|
||||
reset(clearBindings: false)
|
||||
_ = try step()
|
||||
return row[0]
|
||||
}
|
||||
|
||||
/// - Parameter bindings: A list of parameters to bind to the statement.
|
||||
///
|
||||
/// - Returns: The first value of the first row returned.
|
||||
public func scalar(_ bindings: [Binding?]) throws -> Binding? {
|
||||
return try bind(bindings).scalar()
|
||||
}
|
||||
|
||||
|
||||
/// - Parameter bindings: A dictionary of named parameters to bind to the
|
||||
/// statement.
|
||||
///
|
||||
/// - Returns: The first value of the first row returned.
|
||||
public func scalar(_ bindings: [String: Binding?]) throws -> Binding? {
|
||||
return try bind(bindings).scalar()
|
||||
}
|
||||
|
||||
public func step() throws -> Bool {
|
||||
return try connection.sync { try self.connection.check(sqlite3_step(self.handle)) == SQLITE_ROW }
|
||||
}
|
||||
|
||||
fileprivate func reset(clearBindings shouldClear: Bool = true) {
|
||||
sqlite3_reset(handle)
|
||||
if (shouldClear) { sqlite3_clear_bindings(handle) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Statement : Sequence {
|
||||
|
||||
public func makeIterator() -> Statement {
|
||||
reset(clearBindings: false)
|
||||
return self
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public protocol FailableIterator : IteratorProtocol {
|
||||
func failableNext() throws -> Self.Element?
|
||||
}
|
||||
|
||||
extension FailableIterator {
|
||||
public func next() -> Element? {
|
||||
return try! failableNext()
|
||||
}
|
||||
}
|
||||
|
||||
extension Array {
|
||||
public init<I: FailableIterator>(_ failableIterator: I) throws where I.Element == Element {
|
||||
self.init()
|
||||
while let row = try failableIterator.failableNext() {
|
||||
append(row)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Statement : FailableIterator {
|
||||
public typealias Element = [Binding?]
|
||||
public func failableNext() throws -> [Binding?]? {
|
||||
return try step() ? Array(row) : nil
|
||||
}
|
||||
}
|
||||
|
||||
extension Statement : CustomStringConvertible {
|
||||
|
||||
public var description: String {
|
||||
return String(cString: sqlite3_sql(handle))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct Cursor {
|
||||
|
||||
fileprivate let handle: OpaquePointer
|
||||
|
||||
fileprivate let columnCount: Int
|
||||
|
||||
fileprivate init(_ statement: Statement) {
|
||||
handle = statement.handle!
|
||||
columnCount = statement.columnCount
|
||||
}
|
||||
|
||||
public subscript(idx: Int) -> Double {
|
||||
return sqlite3_column_double(handle, Int32(idx))
|
||||
}
|
||||
|
||||
public subscript(idx: Int) -> Int64 {
|
||||
return sqlite3_column_int64(handle, Int32(idx))
|
||||
}
|
||||
|
||||
public subscript(idx: Int) -> String {
|
||||
return String(cString: UnsafePointer(sqlite3_column_text(handle, Int32(idx))))
|
||||
}
|
||||
|
||||
public subscript(idx: Int) -> Blob {
|
||||
if let pointer = sqlite3_column_blob(handle, Int32(idx)) {
|
||||
let length = Int(sqlite3_column_bytes(handle, Int32(idx)))
|
||||
return Blob(bytes: pointer, length: length)
|
||||
} else {
|
||||
// The return value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer.
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
return Blob(bytes: [])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
public subscript(idx: Int) -> Bool {
|
||||
return Bool.fromDatatypeValue(self[idx])
|
||||
}
|
||||
|
||||
public subscript(idx: Int) -> Int {
|
||||
return Int.fromDatatypeValue(self[idx])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Cursors provide direct access to a statement’s current row.
|
||||
extension Cursor : Sequence {
|
||||
|
||||
public subscript(idx: Int) -> Binding? {
|
||||
switch sqlite3_column_type(handle, Int32(idx)) {
|
||||
case SQLITE_BLOB:
|
||||
return self[idx] as Blob
|
||||
case SQLITE_FLOAT:
|
||||
return self[idx] as Double
|
||||
case SQLITE_INTEGER:
|
||||
return self[idx] as Int64
|
||||
case SQLITE_NULL:
|
||||
return nil
|
||||
case SQLITE_TEXT:
|
||||
return self[idx] as String
|
||||
case let type:
|
||||
fatalError("unsupported column type: \(type)")
|
||||
}
|
||||
}
|
||||
|
||||
public func makeIterator() -> AnyIterator<Binding?> {
|
||||
var idx = 0
|
||||
return AnyIterator {
|
||||
if idx >= self.columnCount {
|
||||
return Optional<Binding?>.none
|
||||
} else {
|
||||
idx += 1
|
||||
return self[idx - 1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// SQLite.swift
|
||||
// https://github.com/stephencelis/SQLite.swift
|
||||
// Copyright © 2014-2015 Stephen Celis.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
/// - Warning: `Binding` is a protocol that SQLite.swift uses internally to
|
||||
/// directly map SQLite types to Swift types.
|
||||
///
|
||||
/// Do not conform custom types to the Binding protocol. See the `Value`
|
||||
/// protocol, instead.
|
||||
public protocol Binding {}
|
||||
|
||||
public protocol Number : Binding {}
|
||||
|
||||
public protocol Value : Expressible { // extensions cannot have inheritance clauses
|
||||
|
||||
associatedtype ValueType = Self
|
||||
|
||||
associatedtype Datatype : Binding
|
||||
|
||||
static var declaredDatatype: String { get }
|
||||
|
||||
static func fromDatatypeValue(_ datatypeValue: Datatype) -> ValueType
|
||||
|
||||
var datatypeValue: Datatype { get }
|
||||
|
||||
}
|
||||
|
||||
extension Double : Number, Value {
|
||||
|
||||
public static let declaredDatatype = "REAL"
|
||||
|
||||
public static func fromDatatypeValue(_ datatypeValue: Double) -> Double {
|
||||
return datatypeValue
|
||||
}
|
||||
|
||||
public var datatypeValue: Double {
|
||||
return self
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Int64 : Number, Value {
|
||||
|
||||
public static let declaredDatatype = "INTEGER"
|
||||
|
||||
public static func fromDatatypeValue(_ datatypeValue: Int64) -> Int64 {
|
||||
return datatypeValue
|
||||
}
|
||||
|
||||
public var datatypeValue: Int64 {
|
||||
return self
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension String : Binding, Value {
|
||||
|
||||
public static let declaredDatatype = "TEXT"
|
||||
|
||||
public static func fromDatatypeValue(_ datatypeValue: String) -> String {
|
||||
return datatypeValue
|
||||
}
|
||||
|
||||
public var datatypeValue: String {
|
||||
return self
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Blob : Binding, Value {
|
||||
|
||||
public static let declaredDatatype = "BLOB"
|
||||
|
||||
public static func fromDatatypeValue(_ datatypeValue: Blob) -> Blob {
|
||||
return datatypeValue
|
||||
}
|
||||
|
||||
public var datatypeValue: Blob {
|
||||
return self
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
extension Bool : Binding, Value {
|
||||
|
||||
public static var declaredDatatype = Int64.declaredDatatype
|
||||
|
||||
public static func fromDatatypeValue(_ datatypeValue: Int64) -> Bool {
|
||||
return datatypeValue != 0
|
||||
}
|
||||
|
||||
public var datatypeValue: Int64 {
|
||||
return self ? 1 : 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Int : Number, Value {
|
||||
|
||||
public static var declaredDatatype = Int64.declaredDatatype
|
||||
|
||||
public static func fromDatatypeValue(_ datatypeValue: Int64) -> Int {
|
||||
return Int(datatypeValue)
|
||||
}
|
||||
|
||||
public var datatypeValue: Int64 {
|
||||
return Int64(self)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user