Skip to content

Commit

Permalink
Merge pull request #8 from qutheory/statement-testing
Browse files Browse the repository at this point in the history
clean up statement code
  • Loading branch information
tanner0101 committed Jun 8, 2016
2 parents 4dc67f4 + e66137e commit 2b2bb75
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 88 deletions.
216 changes: 140 additions & 76 deletions Sources/SQLite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,68 +7,91 @@
let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)

public class SQLite {
typealias PrepareClosure = ((SQLite) throws -> ())

private var statement: OpaquePointer?

var database: OpaquePointer?

/**
The prepare closure is used
to bind values to the SQLite statement
in a safe, escaped manner.
*/
typealias PrepareClosure = ((Statement) throws -> ())

/**
Provides more useful type
information for the Database pointer.
*/
typealias Database = OpaquePointer

/**
An optional pointer to the
connection to the SQLite database.
*/
var database: Database?

/**
Opens a connection to the SQLite
database at a given path.

If the database does not already exist,
it will be created.
*/
init(path: String) throws {
let options = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX
if sqlite3_open_v2(path, &database, options, nil) != SQLITE_OK {
throw Error.connection(errorMessage)
throw Error.connection(database?.errorMessage ?? "")
}
}

/**
Closes a connetion to the database.
*/
func close() {
sqlite3_close(database)
}

struct Result {
struct Row {
var data: [String: String]

init() {
data = [:]
}
}

var rows: [Row]

init() {
rows = []
}
/**
Closes the database when deinitialized.
*/
deinit {
self.close()
}

/**
Executes a statement query string
and calls the prepare closure to bind
any prepared values.

The resulting rows are returned if
no errors occur.
*/
func execute(_ queryString: String, prepareClosure: PrepareClosure = { _ in }) throws -> [Result.Row] {
bindPosition = 0
guard let database = self.database else {
throw Error.execute("No database")
}

let statementPointer = UnsafeMutablePointer<OpaquePointer?>.init(allocatingCapacity: 1)
let statementContainer = UnsafeMutablePointer<OpaquePointer?>.init(allocatingCapacity: 1)
defer {
statementPointer.deinitialize()
self.statement = nil
statementContainer.deallocateCapacity(1)
}

if sqlite3_prepare_v2(database, queryString, -1, statementPointer, nil) != SQLITE_OK {
throw Error.prepare(errorMessage)
if sqlite3_prepare_v2(database, queryString, -1, statementContainer, nil) != SQLITE_OK {
throw Error.prepare(database.errorMessage)
}

guard let statement = statementPointer.pointee else {
return []
guard let statementPointer = statementContainer.pointee else {
throw Error.execute("Statement pointer errror")
}
self.statement = statement

try prepareClosure(self)
let statement = Statement(pointer: statementPointer, database: database)
try prepareClosure(statement)

var result = Result()
while sqlite3_step(statement) == SQLITE_ROW {
while sqlite3_step(statement.pointer) == SQLITE_ROW {

var row = Result.Row()
let count = sqlite3_column_count(statement)
let count = sqlite3_column_count(statement.pointer)

for i in 0..<count {
let text = sqlite3_column_text(statement, i)
let name = sqlite3_column_name(statement, i)
let text = sqlite3_column_text(statement.pointer, i)
let name = sqlite3_column_name(statement.pointer, i)

var value: String? = nil
if let text = text {
Expand All @@ -88,83 +111,124 @@ public class SQLite {
result.rows.append(row)
}

if sqlite3_finalize(statement) != SQLITE_OK {
throw Error.execute(errorMessage)
if sqlite3_finalize(statement.pointer) != SQLITE_OK {
throw Error.execute(database.errorMessage)
}

return result.rows
}

var lastId: Int {
/**
Returns an identifier for the last
inserted row.
*/
var lastId: Int? {
guard let database = database else {
return nil
}

let id = sqlite3_last_insert_rowid(database)
return Int(id)
}

//MARK: Error

public enum Error: ErrorProtocol {
case connection(String)
case close(String)
case prepare(String)
case bind(String)
case execute(String)
}

}

extension SQLite {
/**
Represents a row of data from
a SQLite table.
*/
struct Result {
struct Row {
var data: [String: String]

init() {
data = [:]
}
}

var rows: [Row]

init() {
rows = []
}
}
}

extension SQLite.Database {
/**
Returns the last error message
for the current database connection.
*/
var errorMessage: String {
if let raw = sqlite3_errmsg(database) {
if let raw = sqlite3_errmsg(self) {
return String(cString: raw) ?? "Unknown"
} else {
return "Unknown"
}
}

//MARK: Bind

var bindPosition: Int32 = 0

var nextBindPosition: Int32 {
bindPosition += 1
return bindPosition
}

func reset(_ statementPointer: OpaquePointer) {
sqlite3_reset(statementPointer)
sqlite3_clear_bindings(statementPointer)
}
}

func bind(_ value: Double) throws {
guard let statement = self.statement else {
return
extension SQLite {
/**
Represents a single database statement.
The statement is used to bind prepared
values and contains a pointer to the
underlying SQLite statement memory.
*/
class Statement {
typealias Pointer = OpaquePointer

var pointer: Pointer
var database: Database
var bindPosition: Int32
var nextBindPosition: Int32 {
bindPosition += 1
return bindPosition
}

if sqlite3_bind_double(statement, nextBindPosition, value) != SQLITE_OK {
throw Error.bind(errorMessage)
init(pointer: Pointer, database: Database) {
self.pointer = pointer
self.database = database
bindPosition = 0
}
}

func bind(_ value: Int) throws {
guard let statement = self.statement else {
return
func reset(_ statementPointer: OpaquePointer) {
sqlite3_reset(statementPointer)
sqlite3_clear_bindings(statementPointer)
}

if sqlite3_bind_int(statement, nextBindPosition, Int32(value)) != SQLITE_OK {
throw Error.bind(errorMessage)
func bind(_ value: Double) throws {
if sqlite3_bind_double(pointer, nextBindPosition, value) != SQLITE_OK {
throw Error.bind(database.errorMessage)
}
}
}

func bind(_ value: String) throws {
guard let statement = self.statement else {
return
func bind(_ value: Int) throws {
if sqlite3_bind_int(pointer, nextBindPosition, Int32(value)) != SQLITE_OK {
throw Error.bind(database.errorMessage)
}
}

let strlen = Int32(value.characters.count)
if sqlite3_bind_text(statement, nextBindPosition, value, strlen, SQLITE_TRANSIENT) != SQLITE_OK {
throw Error.bind(errorMessage)
func bind(_ value: String) throws {
let strlen = Int32(value.characters.count)
if sqlite3_bind_text(pointer, nextBindPosition, value, strlen, SQLITE_TRANSIENT) != SQLITE_OK {
throw Error.bind(database.errorMessage)
}
}
}

func bind(_ value: Bool) throws {
try bind(value ? 1 : 0)
func bind(_ value: Bool) throws {
try bind(value ? 1 : 0)
}
}

}

31 changes: 19 additions & 12 deletions Sources/SQLiteDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ public class SQLiteDriver: Fluent.Driver {
let sql = SQL(query: query)

// print("SQLite executing: \(sql.statement)") // useful for developing
let results = try database.execute(sql.statement) { preparer in
try self.bind(preparer: preparer, to: sql.values)
let results = try database.execute(sql.statement) { statement in
try self.bind(statement: statement, to: sql.values)
}

if query.action == .create {
if let id = database.lastId where query.action == .create {
return [
[idKey : database.lastId]
[idKey : id]
]
} else {
return map(results: results)
Expand All @@ -48,34 +48,41 @@ public class SQLiteDriver: Fluent.Driver {
values and returns the results.
*/
public func raw(_ statement: String, values: [Value] = []) throws -> [[String: Value]] {
let results = try database.execute(statement) { preparer in
try self.bind(preparer: preparer, to: values)
let results = try database.execute(statement) { statement in
try self.bind(statement: statement, to: values)
}
return map(results: results)
}

func bind(preparer: SQLite, to values: [Value]) throws {
/**
Binds an array of values to the
SQLite statement.
*/
func bind(statement: SQLite.Statement, to values: [Value]) throws {
for value in values {
switch value.structuredData {
case .int(let int):
try preparer.bind(int)
try statement.bind(int)
case .double(let double):
try preparer.bind(double)
try statement.bind(double)
case .string(let string):
try preparer.bind(string)
try statement.bind(string)
case .array(_):
throw Error.unsupported("Array values not supported.")
case .dictionary(_):
throw Error.unsupported("Dictionary values not supported.")
case .null: break
case .bool(let bool):
try preparer.bind(bool)
try statement.bind(bool)
case .data(let data):
try preparer.bind(String(data))
try statement.bind(String(data))
}
}
}

/**
Maps SQLite Results to Fluent results.
*/
func map(results: [SQLite.Result.Row]) -> [[String: Value]] {
return results.map { row in
var data: [String: Value] = [:]
Expand Down

0 comments on commit 2b2bb75

Please sign in to comment.