Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make each SQLiteConnection have its own SQLite handle #50

Merged
merged 10 commits into from
Feb 6, 2019
58 changes: 34 additions & 24 deletions Sources/SQLite/Database/SQLiteConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,41 +23,46 @@ public final class SQLiteConnection: BasicWorker, DatabaseConnection, DatabaseQu
public typealias Database = SQLiteDatabase

/// See `DatabaseConnection`.
public var isClosed: Bool

/// See `BasicWorker`.
public let eventLoop: EventLoop

public var isClosed: Bool = false

/// See `DatabaseConnection`.
public var extend: Extend

public var extend: Extend = [:]
dpgao marked this conversation as resolved.
Show resolved Hide resolved
/// Optional logger, if set queries should be logged to it.
public var logger: DatabaseLogger?

/// Reference to parent `SQLiteDatabase` that created this connection.
/// This reference will ensure the DB stays alive since this connection uses
/// it's C pointer handle.
internal let database: SQLiteDatabase

/// it's dispatch queue.
private let database: SQLiteDatabase

/// Internal SQLite database handle.
internal let handle: OpaquePointer

/// See `BasicWorker`.
public let eventLoop: EventLoop

/// Create a new SQLite conncetion.
internal init(database: SQLiteDatabase, on worker: Worker) {
internal init(database: SQLiteDatabase, on worker: Worker) throws {
self.database = database
// Make database connection
let options = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX
var handle: OpaquePointer?
guard sqlite3_open_v2(database.storage.path, &handle, options, nil) == SQLITE_OK, let c = handle else {
throw SQLiteError(problem: .error, reason: "Could not open database.", source: .capture())
}
self.handle = c
self.eventLoop = worker.eventLoop
self.extend = [:]
self.isClosed = false
}

/// Returns an identifier for the last inserted row.
public var lastAutoincrementID: Int64? {
return sqlite3_last_insert_rowid(database.handle)
return sqlite3_last_insert_rowid(handle)
}

/// Returns the last error message, if one exists.
internal var errorMessage: String? {
guard let raw = sqlite3_errmsg(database.handle) else {
return nil
}
return String(cString: raw)
return sqlite3_errmsg(handle).map(String.init(cString:))
}

/// See `SQLConnection`.
Expand All @@ -78,11 +83,11 @@ public final class SQLiteConnection: BasicWorker, DatabaseConnection, DatabaseQu
public func query(_ query: SQLiteQuery, _ onRow: @escaping ([SQLiteColumn: SQLiteData]) throws -> ()) -> Future<Void> {
var binds: [Encodable] = []
let sql = query.serialize(&binds)
let promise = eventLoop.newPromise(Void.self)
let data = try! binds.map { try SQLiteDataEncoder().encode($0) }
// log before anything happens, in case there's an error
logger?.record(query: sql, values: data.map { $0.description })
database.blockingIO.submit { state in
let promise = eventLoop.newPromise(Void.self)
database.blockingIO.submit { _ in
do {
let statement = try SQLiteStatement(query: sql, on: self)
try statement.bind(data)
Expand All @@ -97,9 +102,9 @@ public final class SQLiteConnection: BasicWorker, DatabaseConnection, DatabaseQu
}
}
}
return promise.succeed(result: ())
promise.succeed(result: ())
} catch {
return promise.fail(error: error)
promise.fail(error: error)
}
}
return promise.futureResult
Expand All @@ -109,4 +114,9 @@ public final class SQLiteConnection: BasicWorker, DatabaseConnection, DatabaseQu
public func close() {
isClosed = true
}

/// Closes the open SQLite handle on deinit.
deinit {
sqlite3_close(handle)
}
}
35 changes: 9 additions & 26 deletions Sources/SQLite/Database/SQLiteDatabase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,48 +20,31 @@ public final class SQLiteDatabase: Database, LogSupporting {
/// Thread pool for performing blocking IO work. See `BlockingIOThreadPool`.
internal let blockingIO: BlockingIOThreadPool

/// Internal SQLite database handle.
internal let handle: OpaquePointer

/// Create a new SQLite database.
///
/// let sqliteDB = SQLiteDatabase(storage: .memory)
///
/// - parameters:
/// - storage: SQLite storage method. See `SQLiteStorage`.
/// - threadPool: Thread pool for performing blocking IO work. See `BlockingIOThreadPool`.
/// - queue: Dispatch queue for performing blocking IO work. See `DispatchQueue`.
dpgao marked this conversation as resolved.
Show resolved Hide resolved
/// - throws: Errors creating the SQLite database.
public init(storage: SQLiteStorage = .memory, threadPool: BlockingIOThreadPool? = nil) throws {
self.storage = storage
self.blockingIO = threadPool ?? BlockingIOThreadPool(numberOfThreads: 2)
self.blockingIO.start()
// make connection
let options = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX
var handle: OpaquePointer?
guard sqlite3_open_v2(self.storage.path, &handle, options, nil) == SQLITE_OK, let c = handle else {
throw SQLiteError(problem: .error, reason: "Could not open database.", source: .capture())
}
self.handle = c
}

/// See `Database`.
public func newConnection(on worker: Worker) -> Future<SQLiteConnection> {
let conn = SQLiteConnection(database: self, on: worker)
return worker.future(conn)
do {
let conn = try SQLiteConnection(database: self, on: worker)
return worker.future(conn)
} catch {
return worker.future(error: error)
}
}

/// See `LogSupporting`.
public static func enableLogging(_ logger: DatabaseLogger, on conn: SQLiteConnection) {
conn.logger = logger
}

/// Closes the open SQLite handle on deinit.
deinit {
sqlite3_close(handle)
self.blockingIO.shutdownGracefully { error in
if let error = error {
print("[SQLite] [ERROR] Could not shutdown BlockingIOThreadPool: \(error)")
}
}
}
}
22 changes: 11 additions & 11 deletions Sources/SQLite/Database/SQLiteStatement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import SQLite3
#endif

internal struct SQLiteStatement {
internal let connection: SQLiteConnection
internal let c: OpaquePointer

private let c: OpaquePointer
private let connection: SQLiteConnection
internal init(query: String, on connection: SQLiteConnection) throws {
var handle: OpaquePointer?
let ret = sqlite3_prepare_v2(connection.database.handle, query, -1, &handle, nil)
let ret = sqlite3_prepare_v2(connection.handle, query, -1, &handle, nil)
guard ret == SQLITE_OK, let c = handle else {
throw SQLiteError(statusCode: ret, connection: connection, source: .capture())
}
self.connection = connection
self.c = c
self.connection = connection
}

internal func bind(_ binds: [SQLiteData]) throws {
Expand Down Expand Up @@ -52,21 +52,21 @@ internal struct SQLiteStatement {
}
}
}

}

internal func getColumns() throws -> [SQLiteColumn]? {
var columns: [SQLiteColumn] = []

dpgao marked this conversation as resolved.
Show resolved Hide resolved
let count = sqlite3_column_count(c)
columns.reserveCapacity(Int(count))

// iterate over column count and intialize columns once
// we will then re-use the columns for each row
for i in 0..<count {
try columns.append(column(at: i))
}

return columns
}

Expand All @@ -85,7 +85,7 @@ internal struct SQLiteStatement {
case SQLITE_ROW: break
default: throw SQLiteError(statusCode: step, connection: connection, source: .capture())
}


var row: [SQLiteColumn: SQLiteData] = [:]

Expand Down