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 ...
Related
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.
When I encrypt my database using sqlcipher, and call inDatabase in FMDatabaseQueue——success!
But When I change inDatabase to inTransaction, the console says "File is encrypted or is not a database".
The code:
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:st_dbPath];
// success
[queue inDatabase:^(FMDatabase *db) {
[db setKey:st_dbKey];
[db executeUpdate:#"INSERT INTO t_user VALUES (16)"];
}];
// fail : File is encrypted or is not a database
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
[db setKey:st_dbKey];
[db executeUpdate:#"INSERT INTO t_user VALUES (17)"];
}];
And the encrypt database code:
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDir = [documentPaths objectAtIndex:0];
NSString *ecDB = [documentDir stringByAppendingPathComponent:st_dbEncryptedName];
// SQL Query. NOTE THAT DATABASE IS THE FULL PATH NOT ONLY THE NAME
const char* sqlQ = [[NSString stringWithFormat:#"ATTACH DATABASE '%#' AS encrypted KEY '%#';", ecDB, st_dbKey] UTF8String];
sqlite3 *unencrypted_DB;
if (sqlite3_open([st_dbPath UTF8String], &unencrypted_DB) == SQLITE_OK) {
// Attach empty encrypted database to unencrypted database
sqlite3_exec(unencrypted_DB, sqlQ, NULL, NULL, NULL);
// export database
sqlite3_exec(unencrypted_DB, "SELECT sqlcipher_export('encrypted');", NULL, NULL, NULL);
// Detach encrypted database
sqlite3_exec(unencrypted_DB, "DETACH DATABASE encrypted;", NULL, NULL, NULL);
sqlite3_close(unencrypted_DB);
}
else {
sqlite3_close(unencrypted_DB);
NSAssert1(NO, #"Failed to open database with message '%s'.", sqlite3_errmsg(unencrypted_DB));
}
the encrypt code come from there:http://www.guilmo.com/fmdb-with-sqlcipher-tutorial/
Calling inTransaction causes the SQL statement begin exclusive transaction to be executed on your database before calling your completion block. Therefore that SQL is executed before you have a chance to call setKey.
You could instead use inDatabase and call beginTransaction on the FBDatabase instance that is passed back like this:
[self.queue inDatabase:^(FMDatabase *db) {
[db setKey:st_dbKey];
[db beginTransaction];
[db executeUpdate:#"INSERT INTO t_user VALUES (17)"];
[db commit];
}];
Gus Hovland's answer works but I think what works better is to change inTransaction: to inDeferredTransaction:. From FMDB's documentation for inTransaction:
" Unlike SQLite's BEGIN TRANSACTION, this method currently performs
an exclusive transaction, not a deferred transaction. This behavior
is likely to change in future versions of FMDB, whereby this method
will likely eventually adopt standard SQLite behavior and perform
deferred transactions. If you really need exclusive tranaction, it is
recommended that you use inExclusiveTransaction, instead, not only
to make your intent explicit, but also to future-proof your code."
I am using a multi threaded environment in my app, I need to constantly access sqlite db in order to update my views and also update my DB with server data via multiple background threads. Right now I am using FMDB for DB interaction but still getting DB locked problem.
FMDatabaseQueue *_queue = [FMDatabaseQueue databaseQueueWithPath:databasePath];
NSOperationQueue *_writeQueue = [NSOperationQueue new];
[_writeQueue setMaxConcurrentOperationCount:1];
NSRecursiveLock *_writeQueueLock = [NSRecursiveLock new];
[_writeQueue addOperationWithBlock:^{
BOOL tryLock = NO;
#try {
[_writeQueueLock lock];
tryLock = YES;
[_queue inDatabase:^(FMDatabase *db) {
#try {
[db logsErrors];
[db executeUpdate:updateSQL];
}
#catch (NSException *exception) {
}
#finally {
}
}];
}
#catch (NSException *exception) {
NSLog(#"Error while inserting data saveLocation inside operation queue. %#", exception.description);
}
#finally {
if (tryLock) {
[_writeQueueLock unlock];
}
}
}];
This what I am doing every time I insert data and similar way when I read data from DB as I am locking, Process should not be able to access DB until one thread finishes. I don't know what is wrong please help me out.
Whenever multiple threads try to access same table to read and write or two threads wants to write on same table of same db sqlite produces db locked signal so to resolve this you need Locks
NSRecursiveLock *_writeQueueLock = [NSRecursiveLock new];
as you've added in your code, but this won't help you much as you're trying to craete a new lock every time you insert.
This lock should be a single object for all your blocking calls to DB like insert, update, delete etc.
Try creating a singleton instance of lock, This should help:
static FMDatabaseQueue *_queue;
static NSOperationQueue *_writeQueue;
static NSRecursiveLock *_writeQueueLock;
+(SomeDBClass*)getSharedInstance{
if (!sharedInstance) {
sharedInstance = [[super allocWithZone:NULL]init];
_queue = [FMDatabaseQueue databaseQueueWithPath:databasePath];
_writeQueue = [NSOperationQueue new];
[_writeQueue setMaxConcurrentOperationCount:1];
_writeQueueLock = [NSRecursiveLock new];
}
return sharedInstance;
}
Now once your objects created you can call your insert, update, delete method on these queue and locks like:
[_writeQueue addOperationWithBlock:^{
BOOL tryLock = NO;
#try {
[_writeQueueLock lock];
tryLock = YES;
[_queue inDatabase:^(FMDatabase *db) {
#try {
[db logsErrors];
[db executeUpdate:updateSQL];
}
#catch (NSException *exception) {
}
#finally {
}
}];
}
#catch (NSException *exception) {
NSLog(#"Error while inserting data saveLocation inside operation queue. %#", exception.description);
}
#finally {
if (tryLock) {
[_writeQueueLock unlock];
}
}
}];
You can also refer this for better understanding, Hope this helps and I am new to the stack so please be little bit forgiving Thanks.
Using FMDatabaseQueue completely eliminates the need for any other locking mechanism. Adding another locking mechanism is only going to complicate the issue further.
If you are getting messages about the database being "locked", it because either:
you have multiple FMDatabase/FMDatabaseQueue objects out there; or
you're calling inDatabase from within another inDatabase call.
You should have one FMDatabaseQueue object which is shared amongst all of the threads and you need to make sure that none of your functions called with a inDatabase block calls something else that itself tries another inDatabase call.
I've created this method in my iOS to call to FMDatabaseQueue:
-(void) enqueueSelectStatement:(NSString*)selectStatement withArguments:(NSArray*)args {
NSLog(#"Checkpoint 1");
[self.dbQueue inDatabase:^(FMDatabase *db) {
FMResultSet *result = [db executeQuery:selectStatement withArgumentsInArray:args];
NSLog(#"Checkpoint 2");
}];
NSLog(#"Checkpoint 3");
}
When I call this method with:
NSString *query = #"SELECT * FROM mytable WHERE id = ?";
[self enqueueSelectStatement:query withArguments:#[1]];
I get Checkpoint 1 in my terminal output, but nothing else. The app does not crash and continues to run, but the method/block apparently dies after Checkpoint 1
Is the method just stopping, and if so, why?
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;
}