SQLite select query error unrecognized token ":" - ios

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
}
}

Related

Conditionally Add Queries in FIrestore for Swift

I've read several articles here on StackOverflow asking this same question and the answers are all the same but it doesn't seem to work for me and I'm not sure why. I've read Elegant Way To Dynamically Query FireStore, Conditional Where Query on Firestore, the two articles that question references, plus How to conditionally add another filter to a query in Firebase Firestore using swift?, and the article Firestore Query Options Swift.
I followed the simple guidance the all offered:
var ref = db.collection("Violations")
if locationCompoundQuery != "" {
ref = ref.whereField("location", isEqualTo: locationCompoundQuery)
}
The issue is that Xcode gives me an error: Cannot assign value of type 'Query' to type "CollectionReference" insert 'as! CollectionReference'. If I do that it will compile and run, but when the query runs it crashes. The message in the console is: Could not cast value of type 'FIRQuery' (0x108972150) to 'FIRCollectionReference'
I'm using Xcode 13.1. All of my other queries that are compound without conditional statements run just fine. Any help is greatly appreciated.
I had the same issue. This works for me on Xcode 14.0.1 and Firebase 9.6.0.
let ref = db.collection(EventsObject)
var query: Query = ref
if filter == .all {
query = ref.whereField(CloudEvent.CodingKeys.ownerID.stringValue, isNotEqualTo: "") // must have any OwnerID
} else if filter == .allPublished {
query = ref.whereField(CloudEvent.CodingKeys.ownerID.stringValue, isNotEqualTo: "") // must have any OwnerID
query = query.whereField(CloudEvent.CodingKeys.published.stringValue, isEqualTo: true) // and has been published
} else if filter == .onlyThisUser {
query = ref.whereField(CloudEvent.CodingKeys.ownerID.stringValue, isEqualTo: currentUser) // belongs to this user
} else if filter == .onlyThisUserPublished {
query = ref.whereField(CloudEvent.CodingKeys.ownerID.stringValue, isEqualTo: currentUser) // belongs to this user
query = query.whereField(CloudEvent.CodingKeys.published.stringValue, isEqualTo: true) // and has been published
} else if filter == .onlyThisUserNotPublished {
query = ref.whereField(CloudEvent.CodingKeys.ownerID.stringValue, isEqualTo: currentUser) // belongs to this user
query = query.whereField(CloudEvent.CodingKeys.published.stringValue, isEqualTo: false) // and has not been published
}
query.addSnapshotListener({ querySnapShot, error in
if let querySnapShot = querySnapShot {
self.cloudEvents = querySnapShot.documents.compactMap { document in
do {
return try document.data(as: CloudEvent.self)
}
catch {
EventLogging.logger.error("error loading cloud events: \(error.localizedDescription)")
}
return nil
}
}
})

Trying to figure out a better way to delete element from Firestore dictionary

I'm using Firestore in my application, and I have a map field called "votes" for user's upvotes or downvotes. It looks like this:
I want to add an option to delete an element from there, this is what I got now:
//getting the user's votes dictionary and removing the post from it.
userRef.getDocument { (doc, error) in
if let _ = error { completion(false) }
guard let dict = doc?.data()?[USER_VOTES] as? [String: Any] else { return }
currentDict = dict
currentDict.removeValue(forKey: id)
}
//setting the votes dictionary with the updated one.
userRef.setData(currentDict) { (error) in
if let _ = error { completion(false) }
else { completion(true) }
}
to me, It looks not really efficient, because each time a user is trying to remove an element from this dictionary, I have to write to the database. which can slow down the process and to my understanding, the free tier of Firestore limits the number of writes.
Is there a better way, maybe deleting it right from the user's document? I tried to look for answers, but couldn't find anything that worked for me.
This one for example: Removing a dictionary element in Firebase looks like what I need to do, but I couldn't get it to work.
EDIT:
I tried deleting it like that
userRef.updateData([
USER_VOTES:[
id: FieldValue.delete()
]
]) { (error) in
if let _ = error { completion(false) }
}
The app crashes says:
Terminating app due to uncaught exception 'FIRInvalidArgumentException', reason: 'FieldValue.delete() can only appear at the top level of your update data
To be able to delete a specific field you should follow the steps mentioned here.
For your case I have created the following under collection 'voting':
So to delete vote2 field you should use:
// Get the `FieldValue` object
let FieldValue = require('firebase-admin').firestore.FieldValue;
// Create a document reference
let fieldRef = db.collection('voting').doc('votes');
// Remove the 'vote2' field from the document 'votes'
let removeField = fieldRef.update({
vote2: FieldValue.delete()
});
And here is the document after running the above:
EDIT :
If the data model is a map inside a document, for example:
Then here is how you can delete a field inside the array which is inside the document:
let docRef = db.collection('voting').doc('user1');
let removeField = docRef.set({'votes':
{['id_vote_1'] : FieldValue.delete()}}, {merge: true});
Here is the document after running the above:

