Skip to content

Commit

Permalink
Added support for SQLite parameter binding, Removed the "esc" method …
Browse files Browse the repository at this point in the history
…and the Bridge class which was required just to support the method, Refactored the code and made some methods that should not be accessed from outside private
  • Loading branch information
FahimF committed Sep 4, 2014
1 parent 39100a4 commit 691349f
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 111 deletions.
15 changes: 0 additions & 15 deletions Bridge.h

This file was deleted.

22 changes: 0 additions & 22 deletions Bridge.m

This file was deleted.

1 change: 0 additions & 1 deletion Bridging-Header.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@

#import "sqlite3.h"
#import <time.h>
#import "Bridge.h"
43 changes: 24 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
SQLiteDB
========

This is a basic SQLite wrapper for Swift. It is very simple at the moment and does not provide any advanced functionality. Additionally, it's not pure Swift at the moment due to some difficulties in making all of the necessary SQLite C API calls from Swift.
This is a simple and lightweight SQLite wrapper for Swift. It allows all basic SQLite functionality including being able to bind values to parameters in an SQL statement. The framework does require an initial SQLite databae to be included in your project - it does not create the database for you via code.

**Important** If you are new to Swift or have not bothered to read up on the Swift documentation, please do not contact me about Swift functionality. I just don't have the time to answer your queries about Swift. Of course, if you're willing to pay for my time though, feel free to contact me :)
**Important:** If you are new to Swift or have not bothered to read up on the Swift documentation, please do not contact me about Swift functionality. I just don't have the time to answer your queries about Swift. Of course, if you're willing to pay for my time though, feel free to contact me :)

Adding to Your Project
---
* Create your SQLite database however you like, but name it `data.db` and then add the `data.db` file to your Xcode project. (If you want to name the database file something other than `data.db`, then change the `DB_NAME` constant in the `SQLiteDB` class accordingly.)

**Note:** Remember to add the database file to your application target when you add it to the project. If you don't add the database file to a project target, it will not be copied to the device along with the other project resources.
**Note:** Remember to add the database file above to your application target when you add it to the project. If you don't add the database file to a project target, it will not be copied to the device along with the other project resources.

* Add all of the included source files (except for README.md, of course) to your project.
* If you don't have a bridging header file, use the included `Bridging-Header.h` file. If you already have a bridging header file, then copy the contents from the included `Bridging-Header.h` file in to your own bridging header file.
* If you didn't have a bridging header file, make sure that you modify your project settings to point to the new bridging header file. This will be under the "Build Settings" for your target and will be named "Objective-C Bridging Header".
* Add the SQLite library (libsqlite3.0.dylib) to your project under the "Build Phases" - "Link Binary With Libraries" section.

* If you don't have a bridging header file, use the included `Bridging-Header.h` file. If you already have a bridging header file, then copy the contents from the included `Bridging-Header.h` file to your own bridging header file.

* If you didn't have a bridging header file, make sure that you modify your project settings to point to the new bridging header file. This will be under **Build Settings** for your target and will be named **Objective-C Bridging Header**.

* Add the SQLite library (libsqlite3.0.dylib) to your project under **Build Phases** - **Link Binary With Libraries** section.

That's it. You're set!

Expand All @@ -27,32 +30,34 @@ Usage

* You can make SQL queries using the `query` method (the results are returned as an array of `SQLRow` objects):
```swift
let data = db.query("SELECT * FROM customers WHERE name='John'")
let row = data[0]
if let name = row["name"] {
textLabel.text = name.asString()
if let data = db.query("SELECT * FROM customers WHERE name='John'") {
let row = data[0]
if let name = row["name"] {
textLabel.text = name.asString()
}
}
```
In the above, `db` is a reference to the shared SQLite database instance and `SQLRow` is a class defined to model a data row in SQLiteDB. You can access a column from your query results by subscripting the `SQLRow` instance based on the column name. That returns an `SQLColumn` instance from which you can retrieve the data as a native data type by using the `asString`, `asInt`, `asDouble`, `asData`, and `asDate` methods provided by `SQLColumn`.

