Swift SqLite Setup - ios

Im making an application for IOS 9, using swift, and my application will consist of using a database. I searched on the internet and set up a SqLite database and am using the Firefox add on to have a GUI to view it. I make a Xcode project and as many tutorials said to do I went to my Build Phases > Link Binary With Libraries and searched for libsqlite3.0.dylib but I only found libsqlite3.tbd and libsqlite3.0.tbd . I said ok and clicked on libsqlite3.tbd hoping that it wouldnt matter, but I got stuck at the next step of using sqlite in IOS. I had to
#import <sqlite3.h>
but the thing was that i would get an error when ever that happened. I just need some step by step instructions on how to get my SqLite database to start working!!! I can't find anything on the web that isn't using IOS 7 and adding libsqlite3.dylib to their build phase. Please help!!

You may try the following:
When expand Link Binary and hit + , then click 'Add other', when the window pops up hit CTRL+Shift + G. In the Go to folder field put /usr/lib -> Go and you will find 'libsqlite3.dylib'

Why don't you use a Swift SQLite wrapper such as https://github.com/groue/GRDB.swift or https://github.com/stephencelis/SQLite.swift?

If you want to use Swift then Here is an easy solution.
Step 1: Import SQLite
import SQLite3
Step 2: Create and Open a database file
let fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent("YourFileName.sqlite")
if sqlite3_open(fileURL.path, &db) != SQLITE_OK {
print("error opening database")
}
Step 4: Create a Table
if sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS Heroes (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, powerrank INTEGER)", nil, nil, nil) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("error creating table: \(errmsg)")
}
Step 5: Insert Values
var stmt: OpaquePointer?
let queryString = "INSERT INTO Heroes (name, powerrank) VALUES (?,?)"
if sqlite3_prepare(db, queryString, -1, &stmt, nil) != SQLITE_OK{
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("error preparing insert: \(errmsg)")
return
}
if sqlite3_bind_text(stmt, 1, name, -1, nil) != SQLITE_OK{
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("failure binding name: \(errmsg)")
return
}
if sqlite3_bind_int(stmt, 2, (powerRanking! as NSString).intValue) != SQLITE_OK{
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("failure binding name: \(errmsg)")
return
}
if sqlite3_step(stmt) != SQLITE_DONE {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("failure inserting hero: \(errmsg)")
return
}
By following these steps you can easily insert some rows in your table. For other operations like, reading visit this nice step by step guide - Swift SQLite Tutorial.

Related

Saving database changes into .db file with SQLite (Swift)

In my Project I am opening an already existing database from a .db with sqlite3_open. This file was created with the command-line tool sqlite3. I am updating some rows in a table, which works fine, but those updates are only made in the temporary instance of the database. How can I also update my .db file for future runs of my project to work with updated data?
Somebody asked here how to save a SQLite database in Swift. But the answer seems to me somehow unsatisfying, because the file should be written in the same format as created by sqlite3, so that it can be opened again via sqlite3_open.
Is there maybe even a function presented by SQLite? I couldn't find anything for that...
You cannot modify the contents of the bundle. So you will want to copy the file to some a directory where it can be modified. Historically we might have advised using “documents” folder for this. But nowadays we use the “application support directory”. See iOS Storage Best Practices.
Anyway, the basic idea is:
include the original database in the bundle (by adding it to your app’s target, if you haven’t already);
try to open the database (with SQLITE_READWRITE option but not the SQLITE_CREATE option ... we don’t want it creating a blank database if it doesn’t find it) in the application support directory; and
if that fails (because the file is not found), copy the database from the bundle to the application support directory and try again
Thus, perhaps something like:
var db: OpaquePointer?
enum DatabaseError: Error {
case bundleDatabaseNotFound
case sqliteError(Int32, String?)
}
func openDatabase() throws {
let fileURL = try FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("database.db")
// try opening database and just return if it succeeded
if sqlite3_open_v2(fileURL.path, &db, SQLITE_OPEN_READWRITE, nil) == SQLITE_OK {
return
}
// if it failed, clean up before trying again ...
sqlite3_close(db)
db = nil
// then copy database from bundle ...
guard let bundlePath = Bundle.main.url(forResource: "database", withExtension: "db") else {
throw DatabaseError.bundleDatabaseNotFound
}
try FileManager.default.copyItem(at: bundlePath, to: fileURL)
// and try again
let rc = sqlite3_open_v2(fileURL.path, &db, SQLITE_OPEN_READWRITE, nil)
if rc == SQLITE_OK { return }
// and if it still failed, again, clean up, and then throw error
let errorMessage = sqlite3_errmsg(db)
.flatMap { String(cString: $0) }
sqlite3_close(db)
db = nil
throw DatabaseError.sqliteError(rc, errorMessage)
}

SQLite select query error unrecognized token ":"

