SQLITE_BUSY error in iOS application - ios

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.

Related

Listener event not triggered when document is updated (Google Firestore)

I am struggling to understand why my event listener that I initialize on a document is not being triggered whenever I update the document within the app in a different UIViewController. If I update it manually in Google firebase console, the listener event gets triggered successfully. I am 100% updating the correct document too because I see it get updated when I update it in the app. What I am trying to accomplish is have a running listener on the current user that is logged in and all of their fields so i can just use 1 global singleton variable throughout my app and it will always be up to date with their most current fields (name, last name, profile pic, bio, etc.). One thing I noticed is when i use setData instead of updateData, the listener event gets triggered. For some reason it doesn't with updateData. But i don't want to use setData because it will wipe all the other fields as if it is a new doc. Is there something else I should be doing?
Below is the code that initializes the Listener at the very beginning of the app after the user logs in.
static func InitalizeWhistleListener() {
let currentUser = Auth.auth().currentUser?.uid
let userDocRef = Firestore.firestore().collection("users").document(currentUser!)
WhistleListener.shared.listener = userDocRef.addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
print("INSIDE LISTENER")
}
}
Below is the code that update's this same document in a different view controller whenever the user updates their profile pic
func uploadProfilePicture(_ image: UIImage) {
guard let uid = currentUser!.UID else { return }
let filePath = "user/\(uid).jpg"
let storageRef = Storage.storage().reference().child(filePath)
guard let imageData = image.jpegData(compressionQuality: 0.75) else { return }
storageRef.putData(imageData) { metadata, error in
if error == nil && metadata != nil {
self.userProfileDoc!.updateData([
"profilePicURL": filePath
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
print("Document successfully updated")
}
}
}
}
}
You can use set data with merge true it doesn't wipe any other property only merge to specific one that you declared as like I am only update the name of the user without wiping the age or address
db.collection("User")
.document(id)
.setData(["name":"Zeeshan"],merge: true)
The answer is pretty obvious (and sad at the same time). I was constantly updating the filepath to be the user's UID therefore, it would always be the same and the snapshot wouldn't recognize a difference in the update. It had been some time since I had looked at this code so i forgot this is what it was doing. I was looking past this and simply thinking an update (no matter if it was different from the last or not) would trigger an event. That is not the case! So what I did was append an additional UUID to the user's UID so that it changed.

Parse won't save objects in local datastore after I close application and disconnect from Wi-Fi

I'm trying to save some objects locally so when I close my application and disconnect from my Wi-Fi I'll still be able to have the data but this does not work.
I have created this function to save my database.
func saveAllQueriesLocally() {
let queries = PFQuery(className: "Directory")
PFObject.unpinAllObjectsInBackground()
queries.findObjectsInBackground { (object, error) in
if let error = error {
// The query failed
print(error.localizedDescription)
} else if let object = object {
// The query succeeded with a matching result
for i in object{
i.pinInBackground()
}
} else {
// The query succeeded but no matching result was found
}
}
}
and I have called this function inside viewDidLoad() of my main ViewController. I am not sure but I am guessing the function searches the database when it is offline and since it does not retrieve anything it rewrites the cache as empty.
While being connected to the internet, objects get retrieved correctly.

CloudKit, after reinstalling an app how do I reset my subscriptions to current status of records?

