FMResultSet returning nil in another ViewController - ios

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.

Related

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

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.

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.

How can I get originalvalues from EF savechanges for complex primary key

i've implemented audit log for all my db objects but i'm running into an issue because of a special case.
i have code similar to the following.
private IEnumerable<CDMA_CHANGE_LOG> GetAuditRecords(DbEntityEntry ent, string userId, string primaryKeyId, bool updateFlag, object originalEntity)
{
List<CDMA_CHANGE_LOG> result = new List<CDMA_CHANGE_LOG>();
string changeId = Guid.NewGuid().ToString();
TableAttribute tableAttr = ent.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), false).SingleOrDefault() as TableAttribute;
// Get table name (if it has a Table attribute, use that, otherwise get the pluralized name)
string entityName = tableAttr != null ? tableAttr.Name : ObjectContext.GetObjectType(ent.Entity.GetType()).Name;
var changeTime = DateTime.UtcNow;
if (ent.State == EntityState.Modified)
{
foreach (var prop in ent.OriginalValues.PropertyNames)
{
//we cant use this because getdatabasevalues will error out when there are 2 rows in the db for this primarykey (U,A)
//var originalValue = ent.GetDatabaseValues().GetValue<object>(prop) == null ? "" : ent.GetDatabaseValues().GetValue<object>(prop).ToString();
var currentValue = ent.CurrentValues[prop] == null ? "" : ent.CurrentValues[prop].ToString();
// For updates, we only want to capture the columns that actually changed
var primaryKey = GetPrimaryKeyValue(ent);
if (originalValue != currentValue)
{
result.Add(new CDMA_CHANGE_LOG()
{
USERID = userId,
DATECHANGED = changeTime,
EVENTTYPE = "M", // Modified
ENTITYNAME = entityName,
PRIMARYKEYVALUE = primaryKey.ToString(),
PROPERTYNAME = prop,
OLDVALUE = originalValue,
NEWVALUE = currentValue,
CHANGEID = changeId
});
}
}
}
return result;
}
now i cant use originalValue = ent.GetDatabaseValues().GetValue(prop) because the records have two fields as primary key so i thought i could query the original value manually from the controller and send as
object originalEntity
so i'm wondering how do i cast originalEntity to the current entity
var originalValues = Convert.ChangeType(originalEntity, ent.GetType());
does not work.
i'm finding it difficult to get. thanks for any help.

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.

Parse via Swift with Complex Queries

Im having some difficulty with creating complex queries in Parse. What I'm looking to achieve is searching the _Users class and not returning myself as a result and if an Invite already exists in the Invites class, show Pending text rather than Add Button and if both myself and user have already accepted invite, dont show them at all.
I've achieved some of it but I'm not sure i'm doing it the most efficient way. For instance, I first query the _User class and find any users that match the search terms, then loop through and if the objectId == currentUser().objectId, skip that record. Then I run another query in that loop on each record to see if there are any pending invites, if so, i set a flag to true for Pending however i've done that search not in the background because i was having isses with the first query block finishing before the second and my flag not getting set first. So would I then do a third query to see if the field is accepted? Or is there a way to make this all one big query? My code is below for the searching:
func performSearchForText(text: String, completion: SearchComplete) {
state = .Loading
// Query Parse
var containsDisplayName = PFQuery(className:"_User")
containsDisplayName.whereKey("displayName", containsString: text)
var containsUsername = PFQuery(className: "_User")
containsUsername.whereKey("username", containsString: text)
var query = PFQuery.orQueryWithSubqueries([containsUsername, containsDisplayName])
query.findObjectsInBackgroundWithBlock {
(results: [AnyObject]?, error: NSError?) -> Void in
//self.state = .NotSearchedYet
var success = false
if error == nil {
// Found results
// Set Result state to either Results or NoResults
if let results = results {
//println(results)
if results.count == 0 {
self.state = .NoResults
} else {
// Read Results into UserSearchResult Array
// Dont show self in results
// If user has already accepted a request, dont show
// If they have already invited, show Pending instead of button
var userSearchResults = [UserSearchResult]()
var searchResult = UserSearchResult()
for result in results {
if result.objectId != PFUser.currentUser()?.objectId {
// Query invites table to see if they already are accepted with user
// or if a pending invite exists
// Set invite or accepted respectively
var invitedQuery = PFQuery(className: "Invites")
invitedQuery.whereKey("pending", equalTo: true)
invitedQuery.whereKey("inviteToUser", equalTo: result.objectId!!)
var invitedQueryResults = invitedQuery.findObjects()
if invitedQueryResults?.count > 0 {
self.pendingInvite = true
}
searchResult.displayName = result["displayName"] as! String
searchResult.emailAddress = result["username"] as! String
searchResult.inviteUserID = result.objectId!!
searchResult.invited = self.pendingInvite
searchResult.accepted = false
userSearchResults.append(searchResult)
}
}
if userSearchResults.count > 0 {
self.state = .Results(userSearchResults)
} else {
self.state = .NoResults
}
}
success = true
}
dispatch_async(dispatch_get_main_queue()) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
completion(success)
}
} else {
// Error, print it
println(error)
}
}
}
Unless there's a lot more to your invites table, I'd suggest removing it and adding an "invitePending" field to your user table.
Then you can just further refine your other queries by using wherekey("invitePending", equalTo: true).
Additionally, you can do something like this at the start so you don't have to check for currentUser in the query completion block:
containsDisplayName.whereKey("objectId", notEqualTo: PFUser.currentUser()!.objectId!)
But you definitely don't need the invites table if it isn't storing lots of other info. If you have to keep it as a separate table, using a pointer (for 1 to 1) or a PFRelation (for 1 to many) would save you the headache of that inner query.

Resources