I have to update the structure of my exiting table, already done in past. Now, I delete and recreate table to add some columns, but when I query db, I obtain the error -> unrecognized token: ":"
But db and all tables are empty. I don't understand what going wrong.
I'm trying to modify, update code, change query table but obtain the same result.
var settings = Table('settings')
if let db = getDB() {
do{
try db.run(settings.drop(ifExists: true)) //OK
try db.run(settings.create { t in
t.column(id, primaryKey: .autoincrement)
t.column(Expression<String?>("email"), defaultValue: nil)
t.column(Expression<String?>("token"), defaultValue: nil)
}) //OK
try db.scalar(settings.count) // ERROR
} catch {
throw error
}
}

Swift App with SQLITE crashes after updating DB. SQLITE file is not copied to file/bundle

My app worked perfectly until I used a new SQLite browser do do some changes in my sqlite file. Then there suddenly is a problem with opening the database.
The database opening function look like this:
func openDatabase() -> Bool {
do {
let manager = FileManager.default
let documentsURL = try manager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("database.sqlite")
var rc = sqlite3_open_v2(documentsURL.path, &db, SQLITE_OPEN_READWRITE, nil)
if rc == SQLITE_CANTOPEN {
let bundleURL = Bundle.main.url(forResource: "database", withExtension: "sqlite")!
try manager.copyItem(at: bundleURL, to: documentsURL)
rc = sqlite3_open_v2(documentsURL.path, &db, SQLITE_OPEN_READWRITE, nil)
}
if rc != SQLITE_OK {
print("Error: \(rc)")
return false
}
return true
} catch {
print(error)
return false
}
}
After making a change in the DB with DB Browser for sqlite, the app crashes the moment the databese is opened, pointing out that this line gives an unexpected nil while unwrapping optional value:
let bundleURL = Bundle.main.url(forResource: "database", withExtension: "sqlite")!
Log gives following text:
2018-12-20 18:16:22.306631+0100 eFloraSnap[7844:165367] [logging-persist] cannot open file at line 42249 of [95fbac39ba]
2018-12-20 18:16:22.306810+0100 eFloraSnap[7844:165367] [logging-persist] os_unix.c:42249: (0) open(/Users/tomsol/Library/Developer/CoreSimulator/Devices/143DCE6E-01F0-40D0-9640-6397504D81FA/data/Containers/Data/Application/6BB619CB-BEF9-4917-9A74-8C92D84DA2F0/Documents/database.sqlite) - Undefined error: 0
(lldb)
I dont get it. I totally uninstalled Xcode with "Clean My Mac" ann reinstalled Xcode from scratch, in hope of removing som bad settings. This did not solve anything. Thinking there might be a problem with some databse formatting.
Is there any other reason this happens?
EDIT:
I have now seen, that the databse.sqlite is not in the path given in the log. So why is the sqlite file not copied there when building and running? Same problem with device as simulator.
Well, seems like completely uninstalling Xcode does not fixes bad settings.
Adding the sqlite file in Buildphases-Copy bundle resources fixed it. Why it disappered from copy bundle resources in the first time is unknown. Why it wasnt autmatically added using a freshly installed xcode is also unknown.

CKRecord fetch return 1 record with empty RecordID