* You can execute all non-query SQL commands (INSERT, DELETE, UPDATE etc.) using the `execute` method:
* If you'd prefer to bind values to your query instead of creating the full SQL string, then you can execute the above SQL also like this:
```swift
db.execute("DELETE * FROM customers WHERE last_name='Smith'")
let name = "John"
data = db.query("SELECT * FROM customers WHERE name=?", parameters:[name]) {
```

* You can also create SQL statements with variable/dynamic values quite easily using Swift's string manipulation functionality. (And you do not need to use the SQLite bind API calls.)
* Of course, you can also construct the above SQL query by using Swift's string manipulation functionality as well (without using the SQLite bind functionality):
```swift
let name = "Smith"
db.execute("DELETE * FROM customers WHERE last_name='\(name)'")
let name = "John"
data = db.query("SELECT * FROM customers WHERE name='\(name)'") {
```

* If your variable values contain quotes, remember to use the `esc` method to quote and escape the special characters in your input data. Otherwise, you will get a runtime error when trying to execute your SQL statements. (Note that the `esc` method encloses your data in quotes - so you don't have to enclose the final value in quotes when building your SQL statement.)
* You can execute all non-query SQL commands (INSERT, DELETE, UPDATE etc.) using the `execute` method:
```swift
let db = SQLiteDB.sharedInstance()
let name = db.esc("John's Name")
let sql = "SELECT * FROM clients WHERE name=\(name)"
db.execute("DELETE * FROM customers WHERE last_name='Smith'")
```

* The `esc` method which was previously available in SQLiteDB is no longer there. So, for instance, if you need to escape strings with embedded quotes, you should use the SQLite parameter binding functionality as shown above.

Questions?
---
* FAQ: [FAQs](https://github.com/FahimF/SQLiteDB/wiki/FAQs)
Expand Down
109 changes: 55 additions & 54 deletions SQLiteDB.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ let SQLITE_DATE = SQLITE_NULL + 1
let QUEUE_LABLE = "SQLiteDB"
var db:COpaquePointer = nil
var queue:dispatch_queue_t
var fmt = NSDateFormatter()

struct Static {
static var instance:SQLiteDB? = nil
Expand Down Expand Up @@ -192,6 +193,7 @@ let SQLITE_DATE = SQLITE_NULL + 1
println("SQLiteDB - failed to open DB!")
sqlite3_close(db)
}
fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
}

deinit {
Expand Down Expand Up @@ -227,13 +229,8 @@ let SQLITE_DATE = SQLITE_NULL + 1
}
}

// Execute SQL and return result code
func execute(sql:String)->CInt {
return execute(sql, parameters:nil)
}