why I still get deleted documents when getting data using listener from Firestore in Swift?

I trying to make an event app. and I add a new field in my events documents. I try to add "venue" field for my event documents
so before I run the app, I delete all the available data on my Firestore database. But when I retrieve my data back to the app, it is said that the "venue" is nil, it seems that the "venue" field is not exist, even though in fact, the "venue" field exist on my firestore database.
I suspect my app still retrieve my deleted documents. here is why
here is the code I use :
enum FirestoreCollectionReference {
case users
case events
case cities
case APIKey
private var path : String {
switch self {
case .users : return "users"
case .events : return "events"
case .cities : return "cities"
case .APIKey : return "secretAPIKeyKM"
}
}
func reference () -> CollectionReference {
return Firestore.firestore().collection(path)
}
}
FirestoreCollectionReference.events.reference()
.whereField("city", isEqualTo: selectedCity)
.whereField("eventType", isEqualTo: selectedEventType)
.whereField("coordinate", isGreaterThan: lesserGeopoint)
.whereField("coordinate", isLessThan: greaterGeopoint)
.order(by: "coordinate")
.order(by: "dateTimeStart", descending: true)
.limit(to: 20)
.addSnapshotListener { (snapshot, error) in
if let error = error {
completion(nil,eventListener)
print("Error when observing events document: \(error.localizedDescription)")
} else {
print("Successfully get events data from Firestore by Listener")
guard let documentsSnapshot = snapshot else {
completion(nil, eventListener)
return
}
let eventDocuments = documentsSnapshot.documents
print("the number of documents: \(eventDocuments.count)")
var eventsArray = [EventKM]()
for document in eventDocuments {
let eventDictionary = document.data()
let theEvent = EventKM(dictionary: eventDictionary)
eventsArray.append(theEvent)
}
completion(eventsArray,eventListener)
}
}
}
I try to print the number of documents, and it shows that I have 8 documents from this query, in fact, it should be only one document available in my database.
I try to delete the composite indexes from firebase console, but usually, after I delete the composite indexes, It will give an error + a link to generate the composite indexes in my debugging area on my Xcode, but after I delete the composite indexes, I don't get the error + link to generate the indexes, and give 8 documents (it should be one document only)
it seems the data is cached on my iOS app. isn't it? or is this a bug since Firestore is still in Beta version? I need to understand why and how to solve this issue so I can understand firebase better. Thanks in advance.
FireBase has cached in device so if user stay in outside of internet,
But still can use Firebase.
So You just remove your app in your simulator.
In my case, It fixed.

Inserting initial data after database creation in SQLite.swift

I have a SQLite database that I want to populate with some initial data.
Using SQLite.swift, this method seems to be working:
do {
let _ = try db.run( userDictionary.create(ifNotExists: false) {t in
t.column(wordId, primaryKey: true)
t.column(word, unique: true)
t.column(frequency, defaultValue: 1)
t.column(following, defaultValue: "")
})
} catch _ {
print("table already exists")
return
}
// Add some initial data for a new database
print("adding new data")
myMethodToInsertInitialData()
The way this works, though is that I am using ifNotExists: false to throw an error every time after the initial database creation. (Setting it to true would not throw an error (or allow the early return).) However, throwing an error on purpose every time except the first time seems like poor programming. I don't really mean it as an error. I just want to insert some data in a newly created database. Is there a better way to do this or is this what everyone does?
To check whether data is available in table or not in SQLite.swift
do
{
db = try Connection(YOUR_DB_PATH)
let intCount : Int64 = db.scalar("SELECT count(*) FROM yourTableName") as! Int64
if (intCount == 0)
{
//callWebServiceCall()
}
else
{
//getDataFromDB()
}
}
catch
{
print("error")
}

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