Here is the code that using SQLite.
This function will be call from some threads.
I don't know well, following code is correct one or not.
There are lock/unlock code.
Please advice me whether I used lock/unlock correctly.
- (NSMutableArray*) Query:(NSString *)query_str forColumn:(NSString*)mycolumn
{
sqlite3_stmt *statement;
const char *query_stmt = [query_str UTF8String];
[dbLock lock];
if (sqlite3_prepare_v2(contactDB, query_stmt, -1, &statement, NULL) != SQLITE_OK) {
[dbLock unlock];
return nil;
}
NSMutableArray *queryResult = [NSMutableArray array];
while (sqlite3_step(statement) == SQLITE_ROW) {
NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];
int num_of_fields = sqlite3_column_count(statement);
for (int i = 0; i < num_of_fields; i++) {
NSString * col_name = [[NSString alloc] initWithUTF8String:sqlite3_column_name(statement, i)];
const char * value = (const char *)sqlite3_column_text(statement, i);
NSString * col_text;
if (value)
col_text = [[NSString alloc] initWithUTF8String:value];
else
col_text = #"";
if (col_name && col_text) {
if ([col_text isEqualToString:#"--"] == NO && [col_text isEqualToString:#""] == NO)
[dict setObject:col_text forKey:col_name];
}
}
if (mycolumn == nil) {
[queryResult addObject:dict];
} else {
NSString *tmp = [dict objectForKey:mycolumn];
if (tmp != nil) {
[queryResult addObject:tmp];
}
}
}
sqlite3_finalize(statement);
[dbLock unlock];
return queryResult;
}
The locking is around all the database access functions, so as long as all the other database access code also does similar locking, the database is protected.
The query_str access is outside the lock, so it is not protected against concurrent modifications. Whether that could happen in your program is something only you can decide.
Related
- (NSMutableArray*)getAllDataFromTableUSERINFO:(NSString *)fetchQuery {
NSMutableArray *resultArray1 =[[NSMutableArray alloc]init];
#autoreleasepool {
if (sqlite3_open([[self getDBPath]UTF8String],&database)==SQLITE_OK){
sqlite3_stmt *stmt;
const char *sqlfetch=[fetchQuery UTF8String];
if (sqlite3_prepare(database, sqlfetch, -1,&stmt, NULL)==SQLITE_OK) {
while (sqlite3_step(stmt)==SQLITE_ROW){
RM_USER_INFO *UserInfo = [[RM_USER_INFO alloc]init];
UserInfo.uid=[NSString stringWithUTF8String:(char *)sqlite3_column_text(stmt,0)];
UserInfo.username=[NSString stringWithUTF8String:(char *)sqlite3_column_text(stmt,1)];
UserInfo.password=[NSString stringWithUTF8String:(char *)sqlite3_column_text(stmt,2)];
UserInfo.fname=[NSString stringWithUTF8String:(char *)sqlite3_column_text(stmt,3)];
UserInfo.mname=[NSString stringWithUTF8String:(char *)sqlite3_column_text(stmt,4)];
[resultArray1 addObject:UserInfo];
}
}
sqlite3_finalize(stmt);
stmt=nil;
sqlite3_close(database);
sqlite3_release_memory(120);
}
}
return resultArray1;
}
Get always EXE_BAD_ACCESS after fews operations
Try this function:
static NSInteger DBBUSY;
+(NSMutableArray *)executeQuery:(NSString*)str{
sqlite3_stmt *statement= nil;
sqlite3 *database;
NSString *strPath = Your Database path here;
while(DBBUSY);
DBBUSY = 1;
NSMutableArray *allDataArray = [[NSMutableArray alloc] init];
if (sqlite3_open([strPath UTF8String],&database) == SQLITE_OK) {
if (sqlite3_prepare_v2(database, [str UTF8String], -1, &statement, NULL) == SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
NSInteger i = 0;
NSInteger iColumnCount = sqlite3_column_count(statement);
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
while (i< iColumnCount) {
NSString *str = [self encodedString:(const unsigned char*)sqlite3_column_text(statement, i)];
NSString *strFieldName = [self encodedString:(const unsigned char*)sqlite3_column_name(statement, i)];
if(!str)
str = #"";
[dict setObject:str forKey:strFieldName];
i++;
}
[allDataArray addObject:dict];
}
}
DBBUSY = 0;
sqlite3_finalize(statement);
}
sqlite3_close(database);
return allDataArray;
}
This may help you :)
My question is how to insert a fetched JSON array data into sqlite database.
I have fetched JSON data which is an array of dictionaries.
My code to save JSON result looks like this:
-(BOOL) saveApiResults: (NSString *)tableName : (NSArray *)data
{
BOOL saveSuccess = NO;
#try {
const char *dbPath = [databasePath UTF8String];
if(sqlite3_open(dbPath,&database)==SQLITE_OK) {
sqlite3_exec(database, "BEGIN", 0, 0, 0);
//pass an array containing json dictionary to below line
NSDictionary *rowData=[data objectAtIndex:0];
NSArray *keyArray = [rowData allKeys];
NSLog(#"key array %#",keyArray);
NSString *insertSQL=#"INSERT INTO ";
insertSQL=[insertSQL stringByAppendingString:#"moodsdata"];
insertSQL=[insertSQL stringByAppendingString:#" VALUES("];
for(int j=0;j<[keyArray count];j++)
{
insertSQL=[insertSQL stringByAppendingString:#"?"];
if(j<[keyArray count]-1)
insertSQL=[insertSQL stringByAppendingString:#","];
}
insertSQL=[insertSQL stringByAppendingString:#");"];
NSLog(#"query : %# ",insertSQL);
const char *sqlstatement = [insertSQL UTF8String];
sqlite3_stmt *compiledstatement;
if(sqlite3_prepare_v2(database,sqlstatement , -1, &compiledstatement, NULL)==SQLITE_OK) {
for (NSUInteger i = 0; i < [data count]; i++) {
NSDictionary *rowData=[data objectAtIndex:0];
for(int j=0;j<[keyArray count];j++) {
NSString *val = #"";
NSString *value=(NSString *)[rowData objectForKey:[keyArray objectAtIndex:j]];
if((value != nil) && (![value isEqual:[NSNull null]]))
val=[NSString stringWithFormat:#"%#",value];
NSLog(#"values %#",val);
sqlite3_bind_text(compiledstatement,j+1,[val UTF8String], -1, SQLITE_TRANSIENT);
}
if(sqlite3_step(compiledstatement) != SQLITE_DONE) {
NSLog(#"ERROR");
}
sqlite3_clear_bindings(compiledstatement);
sqlite3_reset(compiledstatement);
}
sqlite3_exec(database, "COMMIT", 0, 0, 0);
saveSuccess = YES;
NSLog(#"RESULTS SAVED SUCCESSFULLY!");
} else {
NSLog(#"StatemenT FAILED (%s)", sqlite3_errmsg(database));
}
sqlite3_finalize(compiledstatement);
} else {
NSLog(#"Statement FAILED (%s)", sqlite3_errmsg(database));
}
}
#catch (NSException *exception) {
NSLog(#"NSException : %#",exception.description);
}
#finally {
sqlite3_close(database);
}
return saveSuccess;
}
My question is when I try to pass the JSON array to this method it only saves the value for first array object . i.e. only first dictionary values get saved. Please tell me what I am doing wrong.
You primary issue is that you mistakenly access data[0] instead of data[i] in your loop that does the actual inserting.
But there are lots of other little issues with this code. Here's your code all cleaned up:
-(BOOL) saveApiResults:(NSString *)tableName data:(NSArray *)data
{
BOOL saveSuccess = NO;
const char *dbPath = [databasePath UTF8String];
if (sqlite3_open(dbPath,&database) == SQLITE_OK) {
sqlite3_exec(database, "BEGIN", 0, 0, 0);
//pass an array containing json dictionary to below line
NSDictionary *rowData = data[0];
NSArray *keyArray = [rowData allKeys];
NSLog(#"key array %#",keyArray);
NSMutableString *insertSQL = #"INSERT INTO moods data VALUES(";
for (NSInteger j = 0; j < [keyArray count]; j++)
{
if (j) {
[insertSQL appendString:#","];
}
[insertSQL appendString:#"?"];
}
[insertSQL appendString:#");"];
NSLog(#"query : %# ",insertSQL);
const char *sqlstatement = [insertSQL UTF8String];
sqlite3_stmt *compiledstatement;
if (sqlite3_prepare_v2(database, sqlstatement, -1, &compiledstatement, NULL) == SQLITE_OK) {
for (NSDictionary *rowData in data) {
for (NSInteger j = 0; j < [keyArray count]; j++) {
NSString *val = #"";
NSString *value = rowData[keyArray[j];
if (value && ![value isEqual:[NSNull null]]) {
val = [NSString stringWithFormat:#"%#",value];
}
NSLog(#"values %#",val);
sqlite3_bind_text(compiled statement, j + 1, [val UTF8String], -1, SQLITE_TRANSIENT);
}
if (sqlite3_step(compiledstatement) != SQLITE_DONE) {
NSLog(#"ERROR");
}
sqlite3_reset(compiledstatement);
}
sqlite3_exec(database, "COMMIT", 0, 0, 0);
sqlite3_finalize(compiledstatement);
saveSuccess = YES;
NSLog(#"RESULTS SAVED SUCCESSFULLY!");
} else {
NSLog(#"StatemenTtFAILED (%s)", sqlite3_errmsg(database));
}
sqlite3_close(database);
} else {
NSLog(#"Statement FAILED (%s)", sqlite3_errmsg(database));
}
return saveSuccess;
}
There is no need for try/catch. Just do proper error checking.
Use NSMutableString to build up a string piece by piece.
Only finalize a statement if you successfully prepare it.
Be sure to close a database if you open it.
Use modern syntax for accessing values from dictionaries and arrays. It's easier to read and type.
Use modern loops when possible.
Use whitespace. It makes the code easier to read.
Give all of your method parameters a name.
But your SQLite code was actually better than most posts here. You actually use sqlite_bind_xxx to bind values to your query. Too few people do that.
In your code where you're grabbing the values, you are grabbing the row data with:
NSDictionary *rowData=[data objectAtIndex:0];
I suspect you mean:
NSDictionary *rowData=[data objectAtIndex:i];
I have a table in a database which I access like this:
+(NSMutableArray *) queryDatabaseWithSQL:(NSString *)sql params:(NSArray *)params {
sqlite3 *database;
NSMutableArray *rtn = [[NSMutableArray alloc] init];
int index = 0;
NSString *filepath = [self copyDatabaseToDocumentsWithName:#"database"];
if (sqlite3_open([filepath UTF8String], &database) == SQLITE_OK) {
const char *sqlStatement = [sql cStringUsingEncoding:NSASCIIStringEncoding];
sqlite3_stmt *compiledStatement;
if (sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) {
if ([params count]) {
for (int i = 0; i < [params count]; i++) {
NSString *obj = [params objectAtIndex:i];
sqlite3_bind_blob(compiledStatement, i + 1, [obj UTF8String], -1, SQLITE_TRANSIENT);
}
}
while (sqlite3_step(compiledStatement) == SQLITE_ROW) {
NSMutableArray *arr = [[NSMutableArray alloc] init];
index = 0;
const char *s = (char *)sqlite3_column_text(compiledStatement, index);
while (s) {
[arr addObject:[NSString stringWithUTF8String:s]];
index++;
s = (char *)sqlite3_column_text(compiledStatement, index);
}
if (![rtn containsObject:arr]) {
[rtn addObject:arr];
}
}
}
sqlite3_finalize(compiledStatement);
}
NSLog(#"ERROR: %s", sqlite3_errmsg(database));
sqlite3_close(database);
return rtn;
}
This works fine when I call the function like this:
NSLog(#"%#", [database queryDatabaseWithSQL:#"SELECT * FROM FRIENDSUSERS WHERE USER = ?" params:#[[delegate->user objectForKey:#"username"]]]);
Then when I call the function using a string like this it doesn't return any rows:
NSLog(#"%#", [database queryDatabaseWithSQL:#"SELECT * FROM FRIENDSUSERS WHERE USER = ?" params:#[#"username"]]);
I haven't got a clue what is going one with but I have checked the strings match and they do so I'm now stuck
Can anyone see any reason why this would not work
I have run error checks as well and every time it returns no error, or rows :(
Thanks in advance
I am making a function which allows you to read from a database and then puts the data into a NSMutableArray which it later returns for the user. Though for some reason when it is about to return the data the data is deleted from the array. Here is the code for the function:
+(NSMutableArray *) readFromDataBaseWithName:(NSString *)name withSqlStatement:(NSString *)sql {
sqlite3 *database;
NSMutableArray *rtn = [[NSMutableArray alloc] init];
NSMutableArray *new = [[NSMutableArray alloc] init];
int index = 0;
NSString *filePath = [self copyDatabaseToDocumentsWithName:name];
if (sqlite3_open([filePath UTF8String], &database) == SQLITE_OK) {
const char *sqlStateMent = [sql cStringUsingEncoding:NSASCIIStringEncoding];
sqlite3_stmt *compiledStatement;
if (sqlite3_prepare(database, sqlStateMent, -1, &compiledStatement, NULL) == SQLITE_OK) {
while (sqlite3_step(compiledStatement) == SQLITE_ROW) {
// loop through elements in row and put them into array
const char *s = (char *)sqlite3_column_text(compiledStatement, index);
while (s) {
[new addObject:[NSString stringWithUTF8String:s]];
index++;
s = (char *)sqlite3_column_text(compiledStatement, index);
}
[rtn addObject:new];
[new removeAllObjects];
}
}
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
return rtn;
}
Any advice would be much appreciated.
You keep reusing new. You need to create individual arrays each time.
+(NSMutableArray *) readFromDataBaseWithName:(NSString *)name withSqlStatement:(NSString *)sql {
sqlite3 *database;
NSMutableArray *rtn = [[NSMutableArray alloc] init];
int index = 0;
NSString *filePath = [self copyDatabaseToDocumentsWithName:name];
if (sqlite3_open([filePath UTF8String], &database) == SQLITE_OK) {
const char *sqlStateMent = [sql cStringUsingEncoding:NSASCIIStringEncoding];
sqlite3_stmt *compiledStatement;
if (sqlite3_prepare(database, sqlStateMent, -1, &compiledStatement, NULL) == SQLITE_OK) {
while (sqlite3_step(compiledStatement) == SQLITE_ROW) {
// loop through elements in row and put them into array
NSMutableArray *new = [[NSMutableArray alloc] init];
const char *s = (char *)sqlite3_column_text(compiledStatement, index);
while (s) {
[new addObject:[NSString stringWithUTF8String:s]];
index++;
s = (char *)sqlite3_column_text(compiledStatement, index);
}
[rtn addObject:new];
}
}
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
return rtn;
}
I use Sqlite3 in my iOS app.
If I try to run the command from the console:
INSERT INTO documents (name,fk_1) VALUES ('pluto','')
everything is fine.
But when doing it via prepared statement, the same fails:
INSERT INTO documents (name,fk_1) VALUES (?,?)
saying "foreign key constraint failed".The value of fk_1 = ''.
The table documents is
CREATE TABLE "documents" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL ,"name" varchar,"attivita_id" integer NULL REFERENCES attivita(id) ON DELETE CASCADE)
I was supposing that I can use NULL value as FK, and I can, indeed, because from the command line (first query) it works. The same is not possible with prepared statements?
The code to execute a write is:
-(NSInteger)writeData:(NSDictionary* )data table:(NSString* )table
{
sqlite3_stmt *sqlStatement;
NSMutableArray *columns = [[NSMutableArray alloc] init];
NSMutableArray *values = [[NSMutableArray alloc] init];
NSMutableDictionary *temp = [[NSMutableDictionary alloc] initWithDictionary:data];
#try {
assert([data count] != 0);
if ([[data allKeys] count] == 0) return 1;
// Names/Values for INSERT
// id is AUTOINCREMENT
[temp removeObjectForKey:#"id"];
[columns addObjectsFromArray:[temp allKeys]];
NSString *cols = [columns componentsJoinedByString:#","];
NSMutableString *colNames = [[NSMutableString alloc] initWithString:
[NSString stringWithFormat:#"INSERT INTO %s (",[table UTF8String]]];
[colNames appendString:cols];
[colNames appendString:#")"];
// Values for INSERT
[values addObjectsFromArray:[temp allValues] ];
// Prepare the ? marks for bindings
NSMutableArray * marks = [NSMutableArray new];
for (int i=0 ; i < [values count]; i++)
[marks addObject:#"?"];
NSMutableString *s = [[NSMutableString alloc] init];
NSMutableString *valNames = [[NSMutableString alloc] initWithString:#" VALUES ("];
[valNames appendString:[marks componentsJoinedByString:#","]];
[valNames appendString:#")"];
[colNames appendString:valNames];
const char *sql = [colNames UTF8String];
#ifdef DEBUG
// NSLog(#"avvDB writeDATA insert string %#",colNames);
#endif
if(sqlite3_prepare_v2(db, sql, -1, &sqlStatement, NULL) != SQLITE_OK)
{
NSLog(#"DB:Problem with prepare statement write %s, data: %#",sqlite3_errmsg(db),data);
NSLog(#"DB:Query %#",colNames);
return 0;
}
// binding
for(int i = 0; i < [values count]; i++)
{
// According to the doc, if the 3rd value is a null pointer, the effect is similar to
// sqlite3_bind_null
if ([[values objectAtIndex:i] length] == 0 || [values objectAtIndex:i] == nil)
sqlite3_bind_text(sqlStatement, i+1, nil,-1, SQLITE_TRANSIENT);
else
{
[s setString:[NSString stringWithFormat:#"%#",[values objectAtIndex:i]]];
const char* currentValue = [s UTF8String];
sqlite3_bind_text(sqlStatement, i+1, currentValue,-1, SQLITE_TRANSIENT);
}
}
if (sqlite3_step(sqlStatement) == SQLITE_DONE) {
// NSLog(#"Location inserted on database");
}
else {
NSLog(#"DB:Error on write: %i %s %#",sqlite3_errcode(db),sqlite3_errmsg(db),data);
NSLog(#"DB:Query %#",colNames);
}
// if (sqlite3_exec(db, sql, NULL, NULL, NULL) == SQLITE_OK)
// {
// NSLog(#"Last id %llu %s",sqlite3_last_insert_rowid(db),sqlite3_errmsg(db));
// }
// NSLog(#"Ecexecuted write %#",colNames);
valNames = nil;
colNames = nil;
marks = nil;
s = nil;
}// end try
#catch(NSException* e)
{
NSLog(#"Eccezione in write %#",[e reason]);
}
#finally {
sqlite3_finalize(sqlStatement);
sqlStatement = nil;
return sqlite3_last_insert_rowid(db);
}
}
An empty string is not the same as NULL.
To set a parameter to NULL, use sqlite3_bind_null.
Alternatively, just don't mention empty parameters in the INSERT statement.