// Execute SQL with parameters and return result code
func execute(sql:String, parameters:[AnyObject]?)->CInt {
func execute(sql:String, parameters:[AnyObject]?=nil)->CInt {
var result:CInt = 0
dispatch_sync(queue) {
let stmt = self.prepare(sql, params:parameters)
Expand All @@ -244,64 +241,18 @@ let SQLITE_DATE = SQLITE_NULL + 1
return result
}

// Run SQL query
func query(sql:String)->[SQLRow]? {
return query(sql, parameters:nil)
}

// Run SQL query with parameters
func query(sql:String, parameters:[AnyObject]?)->[SQLRow]? {
func query(sql:String, parameters:[AnyObject]?=nil)->[SQLRow]? {
var rows:[SQLRow]? = nil
dispatch_sync(queue) {
let stmt = self.prepare(sql, params:parameters)
if stmt != nil {
rows = self.query(stmt, sql:sql)
}

}
return rows
}

// SQL escape string - hacky version using an intermediate Objective-C class to make it work
func esc(str: String)->String {
println("SQLiteDB - Original string: \(str)")
let sql = Bridge.esc(str)
println("SQLiteDB - Escaped string: \(sql)")
return sql
}

// SQL escape string - original version, does not work correctly at the moment
func esc2(str: String)->String {
println("SQLiteDB - Original string: \(str)")
let args = getVaList([str as CVarArgType])
// var buf = UnsafePointer<Int8>.alloc(100)
// let cstr = sqlite3_vsnprintf(100, buf, "%Q", args)
// println("SQLiteDB - Escaped result: \(cstr), buffer: \(buf.memory)")
let cstr = sqlite3_vmprintf("%Q", args)
println("SQLiteDB - Escaped result: \(cstr), Raw: \(cstr.debugDescription)")
if let sql = String.fromCString(cstr) {
// sqlite3_free(cstr)
println("SQLiteDB - Escaped string: \(sql)")
return sql
}
return ""
}

// Return last insert ID
func lastInsertedRowID()->Int64 {
var lid:Int64 = 0
dispatch_sync(queue) {
lid = sqlite3_last_insert_rowid(self.db)
}
return lid
}

// Return last SQL error - do not call from within here since the queue handling can result in a deadlock
func lastSQLError()->String {
let buf = sqlite3_errmsg(self.db)
return NSString(CString:buf, encoding:NSUTF8StringEncoding)
}

// Show alert with either supplied message or last error
func alert(msg:String) {
dispatch_async(dispatch_get_main_queue()) {
Expand All @@ -326,7 +277,57 @@ let SQLITE_DATE = SQLITE_NULL + 1
}
// Bind parameters, if any
if params != nil {

// Validate parameters
let cntParams = sqlite3_bind_parameter_count(stmt)
let cnt = CInt(params!.count)
if cntParams != cnt {
let msg = "SQLiteDB - failed to bind parameters, counts did not match. SQL: \(sql), Parameters: \(params)"
println(msg)
self.alert(msg)
return nil
}
var flag:CInt = 0
// Text values passed to a C-API do not work correctly if they are not marked as transient. All the following gymnastics is to get the correct value to pass
let intTran = UnsafeMutablePointer<Int>(bitPattern: -1)
let tranPointer = COpaquePointer(intTran)
let transient = CFunctionPointer<((UnsafeMutablePointer<()>) -> Void)>(tranPointer)
for ndx in 1...cnt {
println("Binding: \(params![ndx-1]) at Index: \(ndx)")
// Check for data types
if params![ndx-1] is String {
let txt = params![ndx-1].copy() as String
flag = sqlite3_bind_text(stmt, CInt(ndx), txt, -1, transient)
} else if params![ndx-1] is NSData {
let data = params![ndx-1].copy() as NSData
flag = sqlite3_bind_blob(stmt, CInt(ndx), data.bytes, -1, nil)
} else if params![ndx-1] is NSDate {
let date = params![ndx-1].copy() as NSDate
let txt = fmt.stringFromDate(date)
flag = sqlite3_bind_text(stmt, CInt(ndx), txt, -1, transient)
} else if params![ndx-1] is Int {
// Is this an integer or float
let vfl = params![ndx-1] as Double
let vint = Double(Int(vfl))
if vfl == vint {
// Integer
let val = params![ndx-1].copy() as Int
flag = sqlite3_bind_int(stmt, CInt(ndx), CInt(val))
} else {
// Float
let val = params![ndx-1].copy() as Double
flag = sqlite3_bind_double(stmt, CInt(ndx), CDouble(val))
}
}
// Check for errors
if flag != SQLITE_OK {
sqlite3_finalize(stmt)
let error = String.fromCString(sqlite3_errmsg(self.db))
let msg = "SQLiteDB - failed to bind for SQL: \(sql), Parameters: \(params), Index: \(ndx) Error: \(error)"
println(msg)
self.alert(msg)
return nil
}
}
}
return stmt
}
Expand Down

0 comments on commit 691349f

Please sign in to comment.