Sqlite shows EXC_BAD_ACCESS while executing query in ios - ios

The code I am using for executing the query is as below:
NSString *score_query = #"Insert into tbl_assessment (Question,Answer,Option1,Option2,Option3,Explanation,Used,ImageName,Reference) values ('All of the following questions would be asked in a lifestyle questionnaire except:','Do you feel any pain in your chest when you perform physical activity?','Does your occupation cause you anxiety (mental stress)?','Does your occupation require extended periods of sitting?','Do you partake in any recreational activities (golf, tennis, skiing, etc.)?','NULL','N','NULL','NULL')";
NSLog(#"%#",score_query);
[database executeQuery:score_query];
I have generated this query dynamically. But I have added the query directly to the string.
But when I tried to execute this insert query, app crashes on below function:
- (NSArray )executeQuery:(NSString )sql, ... {
va_list args;
va_start(args, sql);
NSMutableArray *argsArray = [[NSMutableArray alloc] init];
NSUInteger i;
for (i = 0; i < [sql length]; ++i)
{
if ([sql characterAtIndex:i] == '?')
[argsArray addObject:va_arg(args, id)];
}
va_end(args);
NSArray *result = [self executeQuery:sql arguments:argsArray];
[argsArray release];
return result;
}
The dropbox for the classes I am using for database operation is given below:
https://www.dropbox.com/s/c4haxqnbh0re41c/Archive%202.zip?dl=0
I have already referred the below link but can't understand.
Sqlite shows EXC_BAD_ACCESS in ios SDK

In your example, you are only passing one argument to execQuery:, but your implementation is trying to pull additional arguments from the varargs every time it sees a '?' in the string, of which there are numerous '?'s.
That's causing the crash.

Related

Xcode 13 - Count not working on NSMutableArray anymore

I have something like this in my code which worked fine for many years
NSMutableArray* DeviceList = [NSMutableArray alloc] init]; // Multi-dimensional (n x m) mutable Array filled throughout the code before arriving here:
if ([[DeviceList objectAtIndex:i] count] == 9))
{
[DeviceList removeObjectAtIndex:i];
}
I haven't touched the project for a while and now upgraded to Xcode 13 and get the following error:
Multiple methods named 'count' found with mismatched result, parameter type or attributes
The error list on the left pane shows 12 options where count has been declared.
So what has changed that the simple
someNSUInteger = [[someArray] count];
creates an error and what would be a good way to fix it in 100+ instances without casting every instance with (NSMutableArray*)?
Thanks a lot!
objectAtIndex returns "id" (objects of any type). Before it used to be permissive, and you were allowed to call any method on such (such as "count").
A better way to write this code would be to declare an intermediate variable:
NSArray *deviceRow = [DeviceList objectAtIndex:i];
if ([deviceRow count] == 9) {
or if you know the type if the items inside the array, let's say you have strings:
NSArray<NSString *> *deviceRow = [DeviceList objectAtIndex:i];
if ([deviceRow count] == 9) {
and even better:
NSArray<NSString *> *deviceRow = DeviceList[i];
if (deviceRow.count == 9) {
Another problem is that there's too many instances of that, and you'd rather not touch it everywhere.
One way to fix most of the 100 occurences is to define a wrapper class for DeviceList instead of using a "naked" NSMutableArray:
#interface MyDeviceList
- (nonnull instancetype)init;
- (nullable NSArray *)objectAtIndex:(NSUInteger)index;
... any other NSMutableArray methods used in the code ...
#end
and replace the array instantiation with:
MyDeviceList *DeviceList = [MyDeviceList new];
Hopefully you have much less instantiations than unsafe usages.
You could also have some helper methods in MyDeviceList like "countAtIndex" if it happens that you need it a lot.

How to execute multiple select statement in one query in sqlite in iOS?

Is it possible to execute two or more select statement in one query in SQLite? For example,
We can execute create or insert query,
NSString *create_query = #"create table if not exists Employee (id integer primary key, firstName text, lastName text);create table if not exists Department (id integer primary key, department text, devision text)";
By using,
sqlite3_exec(self.contactDB,[create_query UTF8String], NULL, NULL, &errorMessage) == SQLITE_OK)
we can execute it.
But if query is something like,
NSString *select_query = #"select * from Employee;select * from Department";
Then is it possible to execute? If yes then how to differentiate data from sqlite3_step?
Generally we are fetching data like,
if (sqlite3_prepare_v2(self.contactDB, [select_query UTF8String], -1, &statement, NULL) == SQLITE_OK) {
NSLog(#"prepared from data get");
while (sqlite3_step(statement) == SQLITE_ROW) {
NSString *department = [[NSString alloc]initWithUTF8String:(const char*)sqlite3_column_text(statement, 1)];
NSString *devision = [[NSString alloc]initWithUTF8String:(const char *)sqlite3_column_text(statement, 2)];
NSLog(#"Department : %#, Devision : %#",department,devision);
}
NSLog(#"errror1 is %s",sqlite3_errmsg(self.contactDB));
}
But if there is a two select statement then how to identify column and row in sqlite3_step?
We can execute two select statements together (i.e. select * from Employee;select * from Department ) in terminal, so it should some way in iOS I think.
Yes, you can use sqlite3_exec() to execute two SELECT statements in one call. You just have to provide a callback function where you handle the result rows. I've never used that feature, and how I understand the doc you're on your own to distinguish the rows of each statement; perhaps one can use the column count for that.
I advise against using sqlite3_exec() that way; it seems rather tedious and error-prone. sqlite3_prepare_*() should be the way to go, and it can only handle one result set (SELECT query), but you can have open multiple statements at a time, represented by the ppStmt handle. If you have problems with that you should describe them instead of posting a XY Problem question.
We can perform this by using C style callback function with sqlite3_exec.
There is no proper code available on internet (I haven't found!) so i would like to answer with code sample.
We can implement C - style callback method like
int myCallback(void *parameter, int numberOfColumn, char **resultArr, char **column)
{
NSLog(#"number of column %d",numberOfColumn); // numberOfColumn is return total number of column for correspond table
NSString *columnName = [[NSString alloc]initWithUTF8String:column[0]]; // This will return column name column[0] is for first, column[1] for second column etc
NSLog(#"column name is %#",columnName);
NSString *result = [[NSString alloc]initWithUTF8String:resultArr[2]]; // resultArr returns value for row with respactive column for correspond table. resultArr[2] considered as third column.
NSLog(#"result is %#",result);
return 0;
}
And we can call this callback function in our sqlite3_exec function like,
NSString *getData = #"select * from Department;select * from Employee";
if (sqlite3_exec(self.contactDB, [getData UTF8String], myCallback, (__bridge void *)(self), &err) == SQLITE_OK ) {
if (err) {
NSLog(#"error : %s",err);
}
else {
NSLog(#"executed sucessfully");
}
}
We have make bride : (__bridge void *)(self) as parameter of sqlite3_exec. We can pass NULL in this case because we have implemented c style function. But if we implement Objective - c style function or method then we must pass (__bridge void *)(self) as parameter.
So, By callback function we can execute multiple queries in one statement whether it is select type queries or else.
Reference : One-Step Query Execution Interface

Optimize apple system logs

+ (NSArray *)systemLogDictionariesForAppName:(NSString *)appName {
aslmsg q = asl_new(ASL_TYPE_QUERY);
asl_set_query(q, ASL_KEY_SENDER, [appName cStringUsingEncoding:NSASCIIStringEncoding], ASL_QUERY_OP_EQUAL);
aslresponse r = asl_search(NULL, q);
aslmsg m;
uint32_t i;
const char *key, *val;
NSMutableArray *systemLogDictionaries = [NSMutableArray array];
while (NULL != (m = aslresponse_next(r)))
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
for (i = 0; (NULL != (key = asl_key(m, i))); i++)
{
val = asl_get(m, key);
NSString *stringKey = [NSString stringWithCString:key encoding:NSUTF8StringEncoding];
NSString *stringVal = [NSString stringWithCString:val encoding:NSUTF8StringEncoding];
[dictionary setObject:stringVal forKey:stringKey];
}
[systemLogDictionaries addObject:dictionary];
}
aslresponse_free(r);
return systemLogDictionaries;
}
Above code will get apple system log. Problem is, it take around 8second to pull all the logs from Apple System Log (ASL). Is there any way to optimize asl_set_query to get data faster or any other way which I am missing.
Note: Can we create a ASL query which will take time stamp and we can get less number of data to process. This will solve the problem I think.
ASL supports a few different logging levels, so you could specify a more restrictive level.
For example you can add another query (according to the man page they are joined via the logical AND):
// ...
asl_set_query(q, ASL_KEY_SENDER, [appName cStringUsingEncoding:NSASCIIStringEncoding], ASL_QUERY_OP_EQUAL);
// 3 is error messages
asl_set_query(q, ASL_KEY_LEVEL, "3", ASL_QUERY_OP_LESS_EQUAL | ASL_QUERY_OP_NUMERIC);
//-- Check for time --//
/* A dumped entry with your code looks like:
ASLMessageID = 1825403;
"CFLog Local Time" = "2013-07-20 08:33:12.943";
"CFLog Thread" = 951f;
Facility = "com.apple.Safari";
GID = 20;
Host = "XXX.local";
Level = 4;
Message = "CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon in dictionary on line 3. Parsing will be abandoned. Break on _CFPropertyListMissingSemicolon to debug.";
PID = 183;
ReadUID = 501;
Sender = Safari;
Time = 1374305592;
TimeNanoSec = 943173000;
UID = 501;
Time is a Unix timestamp, so you can use it in your query with ASL_KEY_TIME and one of these operators: ASL_QUERY_OP_EQUAL, ASL_QUERY_OP_GREATER, ASL_QUERY_OP_GREATER_EQUAL, ASL_QUERY_OP_LESS, ASL_QUERY_OP_LESS_EQUAL, ASL_QUERY_OP_NOT_EQUAL
The code below, generates a unix timestamp for yesterday and dumps all messages that occurred yesterday or later.
(Nevermind the dirty/hacky way I generate the timestamp, that was just for testing purposes)
*/
NSDate *yesterday = [NSDate dateWithTimeIntervalSinceNow: -(60.0f*60.0f*24.0f)];
NSString *theDate = [NSString stringWithFormat:#"%d", (int)[yesterday timeIntervalSince1970]];
asl_set_query(q, ASL_KEY_TIME, [theDate cStringUsingEncoding:NSASCIIStringEncoding], ASL_QUERY_OP_GREATER_EQUAL | ASL_QUERY_OP_NUMERIC);
aslresponse r = asl_search(NULL, q);
//...
For some more information on the different error levels check: http://www.cocoanetics.com/2011/03/accessing-the-ios-system-log/
Note that, depending on the level you set and the level your log messages are, further filtering may have no effect (ie. if all messages that are actually logged for your app are of the same level)
Further note, unlike the debug level querying, I haven't yet used the timestamp querying in any productive code, but in a test it seems to work perfectly fine and is doing what it is supposed to do.

sqlite3_column_text returns only 255 bytes even though there is data in db

Ladies and Gents,
I'm using FMDB in an iOS application. I have some text in columns which exceeds 255 bytes. I use BASE and browse the database file, and I can query the length of the data in the columns... So I know my data is there and that it is longer than 255...
YET, when trying to pull out data from these columns, I always get a C string of exactly 255 bytes, and I have no clue whatsoever why.
Here's some piece of code (basically from - (NSString*)stringForColumnIndex:(int)columnIdx {
of FMDB FMResultSet.m, and I can trap the error already there.
- (NSString*)stringForColumnIndex:(int)columnIdx {
if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
return nil;
}
const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx);
int xx = strlen(c); //added by me, already here it is only 255 bytes
if (!c) {
// null row.
return nil;
}
return [NSString stringWithUTF8String:c];
}
What am I missing? I'm using the standard supplied libsqlite3.dylib, FMDB includes sqlite.h. I tried to google similar problems to no avail for hours now.
This is neither a FMDB nor SQLite limitation. I use FMDB to return strings longer than 255 characters from my SQLite database all the time. I just checked:
FMResultSet *rs = [fmdb executeQuery:#"select personnel_description from personnel where personnel_id=297"];
[rs next];
NSString *test2 = [rs stringForColumnIndex:0];
NSLog(#"%s len=%d string=%#", __FUNCTION__, [test2 length], test2);
[rs close];
and I got my 8429 character string, same length that BASE told me it was going to be.
This makes me wonder if there's something strange about your data (e.g. binary data instead of characters, DBCS, etc.). Have you tried returning as an object rather than a string and examining that? Perhaps confirm that it is a NSString and not a NSData result set, e.g.:
id test3 = [rs objectForColumnIndex:0];
if ([test3 isKindOfClass:[NSString class]])
NSLog(#"It's a string");
else
NSLog(#"It's not");
If you really think this is a FMDB issue, you could try posting this question at the FMDB discussion. Also, I'd make sure you have the latest FMDB code, which you can retrieve from that same site.

Multiple Queries not Running in FMDB

I'm using FMDB to create a SQLite database on iPhone. I have a initial.sql that is of the form
CREATE TABLE Abc ... ;
CREATE TABLE Def ... ;
I load this by loading the file into an NSString and running it
NSString * str = // string from file initial.sql
[db executeUpdate: str];
This succeeds but later on I get a failure:
no such table: Def
It's clear that the second statement is not being called. How can I do this so that all of the queries will be called?
According to the SQLite documentation:
"The routines sqlite3_prepare_v2(), sqlite3_prepare(), sqlite3_prepare16(), sqlite3_prepare16_v2(), sqlite3_exec(), and sqlite3_get_table() accept an SQL statement list (sql-stmt-list) which is a semicolon-separated list of statements."
So, this should all work.
I got bitten by this one too; it took me an entire morning of stepping through FMDatabase and reading the sqlite3 API documentation to find it. I am still not entirely sure about the root cause of the issue, but according to this bug in PHP, it is necessary to call sqlite3_exec instead of preparing the statement with sqlite3_prepare_v2 and then calling sqlite3_step.
The documentation does not seem to suggest that this behaviour would happen, hence our confusion, and I would love for someone with more experience with sqlite to come forward with some hypotheses.
I solved this by developing a method to execute a batch of queries. Please find the code below. If you prefer, you could rewrite this into a category instead of just adding it to FMDatabase.h, your call.
Add this to the FMDatabase interface in FMDatabase.h:
- (BOOL)executeBatch:(NSString*)sql error:(NSError**)error;
Add this to the FMDatabase implementation in FMDatabase.m:
- (BOOL)executeBatch:(NSString *)sql error:(NSError**)error
{
char* errorOutput;
int responseCode = sqlite3_exec(db, [sql UTF8String], NULL, NULL, &errorOutput);
if (errorOutput != nil)
{
*error = [NSError errorWithDomain:[NSString stringWithUTF8String:errorOutput]
code:responseCode
userInfo:nil];
return false;
}
return true;
}
Please note that there are many features missing from executeBatch which make it unsuitable for a lot of purposes. Specifically, it doesn't check to see if the database is locked, it doesn't make sure FMDatabase itself isn't locked, it doesn't support statement caching.
If you need that, the above is a good starting point to code it yourself. Happy hacking!
FMDB v2.3 now has a native wrapper for sqlite3_exec called executeStatements:
BOOL success;
NSString *sql = #"create table bulktest1 (id integer primary key autoincrement, x text);"
"create table bulktest2 (id integer primary key autoincrement, y text);"
"create table bulktest3 (id integer primary key autoincrement, z text);"
"insert into bulktest1 (x) values ('XXX');"
"insert into bulktest2 (y) values ('YYY');"
"insert into bulktest3 (z) values ('ZZZ');";
success = [db executeStatements:sql];
It also has a variant that employs the sqlite3_exec callback, implemented as a block:
sql = #"select count(*) as count from bulktest1;"
"select count(*) as count from bulktest2;"
"select count(*) as count from bulktest3;";
success = [db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {
NSInteger count = [dictionary[#"count"] integerValue];
NSLog(#"Count = %d", count);
return 0; // if you return 0, it continues execution; return non-zero, it stops execution
}];
Split Batch Statement
Add in .h file:
#import "FMSQLStatementSplitter.h"
#import "FMDatabaseQueue.h"
FMSQLStatementSplitter can split batch sql statement into several separated statements, then [FMDatabase executeUpdate:] or other methods can be used to execute each separated statement:
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:databasePath];
NSString *batchStatement = #"insert into ftest values ('hello;');"
#"insert into ftest values ('hi;');"
#"insert into ftest values ('not h!\\\\');"
#"insert into ftest values ('definitely not h!')";
NSArray *statements = [[FMSQLStatementSplitter sharedInstance] statementsFromBatchSqlStatement:batchStatement];
[queue inDatabase:^(FMDatabase *adb) {
for (FMSplittedStatement *sqlittedStatement in statements)
{
[adb executeUpdate:sqlittedStatement.statementString];
}
}];

Resources