I'm dealing with the scenario, where a user has previously deleted the app and has now re-installed it.
It was hitting my delta fetch function, which is receiving a lot of old subscription notifications, mostly deletes. But not downloading current records.
I'm now adding code to perform a fetch on each record type to download all the data.
I'd like to reset delta fetch server token, so the app doesn't have to process old subscriptions notifications. However I can't find how to do this, maybe it's not possible.
Are you referring to CKServerChangeToken (documentation) when you say "delta fetch server token"? And are you attempting to sync within the CloudKit private database?
Assuming that is true, here is an example of how I fetch changes from the private database and keep track of the sync token:
//MARK: Fetch Latest from CloudKit from private DB
func fetchPrivateCloudKitChanges(){
print("Fetching private changes...")
//:::
let privateZoneId = CKRecordZone.ID(zoneName: CloudKit.zoneName, ownerName: CKCurrentUserDefaultName)
/----
let options = CKFetchRecordZoneChangesOperation.ZoneOptions()
options.previousServerChangeToken = previousChangeToken
let operation = CKFetchRecordZoneChangesOperation(recordZoneIDs: [privateZoneId], optionsByRecordZoneID: [recordZoneID:options])
//Queue up the updated records to process below
var records = [CKRecord]()
operation.recordChangedBlock = { record in
records.append(record)
}
operation.recordWithIDWasDeletedBlock = { recordId, type in
//Process a deleted record in your local database...
}
operation.recordZoneChangeTokensUpdatedBlock = { (zoneId, token, data) in
// Save new zone change token to disk
previousChangeToken = token
}
operation.recordZoneFetchCompletionBlock = { zoneId, token, _, _, error in
if let error = error {
print(error)
}
// Write this new zone change token to disk
previousChangeToken = token
}
operation.fetchRecordZoneChangesCompletionBlock = { error in
if let error = error {
print(error
}else{
//Success! Process all downloaded records from `records` array above...
//records...
}
}
CloudKit.privateDB.add(operation)
}
//Change token property that gets saved and retrieved from UserDefaults
var previousChangeToken: CKServerChangeToken? {
get {
guard let tokenData = defaults.object(forKey: "previousChangeToken") as? Data else { return nil }
return NSKeyedUnarchiver.unarchiveObject(with: tokenData) as? CKServerChangeToken
}
set {
guard let newValue = newValue else {
defaults.removeObject(forKey: "previousChangeToken")
return
}
let data = NSKeyedArchiver.archivedData(withRootObject: newValue)
defaults.set(data, forKey: "previousChangeToken")
}
}
Your specific situation might differ a little, but I think this is how it's generally supposed to work when it comes to staying in sync with CloudKit.
Update
You could try storing the previousServerChangeToken on the Users record in CloudKit (you would have to add it as a new field). Each time the previousServerChangeToken changes in recordZoneFetchCompletionBlock you would have to save it back to iCloud on the user's record.

How do I implement saving 1000s of records using CloudKit and Swift without exceeding 30 or 40 requests/second

Let me describe the basic flow that I am trying to implement:
User logs in
System retrieves list of user's connections using HTTP request to 3rd party API (could be in the 1000s). I'll call this list userConnections
System retrieves stored connections from my app's database (could be in the 100,000s). I'll call this list connections
System then checks to see if each userConnection exists in the connections list already and if not, saves it to database:
for userConnection in userConnections {
if connections.contains(userConnection) {
//do nothing
} else {
saveRecord(userConnection)
}
}
The problem with this is that when the first users log in, the app will try to make 1000 saveRecord calls in a second which the CloudKit server will not allow.
How can I implement this in a different way using CloudKit and Swift so that I keep it to an acceptable number of requests/second, like ~30 or 40?
For anyone wondering, this is how I ended up doing it. The comment by TroyT was correct that you can batch save your records. This answer includes bonus of queued batches:
let save1 = CKModifyRecordsOperation(recordsToSave: list1, recordIDsToDelete: nil)
let save2 = CKModifyRecordsOperation(recordsToSave: list2, recordIDsToDelete: nil)
save1.database = publicDB
save2.database = publicDB
save2.addDependency(save1)
let queue = NSOperationQueue()
queue.addOperations([save1, save2], waitUntilFinished: false)
save1.modifyRecordsCompletionBlock = { savedRecords, deletedRecordsIDs, error in
if (error != nil){
//handle error
}else{
//data saved
}
}

Steps to setting up relational database (sqlite) in swift

I am working on an iOS app and I want to set up a relational database for it. I read that SQLite is a good database to use. I'm making the app in Swift. I've looked at several tutorials but it seems like all the ones that I found have the DBManager class or libraries in objective-c. Therefore, I would need a wrapper for the db class. I'm not really sure how the wrapper works and how I would be calling objective-c methods using swift syntax.
I was wondering if someone could help clarify the whole process for this from creating the database file and adding it to your xcode project to using objective-c libraries with swift syntax in order to run queries against the database file.
Also, is it worth it to just use Core Data which seems easier to use instead?
Objective-C tutorials are still more-or-less relevant, but obviously won't bring Swift-specific niceties into the fold (nor does FMDB, currently, as recommended by another commenter). I ended up writing SQLite.swift to utilize some of the more interesting aspects of Swift (type-safety and generics, optionals):
https://github.com/stephencelis/SQLite.swift
SQLite.swift provides compile-time safety/confidence, and removes the need for a lot of error handling. Statements and expressions are built in Swift proper, so runtime SQL syntax errors are unlikely.
See the documentation for more information on creating the database file and where to store it (depending on your needs):
https://github.com/stephencelis/SQLite.swift/blob/master/Documentation/Index.md#connecting-to-a-database
As far as Core Data is concerned, it (like most Apple libraries at the time of this answer) doesn't take advantage of Swift, but has a rich legacy and may be the way to go, especially for a smaller, persistent object graph. However, if you want control over a relational database, or if you plan on storing a large dataset that changes frequently, you may become frustrated with Core Data (and the domain-specific knowledge you'll need to attain),
I have myself followed this step by step and well explained tutorial by techtopia.
http://www.techotopia.com/index.php/Swift_iOS_8_Database_Implementation_using_SQLite
http://www.techotopia.com/index.php/An_Example_SQLite_based_iOS_8_Application_using_Swift_and_FMDB
It uses FMDB wrapper.
Creating the Database and Table
override func viewDidLoad() {
super.viewDidLoad()
let filemgr = NSFileManager.defaultManager()
let dirPaths =
NSSearchPathForDirectoriesInDomains(.DocumentDirectory,
.UserDomainMask, true)
let docsDir = dirPaths[0] as! String
databasePath = docsDir.stringByAppendingPathComponent(
"contacts.db")
if !filemgr.fileExistsAtPath(databasePath as String) {
let contactDB = FMDatabase(path: databasePath as String)
if contactDB == nil {
println("Error: \(contactDB.lastErrorMessage())")
}
if contactDB.open() {
let sql_stmt = "CREATE TABLE IF NOT EXISTS CONTACTS (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT, ADDRESS TEXT, PHONE TEXT)"
if !contactDB.executeStatements(sql_stmt) {
println("Error: \(contactDB.lastErrorMessage())")
}
contactDB.close()
} else {
println("Error: \(contactDB.lastErrorMessage())")
}
}
}
The code in the above method performs the following tasks:
-Identifies the application’s Documents directory and constructs a path to the contacts.db database file.
-Creates an NSFileManager instance and subsequently uses it to detect if the database file already exists.
-If the file does not yet exist the code creates the database by creating an FMDatabase instance initialized with the database file path. If the database creation is successful it is then opened via a call to the open method of the new database instance.
-Prepares a SQL statement to create the contacts table in the database and executes it via a call to the FMDB executeStatements method of the database instance.
-Closes the database.
SAVE DATA TO DATABASE
#IBAction func saveData(sender: AnyObject) {
let contactDB = FMDatabase(path: databasePath as String)
if contactDB.open() {
let insertSQL = "INSERT INTO CONTACTS (name, address, phone) VALUES ('\(name.text)', '\(address.text)', '\(phone.text)')"
let result = contactDB.executeUpdate(insertSQL,
withArgumentsInArray: nil)
if !result {
status.text = "Failed to add contact"
println("Error: \(contactDB.lastErrorMessage())")
} else {
status.text = "Contact Added"
name.text = ""
address.text = ""
phone.text = ""
}
} else {
println("Error: \(contactDB.lastErrorMessage())")
}
}
FETCH DATA FROM DATABASE
#IBAction func findContact(sender: AnyObject) {
let contactDB = FMDatabase(path: databasePath as String)
if contactDB.open() {
let querySQL = "SELECT address, phone FROM CONTACTS WHERE name = '\(name.text)'"
let results:FMResultSet? = contactDB.executeQuery(querySQL,
withArgumentsInArray: nil)
if results?.next() == true {
address.text = results?.stringForColumn("address")
phone.text = results?.stringForColumn("phone")
status.text = "Record Found"
} else {
status.text = "Record not found"
address.text = ""
phone.text = ""
}
contactDB.close()
} else {
println("Error: \(contactDB.lastErrorMessage())")
}
}
You could use the sqlite wrapper included in this project. It's written in Objective-C but it can easily be used from within Swift.
Yet another SQLite wrapper for Swift 3: http://github.com/groue/GRDB.swift
It provides:
An API that will look familiar to users of the famous Objective-C FMDB (https://github.com/ccgus/fmdb)
A low-level SQLite API that leverages the Swift standard library.
A pretty Swift query interface for SQL-allergic developers
Support for the SQLite WAL mode, and concurrent database access for extra performance
A Record class that wraps result sets, eats your custom SQL queries for breakfast, provides persistence operations, and changes tracking.
Swift type freedom: pick the right Swift type that fits your data. Use Int64 when needed, or stick with the convenient Int. Store and read NSDate or NSDateComponents. Declare Swift enums for discrete data types. Define your own database-convertible types.
Database migrations
Speed: https://github.com/groue/GRDB.swift/wiki/Performance

Resources