Swift 5 - Cannot INSERT data into SQLite table after performing SELECT - ios

I am experiencing an issue during iOS app development.
Xcode Version: 11
Swift Version: 5
There are two views for the app - First_View and Second_View.
On the First_View, there is a collection view which is to list out the data in table "custom_location" from SQLite. There is a button on First_View which button is to navigate to Second_View.
On Second_View, there is a collection view which is to list out data in table "all_location" from SQLite. User can select the item and that item will be inserted into table "custom_location".
The behavior of current issue:
If there is any record in table "custom_location", the First_View will display those data when the app is launched. Then I try to click the button to navigate the Second_View and select an item. However, that item cannot be inserted into "custom_location".
I notice if the "custom_location" in the beginning, the Second_View is able to insert data into "custom_location".
Related code for selecting and inserting from SQLite:
public static func getCustomLocationList() -> [Int] {
var customLocationList:[Int] = []
var locationId:Int = -1
var db :SQLiteConnect?
//Database connection path
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let sqlitePath = urls[urls.count-1].absoluteString + Settings.dbName
db = SQLiteConnect(path: sqlitePath)
if let mydb = db{
let statement = mydb.fetch("custom_location_list", cond: "location_id > 0", order: "id DESC")
while sqlite3_step(statement) == SQLITE_ROW{
locationId = Int(sqlite3_column_int(statement, 1))
customLocationList.append(locationId)
}
}
return customLocationList
}
public static func insertCustomLocation(_ locationId: Int){
var db :SQLiteConnect?
//Database connection path
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let sqlitePath = urls[urls.count-1].absoluteString + Settings.dbName
db = SQLiteConnect(path: sqlitePath)
if let mydb = db {
mydb.insert("custom_location_list", rowInfo: ["location_id":"\(locationId)"])
}
}
In my SQLiteConnect.swift, the function "fetch" and "insert" is:
func fetch(_ tableName :String, cond :String?, order :String?) -> OpaquePointer {
var statement :OpaquePointer? = nil
var sql = "select * from \(tableName)"
if let condition = cond {
sql += " where \(condition)"
}
if let orderBy = order {
sql += " order by \(orderBy)"
}
sqlite3_prepare_v2(self.db, sql.cString(using: String.Encoding.utf8), -1, &statement, nil)
return statement!
}
func insert(_ tableName :String, rowInfo :[String:String]) -> Bool {
var statement :OpaquePointer? = nil
let sql = "insert into \(tableName) " + "(\(rowInfo.keys.joined(separator: ","))) " + "values (\(rowInfo.values.joined(separator: ",")))"
if sqlite3_prepare_v2(self.db, sql.cString(using: String.Encoding.utf8), -1, &statement, nil) == SQLITE_OK {
if sqlite3_step(statement) == SQLITE_DONE {
return true
}
sqlite3_finalize(statement)
}
return false
}

This issue has been fixed.
The root cause is the DB connect is opened on other method and I set "return" in the middle of that function... Then the DB connection has not been terminated when the function is completed.

Related

facing issue regarding fetching data from a specific column from sqlite in swift

while working on iOS app.. I am facing a problem while fetching a data from sqlite...
below is the code which I am using for fetching the data from database...
func getQazaNmazdata() -> [QazaDB]
{
var dbData = [QazaDB]()
let qstring = "select Counter from Qaza"
var queryStatement: OpaquePointer? = nil
// 1
if sqlite3_prepare_v2(db, qstring, -1, &queryStatement, nil) == SQLITE_OK {
let data = QazaDB()
while sqlite3_step(queryStatement) == SQLITE_ROW
{
let counter = sqlite3_column_int(queryStatement, 0)
data.counter = Int(counter)
dbData.append(data)
} // while loop end
} // if end
var n:Int = 0
for i in dbData
{
print("Value of Counter at Row " + String(n) + " = " + String(i.counter))
n += 1
}
return dbData
}
the outcome which I am receiving from running the above code is
Value of Counter at Row 0 = 8
Value of Counter at Row 1 = 8
Value of Counter at Row 2 = 8
Value of Counter at Row 3 = 8
Value of Counter at Row 4 = 8
the values are repeating in rows..
the image of a table in database.. I am trying to fetch the value of column Counter
I suspect the QazaDB is a reference type (class) and because of that it's showing only the last assigned value. Probably you need to change your while loop like:
while sqlite3_step(queryStatement) == SQLITE_ROW
{
let data = QazaDB()
let counter = sqlite3_column_int(queryStatement, 0)
data.counter = Int(counter)
dbData.append(data)
} // while loop end
You can also change the QazaDB to a struct, which can also fix the issue.