Record from iCloud container is fetched, but there is no RecordID. At the same time in the panel on the site, I see it. I tried to extract the record from another application, I registered the necessary container in the settings - and it was extracted without error.
I do not understand - this is a bug Xcode? After all, the extraction code is identical in the second application, where everything works. And in the debugger at the bottom left you can see that RecordID is not empty.
Code:
privateDatabase.perform(query, inZoneWith: nil) { (results, error) -> Void in
if error != nil {
print(error!.localizedDescription)
result(false)
} else if results != nil, results!.count > 0, let me = results?[0] {
let RN = (me[.recordID] as! CKRecord.ID).recordName
Error:
Thread 2: Fatal error: Unexpectedly found nil while unwrapping an Optional value
Variables View Console:
_recordName __NSCFString * #"UA-kuka-2018-11-16 11:27:59" 0x00000001c0644320
The proper way to get the recordID of a CKRecord is to use the recordID property.
Assuming me is actually a CKRecord:
let RN = me.recordID.recordName

SQLITE_BUSY error in iOS application

I am writing an iOS application to allow medical doctors to select diagnoses codes from a database and bill patients that are also in the sqlite3 database. The main page is a form that asks for the patient name and date of birth. Also on the main page is a button to begin the search (drill down through body locations) for the diagnoses code.
I am using a tab bar with a bill tab, patients tab, and a doctors tab. A user can add patients and doctors from their respective pages. This works and shows the patients/doctors in their list unless I have navigated to the diagnoses detail page on the bill tab.
I keep my database connection in a "DatabaseManager" class that opens and closes the database file on the device.
class DatabaseManager {
var db:COpaquePointer!
init(){
}
/**
* Checks that the database file is on the device. If not, copies the database file to the device.
* Connects to the database after file is verified to be in the right spot.
**/
func checkDatabaseFileAndOpen(){
let theFileManager = NSFileManager.defaultManager()
let filePath = dataFilePath()
if theFileManager.fileExistsAtPath(filePath) {
db = openDBPath(filePath)
} else {
let pathToBundledDB = NSBundle.mainBundle().pathForResource("testDML", ofType: "sqlite3")// Copy the file from the Bundle and write it to the Device
let pathToDevice = dataFilePath()
var error:NSError?
if (theFileManager.copyItemAtPath(pathToBundledDB!, toPath:pathToDevice, error: nil)) {
db = openDBPath(pathToDevice)
} else {
println("database failure")
}
}
}
/**
* Gets the path of the database file on the device
**/
func dataFilePath() -> String {
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let documentsDirectory = paths[0] as! NSString
return documentsDirectory.stringByAppendingPathComponent("testDML.sqlite3") as String
}
/**
* Makes a connection to the database file located at the provided filePath
**/
func openDBPath(filePath:String) -> COpaquePointer {
var db:COpaquePointer = nil
var result = sqlite3_open(filePath, &db)
println("openResult: \(result)")
if result != SQLITE_OK {
sqlite3_close(db)
println("Failed To Open Database")
return nil
}else {
return db
}
}
func closeDB() {
var closeResult = sqlite3_close_v2(db)
print("closed result:\(closeResult)")
if closeResult == SQLITE_OK {
}
}
Any time I do a query I open the database, do the query, and then close the database. I get SQLITE_OK on closing and opening the database for every query I run, but I get a SQLITE_BUSY result when adding patients and doctors only when I have navigated to the diagnoses detail page on the bill tab. All the detail page does is retrieve the diagnoses code and update some text on the screen. The add functions for patients and doctors are below:
func addPatientToDatabase(inputPatient:String, dateOfBirth:String, email:String){
var (firstName, lastName) = split(inputPatient)
println(dateOfBirth)
let query = "INSERT INTO Patient (pID,date_of_birth,f_name,l_name, email) VALUES (NULL, '\(dateOfBirth)', '\(firstName)', '\(lastName!)', '\(email)')"
var statement:COpaquePointer = nil
if sqlite3_prepare_v2(db, query, -1, &statement, nil) == SQLITE_OK {
var sqliteResult = sqlite3_step(statement)
if sqliteResult == SQLITE_DONE {
println("Saved \(firstName) \(lastName!)")
}else {
println("Add patient failed \(sqliteResult)")
}
}
}
func addDoctorToDatabase(inputDoctor:String, email:String) {
var (firstName, lastName) = split(inputDoctor)
let query = "INSERT INTO Doctor (dID,f_name,l_name, email) VALUES (NULL,'\(firstName)', '\(lastName!)', '\(email)')"
var statement:COpaquePointer = nil
if sqlite3_prepare_v2(db, query, -1, &statement, nil) == SQLITE_OK {
var sqliteResult = sqlite3_step(statement)
if sqliteResult == SQLITE_DONE {
println("Saved \(firstName) \(lastName!)")
}else {
println("Add doctor failed for \(firstName) \(lastName!) with error \(sqliteResult)")
}
}
}
I thought that this wouldn't be a problem because it is impossible for the user to run two queries at once and I have made sure that there is only one connection. Does anyone have any suggestions on what might be happening here?
I believe you forget finalizing your prepared statements using sqlite3_finalize(), unless you have unmatched open/close calls or you access the db connection from multiple threads. According to sqlite guidelines:
If the database connection is associated with unfinalized prepared
statements or unfinished sqlite3_backup objects then sqlite3_close()
will leave the database connection open and return SQLITE_BUSY.
In a Swift project in addition to Mostruash's answer, you may even need to use sqlite3_close_v2() method. sqllite3_close() method not always returns SQLITE_OK.
If the database connection is associated with unfinalized prepared
statements or unfinished sqlite3_backup objects then sqlite3_close()
will leave the database connection open and return SQLITE_BUSY. If
sqlite3_close_v2() is called with unfinalized prepared statements
and/or unfinished sqlite3_backups, then the database connection
becomes an unusable "zombie" which will automatically be deallocated
when the last prepared statement is finalized or the last
sqlite3_backup is finished. The sqlite3_close_v2() interface is
intended for use with host languages that are garbage collected, and
where the order in which destructors are called is arbitrary.
Applications should finalize all prepared statements, close all BLOB
handles, and finish all sqlite3_backup objects associated with the
sqlite3 object prior to attempting to close the object. If
sqlite3_close_v2() is called on a database connection that still has
outstanding prepared statements, BLOB handles, and/or sqlite3_backup
objects then it returns SQLITE_OK and the deallocation of resources is
deferred until all prepared statements, BLOB handles, and
sqlite3_backup objects are also destroyed.

Resources