sqlite3 - iOS - database is locked - ios

I am developing an app for ipad and i am using sqlite sentences (select, update, insert, delete).
I open (sqlite3_open) the database at the beginning and close (sqlite3_close) at the end of each sentence. But sometimes i´ve got the "database is locked" message.
I don´t know what can i do to solve this.
Thanks and sorry for this little information.

If I'm not mistaken , the problem with sqllite is that you can only access it once at a time.
If you have multiple threads, you can run in this situation. Example:
Run method1 (which accesses the database) on thread t1.
Run method2 (which accesses the database) on thread t2 after x seconds.
If method1 is not finished in those x seconds , both methods will access it at the same time.
And , as I said , I know that sqllite does not support this.
You should try to flag the usage of your database and if you want to access it but it is in use , try again after x seconds. Like this:
- (void) generalMethodThatUsesDatabses
{
if(databaseIsUsed)
{
[self performSelector:#selector(generalMethodThatUsesDatabses) withObject:nil afterDelay:5];
return;
}
databaseIsUsed = TRUE; //global bool variable
//your code here
databaseIsUsed = FALSE;
}
Hope this helps. Cheers!

You probably opened the database before using the same simulator.
To conclude all actions to a database and release all resources you always
have to use both (!) statements:
sqlite3_finalize(statement);
sqlite3_close(database);

A good way to solve this is to wrap this into a C++ library. This way, you can can create the library wrapper on the stack. This means that the moment that the function goes out of scope, you can close the connection in the destructor.
(note that I use reference counting for the Objective-C)
For instance:
NSArray* ScoreBoard::getAllScores()
{
ScoreBoard::ensureExistingTable();
//Stack allocated
SqliteWrapper sqlite("Scores.sqlite");
NSArray* result = sqlite.RunQuery("SELECT * FROM Scores ORDER BY ID DESC");
return result;
//after this, the sqlite destructor is called
}
It is very nice that the Objective-C compiler allows you to merge C++. It can be extremely useful.
Also
void SqliteWrapper::Close()
{
sqlite3_close(db);
}
as Vincent has pointed out, you have to finalize the statement. If you want keep the connection open, use finalize after each statement. Close out the connection the moment you are discarding the connection.
This method works for me.

it is use for three methed
1.isert
2.update
3. delete.
-(NSMutableArray *)resultSet
-(void)insertWithTitle:(NSString *)title Body:(NSString *)body
-(BOOL)updateAtIndex:(int)index Title:(NSString *)title Body:(NSString *)body
NSMutableArray *result = [[[NSMutableArray alloc] initWithCapacity:0] autorelease];
FMResultSet *rs = [db executeQuery:[self SQL:#"SELECT * FROM %#" inTable:TABLE_NAME]];
while ([rs next]) {
Record *tr = [[Record alloc] initWithIndex:[rs intForColumn:#"id"]
Title:[rs stringForColumn:#"title"]
Body:[rs stringForColumn:#"body"]];
[result addObject:tr];
[tr release];
}
[rs close];
2....
return result;
[db executeUpdate:[self SQL:#"INSERT INTO %# (title, body) VALUES (?,?)" inTable:TABLE_NAME], title, body];
if ([db hadError]) {
NSLog(#"Err %d: %#", [db lastErrorCode], [db lastErrorMessage]);
Delete record :
BOOL success = YES;
[db executeUpdate:[self SQL:#"DELETE FROM %# WHERE id = ?" inTable:TABLE_NAME], [NSNumber numberWithInt:index]];
if ([db hadError]) {
NSLog(#"Err %d: %#", [db lastErrorCode], [db lastErrorMessage]);
success = NO;
}
return success;
}

Related

How to use Vacuum in FMDB iOS

As the title, how to use Vacuum to shrink a SQLite database using FMDB?
Thanks in advance!
Thanks for all your support
I've found out the answer : [database executeUpdate:#"vacuum"];
1.Database Updating
I have a single database controller — VSDatabaseController in my latest app — that talks to SQLite via FMDB.
FMDB differentiates between updates and queries. To update the database the app calls:
-[VSDatabaseController runDatabaseBlockInTransaction:(VSDatabaseUpdateBlock)databaseBlock]
VSDatabaseUpdateBlock is simple:
typedef void (^VSDatabaseUpdateBlock)(FMDatabase *database);
runDatabaseBlockInTransaction is also simple:
- (void)runDatabaseBlockInTransaction:(VSDatabaseUpdateBlock)databaseBlock {
dispatch_async(self.serialDispatchQueue, ^{
#autoreleasepool {
[self beginTransaction];
databaseBlock(self.database);
[self endTransaction];
}
});
}
Here’s a simple example of a call to update the database:
SELECT ALL
- (void)emptyTagsLookupTableForNote:(VSNote *)note {
NSString *uniqueID = note.uniqueID;
[self runDatabaseBlockInTransaction:^(FMDatabase *database) {
[database executeUpdate:
#"delete from tagsNotesLookup where noteUniqueID = ?;", uniqueID];
}];
}
[self.database executeUpdate:
#"CREATE INDEX if not exists noteUniqueIDIndex on tagsNotesLookup (noteUniqueID);"];
Database Fetching
To fetch objects, the app calls:
SELECT ALL
-[VSDatabaseController runFetchForClass:(Class)databaseObjectClass
fetchBlock:(VSDatabaseFetchBlock)fetchBlock
fetchResultsBlock:(VSDatabaseFetchResultsBlock)fetchResultsBlock];
These two lines do much of the work:
SELECT ALL
FMResultSet *resultSet = fetchBlock(self.database);
NSArray *fetchedObjects = [self databaseObjectsWithResultSet:resultSet
class:databaseObjectClass];
A database fetch using FMDB returns an FMResultSet. With that resultSet you can step through and create model objects.
3.Keeping Objects in Memory
FMResultSet *resultSet = [self.database executeQuery:#"select uniqueID from some_table"];
4.Web APIs
- (void)uploadNote:(VSNote *)note {
VSNoteAPICall *apiCall = [[VSNoteAPICall alloc] initWithNote:[note detachedCopy]];
[self enqueueAPICall:apiCall];
}
5.Handling Web API Return Values
VSNote *cachedNote = [self.mapTable objectForKey:downloadedNote.uniqueID];
6.Database Migration
[self.database executeUpdate:#"CREATE TABLE if not exists tags "
"(uniqueID TEXT UNIQUE, name TEXT, deleted INTEGER, deletedModificationDate DATE);"];
I hope, it will help you.

parse call function in background messes up order sequence

I need to retrieve a couple of NSDicionaries that are compared against an id.
First, I'm calling a NSArray with these id's in them. I'm looping over them to see get the details of that id, and with that i'm calling another pfcloud function. Up until this point, all goes well. However, when I'm logging the payment details of the payment id's, the order sequence is is in a different order than the array I putted it in.
for(__block NSString *paymentId in success){
[self getPaymentDetails:paymentId];
}
So for instance: success = #[#"1",#"2",#"3"]
the method getPaymentDetails will log me#[#"details about 1", #"details about 3", #"details about 2"]
However, I need them to be in the exact same order.
This is my getPaymentDetails code:
-(void)getPaymentDetails:(NSString *)paymentId{
PFUser *currentUser = [PFUser currentUser];
[PFCloud callFunctionInBackground:#"getpaymentdetails"
withParameters:#{#"objectid": paymentId, #"userid": currentUser.objectId}
block:^(NSDictionary *success, NSError *error) {
if(success){
NSDictionary *payment = success;
NSString *amount = [payment objectForKey:#"amount"];
if (![amount isKindOfClass:[NSNull class]]) {
[self.amountArray addObject:amount];
}
else {
[self.amountArray addObject:#""];
}
NSString *from = [payment objectForKey:#"from"];
if (![from isKindOfClass:[NSNull class]]) {
[self.fromArray addObject:from];
}
else {
[self.fromArray addObject:#""];
}
} else{
NSLog(#"Error logged getpaymentdetails: %#", error);
}
}];
}
The values stored in the amountArray for instance, do not match the index of the paymentId
How come and how do I solve this?
It may be simpler to just move the whole for loop into the background and then call the Parse function synchronously
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group,queue, ^{
for(__block NSString *paymentId in success){
[self getPaymentDetails:paymentId];
}
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// Release the group when it is no longer needed.
dispatch_release(group);
Then in your getPaymentDetails you would call callFunction:withParameters:error: instead of callFunctionInBackground:withParameters:
This isn't an ideal solution however, as you are eliminating concurrency and so it will take longer to execute.
A better solution is to deal with the fact that the array is unordered at the conclusion of the loop and sort it once all of the data has been retrieved
The request callFunctionInBackground will do is executed asynchronously and there is no guarantee that the first call you make in your loop will finish first. This is not really related to Parse itself, that is just the nature of how this is done. You may end up with the same order by coincidence or a completely random one each time you execute this code.
If you want the order to stay the same, either pass in all IDs to your Cloud Function and update your Cloud Function to handle it or always wait for one call to finish, add the result to your array and then get the details with the next ID (basically a queue).

MagicalRecord does not save data

I am trying my hand at some very basic implementation of MagicalRecord to get the hang of it and run into the following.
When I save an entry and then fetch entries of that type it will come up with the entry I just saved. However, when I save the entry, close the app, start it again, and then fetch, it comes up empty.
Code for saving:
- (void)createTestTask{
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
Task *task = [Task createInContext:localContext];
task.tName = #"First Task";
task.tDescription = #"First Task created with MagicalRecord. Huzzah!";
NSError *error;
[localContext save:&error];
if (error != Nil) {
NSLog(#"%#", error.description);
}
}
Code for fetching: (all I want to know here if anything is actually saved)
- (void) fetchTasks{
NSArray *tasks = [Task findAll];
NSLog(#"Found %d tasks", [tasks count]);
}
I am sure I am missing something here, but not anything I can seem to find on stackoverflow or in the Tutorials I looked at.
Any help is welcome.
I have to ask the obvious "Is it plugged in" question: Did you initialize the Core Data Stack with one of the +[MagicalRecord setupCoreDataStack] methods?
Did your stack initialize properly? That is, is your store and model compatible? When they aren't, MagicalRecord (more appropriately, Core Data) will set up the whole stack without the Persistent Store. This is annoying because it looks like everything is fine until it cannot save to the store...because there is no store. MagicalRecord has a +[MagicalRecord currentStack] method that you can use to examine the current state of the stack. Try that in the debugger after you've set up your stack.
Assuming you did that, the other thing to check is the error log. If you use
[localContext MR_saveToPersistentStoreAndWait];
Any errors should be logged to the console. Generally when you don't see data on a subsequent run of your app, it's because data was not saved when you thought you called save. And the save, in turn, does not happen because your data did not validate correctly. A common example is if you have a required property, and it's still nil at the time you call save. "Normal" core data does not log these problems at all, so you might think it worked, when, in fact, the save operation failed. MagicalRecord, on the other hand, will capture all those errors and log them to the console at least telling you what's going on with your data.
When i have started with magical record I was also facing this problem, problem is context which you are using to save data. here is my code which might help you
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
NSArray *userInfoArray = [UserBasicInfo findByAttribute:#"userId" withValue:[NSNumber numberWithInt:loggedInUserId] inContext:localContext];
UserBasicInfo* userInfo;
if ([userInfoArray count]) {
userInfo = [userInfoArray objectAtIndex:0];
} else {
userInfo = [UserBasicInfo createInContext:localContext];
}
userInfo.activeUser = [NSNumber numberWithBool:YES];
userInfo.firstName = self.graphUser[#"first_name"];
userInfo.lastName = self.graphUser[#"last_name"];
userInfo.userId = #([jsonObject[#"UserId"] intValue]);
userInfo.networkUserId = #([jsonObject[#"NetworkUserId"] longLongValue]);
userInfo.userPoint = #([jsonObject[#"PointsEarned"] floatValue]);
userInfo.imageUrl = jsonObject[#"Picturelist"][0][#"PictureUrL"];
userInfo.imageUrlArray = [NSKeyedArchiver archivedDataWithRootObject:jsonObject[#"Picturelist"]];
} completion:^(BOOL success, NSError *error) {
}];
Use this when your done
[[NSManagedObjectContext MR_defaultContext]saveToPersistentStoreAndWait];

db executeUpdate... in FMDB block and doesn't go over, without error

i'm using the amazing FMDB project in my app in development, i have a NSOperation like this:
- (void)main
{
#autoreleasepool {
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:[[NSUserDefaults standardUserDefaults] valueForKey:#"pathDB"]];
[queue inDatabase:^(FMDatabase *db) {
FMResultSet *toQuery;
if (self._id == nil) {
toQuery = [db executeQuery:#"SELECT id,language,update_time FROM task"];
while ([toQuery next]) {
[myarray addObject:[toQuery resultDictionary]];
}
}];
for (int i = 0; i<[myarray count]; i++){
...Do Something
[queue inDatabase:^(FMDatabase *db) {
FMResultSet *checkImgQuery = [db executeQuery:#"SELECT img_url,img_path FROM task WHERE id = ? AND language = ?",myTask.id ,myTask.lang];
while ([checkImgQuery next]) {
if (![[checkImgQuery stringForColumn:#"img_url"] isEqualToString:myTask.img]) {
NSData *my_img = [NSData dataWithContentsOfURL:[NSURL URLWithString:myTask.img]];
if (my_img != nil) {
NSError *er;
[my_img writeToFile:[checkImgQuery stringForColumn:#"img_path"] options:NSDataWritingAtomic error:&er];
//In the line under here the code block, the app still running, but this operation doesn't
//go over this task
[db executeUpdate:#"UPDATE task SET img_url = ? WHERE id = ? AND language = ?",myTask.img,[NSNumber numberWithInt:myTask.id],[NSNumber numberWithInt:myTask.language];
NSLog(#"%#",[db lastErrorMessage]);
}
...Do Something
}
}
}
}];
}
}
The problem is in [db executeUpdate:...] that sometime works with no problem and sometime freeze and doesn't go over that line, the NSLog i have put there doesn't print anything, the app doesn't crash and continue working, but the thread is stuck there, if i shutdown the run of the app, and i restart it again the thread doesn't stop on the same task, but random on another, with no criteria, some time one works, and some time doesn't...anyone can help?
There are a couple issues that leap out at me, one or more of which may be contributing to your problem:
I notice that you're creating a FMDatabaseQueue object locally. You should only have one FMDatabaseQueue object shared for the entire app (I put it in a singleton). The purpose of the database queue is to coordinate database interactions, and it can't reasonably do that if you're creating new FMDatabaseQueue objects all over the place.
I'd advise against having an inDatabase block in which you're synchronously downloading a bunch of images from the network.
When you submit an inDatabase task, any inDatabase calls on other threads using the same FMDatabaseQueue (and they should use the same queue, or else you're defeating the purpose in having a queue in the first place) will not proceed until the one running in your operation does (or vice versa).
When doing database interaction from multiple threads, coordinated by the FMDatabaseQueue serial queue, you really want to make sure that you "get in and get out" as quickly as possible. Don't embed potentially slow network calls in the middle of the inDatabase block, or else all other database interaction will be blocked until it finishes.
So, do an inDatabase to identify the images that need to be downloaded, but that's it. Then outside of the inDatabase call, retrieve your images, and if you need to update image paths or the like, separate inDatabase call do to do that. But don't include anything slow and synchronous inside the inDatabase block.
I also notice that you're doing a SELECT on task table, keeping that FMRecordSet open, and then trying to update the same record. You want to open your record set, retrieve what you need, and close that recordset before you try to update the same record you retrieved in your recordset.
Always close the FMResultSet before you try to do the executeUpdate that updates the same record.
A bit unrelated, but I might suggest you consider including img_url and img_path in your original SELECT statement, that way your array of dictionary entries will already have everything you need and it saves you from have to do that second SELECT at all.
If you're wondering what the FMDatabaseQueue singleton might look like, you might have a DatabaseManager singleton whose interface looks like:
// DatabaseManager.h
#import Foundation;
#import SQLite3;
#import "FMDB.h"
NS_ASSUME_NONNULL_BEGIN
#interface DatabaseManager : NSObject
#property (nonatomic, strong, readonly) FMDatabaseQueue *queue;
#property (class, readonly, strong) DatabaseManager *sharedManager;
- (id)init __attribute__((unavailable("Use +[DatabaseManager sharedManager] instead")));
+ (id)new __attribute__((unavailable("Use +[DatabaseManager sharedManager] instead")));
#end
NS_ASSUME_NONNULL_END
and the implementation might look like:
// DatabaseManager.m
#import "DatabaseManager.h"
#implementation DatabaseManager
+ (DatabaseManager *)sharedManager {
static id sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
- (id)init {
if ((self = [super init])) {
_queue = [[FMDatabaseQueue alloc] initWithPath:[[NSUserDefaults standardUserDefaults] valueForKey:#"pathDB"]];
}
return self;
}
#end
Then, any code that needs to interact with the database can retrieve the queue like so:
FMDatabaseQueue *queue = DatabaseManager.sharedManager.queue;

FMDB Infinite loop due to sqlite3_step and while (retry)

I have a FMDB executeUpdate which cause an infinite loop :
FMDatabase *db = [FMDatabase databaseWithPath:[CDDBManager getDatabasePath]];
[db closeOpenResultSets];
[db close];
NSLog(#"successfully pass closes");
[db open];
NSLog(#"successfully pass open");
[db setTraceExecution:YES];
BOOL success = [db executeUpdate:#"INSERT OR REPLACE INTO Player (id, ..., is_user) VALUES (?, ..., ?)",
[NSNumber numberWithInt:self.player_id],
...
[NSNumber numberWithInt:1]];
NSLog(#"end update");
[db close];
I call this method in two different VCs and in one it perfectly work ... in other I have an infinite loop (I print retry in the "do{} while()" of FMDB), so I don't see "end update" ...
As you see, I already try to close all result sets and and the db ...
Anyone can see where I've failed ?
All suggestions are welcome.
I think we had the same problem. I was opening a connection for every method where I wanted to call my database. I fixed it by just opening the connection on init.
I'm sorry but it is a simple error in function where I close the FMResultSet after a return in condition clause ...

Resources