Swift: Get number of rows in result set

I want to get number of rows in result set. I am using FMDB for database operations. There is also one function hasAnotherRow() but it returns true everytime. Below is my code.
let selectQuery = "SELECT * FROM \(tableName) WHERE chrSync = 'Y'"
if let rs = database.executeQuery(selectQuery, withArgumentsIn: [])
{
// Here I want to check if rs has more than 0 rows
while(rs.next()){
let dir = rs.resultDictionary
let json = JSON(dir)
data.append(json)
print("Has another: \(rs.hasAnotherRow())") // It always returns true
}
let json = JSON(data)
return json
}
I am new in IOS, so please share me link if is there already answered.
Thanks.
I think here is the key (https://github.com/aws-amplify/aws-sdk-ios/tree/master/AWSCore/FMDB):
You must always invoke -[FMResultSet next] before attempting to access
the values returned in a query, even if you're only expecting one:
FMResultSet *s = [db executeQuery:#"SELECT COUNT(*) FROM myTable"];
if ([s next]) {
int totalCount = [s intForColumnIndex:0];
}
Swift
var s: FMResultSet? = db.executeQuery("SELECT COUNT(*) FROM myTable", withArgumentsIn: nil)
if s?.next() != nil {
var totalCount: Int? = s?.int(forColumnIndex: 0)
}
It can be used any SELECT, if the select returns rows then the last value for totalCount will have the number of rows in your FMResultSet.

SQLite.Swift how to get random row never repeating

I'm experimenting with SQlite.Swift in iOS and I'm trying to randomly pick a row from a table in an SQLite database.
The table is movies and its columns are movieID, title genre and seen.
Problem:
Movies should be picked randomly but as soon as I set seen to "yes" they should no longer be picked.
func randomRow() -> Int {
let path = NSSearchPathForDirectoriesInDomains(
.documentDirectory,
.userDomainMask, true)
.first!
let database = try! Connection("\(path)/movies.sqlite3")
var randomRow = Int(arc4random_uniform(UInt32(try!
database.scalar(movies.where(seen != "yes")
.select(movieID.distinct.count)))))
while previousNumber == randomRow {
randomRow = Int(arc4random_uniform(UInt32(try!
database.scalar(movies.where(seen != "yes")
.select(movieID.distinct.count)))))
}
previousNumber = randomRow
return randomRow
}
let destRow = randomRow()
Now I want to use this random row number to be catched from the table to lead the title and genre of the random movie into let ("dispMovie" and "movieGenre") to be output into a UITextField.
let path = NSSearchPathForDirectoriesInDomains(
.documentDirectory,
.userDomainMask, true)
.first!
let database = try! Connection("\(path)/filme.sqlite3")
for movie in try! database.prepare(
self.movies.where(
seen != "yes" && movieID == "\(destRow)")) {
dispMovie = movie[title]
movieGenre = movie[genre]
}
outputTextField.text = "\(dispMovie), \(movieGenre)"
This code works fine. The only problem is that movies I have seen will still be diplayed because I have this .count in the random row function.
I've also tried to call Raw SQLite with this one:
let newMovie = try! database.scalar(
"SELECT title, genre FROM movies WHERE seen != yes
ORDER BY RANDOM() LIMIT 1") as! String
outputTextField.text = newMovie
But this only displays the title and not the genre and looking for the genre in an extra line is not possible because the movie is picked from a random row. I've tried to reference the movie title in the Raw code but that crashes the app.
Thanks for help and hints.
No Christmas 'till this is done!
After days of thinking and running against walls suddenly the good old while loop came to my mind. It turned out working so I'm leaving this as an answer to my own question:
Forget about the randomRow(). No need for this. I focused on the SQLite RAW in the first place and continued with a combination of scalar, where and select from the SQLite.Swift library. That finally got me this:
Posting my whole UIViewController so you can test yourself:
class TestView: UIViewController {
//Table
let movies = Table("movies")
//Expressions
let movieId = Expression<String>("ID")
let movieTitle = Expression<String>("movieTitle")
let genre = Expression<String>("genre")
let seen = Expression<String>("seen")
//to find with database connection
var randomMovie = String()
var movieDetails = [String]()
//Array of all movies seen
var seenMovies = [String]()
//TestLabel to check everything
#IBOutlet weak var testLabel: UILabel!
#IBAction func testButton(_ sender: Any) {
//Open database connection
let path = NSSearchPathForDirectoriesInDomains(
.documentDirectory, .userDomainMask, true
).first!
let database = try! Connection("\(path)/movies.sqlite3")
//Check column "seen" and add all movie Titles to Array seenMovies
for movie in try! database.prepare(self.movies.where(seen == "yes")) {
self.seenMovies.append(movie[movieTitle])
}
//Select random row from movies Table with SQLite RAW as String
self.randomMovie = "\(try! database.scalar("SELECT movieTitle FROM movies WHERE seen != yes ORDER BY RANDOM() LIMIT 1"))"
//Create Array of details for that randomly picked movie
//Use scalar, where and select to find details from the picked movie above
self.movieDetails = ["\(randomMovie!)",
"\(try! database.scalar(movies.where(movieTitle == "\(randomMovie!)").select(genre)))"]
//And here's the "unique" thing...
//while randomMovie is also an element in seenMovies, loop until it's not
while seenMovies.contains("\(randomMovie!)") {
randomMovie = "\(try! database.scalar("SELECT movieTitle FROM movies WHERE seen != yes ORDER BY RANDOM() LIMIT 1"))"
}
//let testLabel show the results and see that no seen movie will be picked
testLabel.text = "\(details)\n\n\(erraten)"
//To prevent the Array from being appended over and over:
self.seenMovies.removeAll()
}
}
Since I still consider myself a beginner (started coding in October '17) please feel free to optimize this if possible. All my tests went very well and no movie was ever repeated when turned to "seen".
Found a solution from SQLite.Swift issue filed in 2016.
let db = Connection(databasePath)
let movies = Table("movies")
let query = movies.order(Expressions<Int>.random()).limit(1)
for row in try! db.prepare(query) {
// handle query result here
}

FMDB: NULL Values Are Retrieved as Empty Strings

I'm retrieving a customer record with FMDB and Swift using the (simplified) function below. When the optional value in the title column is NULLthe title member of the returned customer object is an empty string rather than nil, which is misleading. Can this be re-written such that NULL values are retrieved as nil? -- Ideally without testing for empty strings and setting nil explicitly (also wrong if the value is in fact an empty string)?
func getCustomerById(id: NSUUID) -> Customer? {
let db = FMDatabase(path: dbPath as String)
if db.open() {
let queryStatement = "SELECT * FROM Customers WHERE id = ?"
let result = db.executeQuery(queryStatement, withArgumentsInArray: [id.UUIDString])
while result.next() {
var customer = Customer();
customer.id = NSUUID(UUIDString: result.stringForColumn("customerId"))
customer.firstName = result.stringForColumn("firstName")
customer.lastName = result.stringForColumn("lastName")
customer.title = result.stringForColumn("title")
return customer
}
}
else {
println("Error: \(db.lastErrorMessage())")
}
return nil
}
The NULL values are returned as nil:
db.executeUpdate("create table foo (bar text)", withArgumentsInArray: nil)
db.executeUpdate("insert into foo (bar) values (?)", withArgumentsInArray: ["baz"])
db.executeUpdate("insert into foo (bar) values (?)", withArgumentsInArray: [NSNull()])
if let rs = db.executeQuery("select * from foo", withArgumentsInArray: nil) {
while rs.next() {
if let string = rs.stringForColumn("bar") {
println("bar = \(string)")
} else {
println("bar is null")
}
}
}
That outputs:
bar = bazbar is null
You might want to double check how the values were inserted. Specifically, were empty values added using NSNull? Or perhaps open the database in an external tool and verify that the columns are really NULL like you expected.

FMResultSet returning nil in another ViewController

I am using FMDB wrapper for my database.I can fetch data using FMResultSet,but when I am trying to return FMResultSet to another ViewController,it returns nil.I am calling my database from here
var resultSet: FMResultSet! = db.getUserById(1)
if(resultSet != nil) {
self.setUserInfo(resultSet)
}
and here is my database coding part
func getUserById(userId: Int) -> FMResultSet {
let sharedInstance = DatabaseHandler()
var database: FMDatabase? = nil
var resultSet: FMResultSet! = sharedInstance.database!.executeQuery("SELECT * FROM user_info WHERE user_id = ?", withArgumentsInArray: [userId])
if(resultSet != nil) {
while resultSet.next() {
var name: String = "USER_NAME"
var location = "USER_LOCATION"
println("Name: \(resultSet.stringForColumn(name))")
println("Location: \(resultSet.stringForColumn(location))")
}
}
sharedInstance.database!.close()
return resultSet
}
When I am printing those values,it shows the values in console,but when I am returning the resultSet,it appears to be nil
What have I done worng?
Your resultSet is only valid while the database is open. Return something other than the resultSet (i.e., a wrapper object/dictionary/whatever). Generally you iterate the resultSet and pull out what you need and use that. Alternately you hand out the resultSet to the caller and it calls .next and closes it when done.

Resources