I'm just going to try out using transactions with the FMDB SQLite iOS wrapper.
The documentation is a little vague on transactions but from having a quick look at some functions I have come up with the following logic:
[fmdb beginTransaction];
// Run the following query
BOOL res1 = [fmdb executeUpdate:#"query1"];
BOOL res2 = [fmdb executeUpdate:#"query2"];
if(!res1 || !res2) [fmdb rollback];
else [fmdb commit];
You could also use FMDatabaseQueue to handle your transactions, which is part of fmdb:
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
[db executeUpdate:#"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
[db executeUpdate:#"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
[db executeUpdate:#"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];
if (whoopsSomethingWrongHappened) {
*rollback = YES;
return;
}
// etc…
[db executeUpdate:#"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]];
}];
Documentation
I wouldn't try to do the second update if the first failed.
bool ret = false;
[fmdb beginTransaction];
ret = [fmdb executeUpdate:#"query1"];
if (ret)
{
ret = [fmdb executeUpdate:#"query2"];
if (!ret)
{
// report error 2
}
}
if(ret)
{
if (![fmdb commit])
{
// panic!
}
}
else
{
if (![fmdb rollback])
{
// panic!
}
}
For paranoid robustness you should have a try ... catch block in case anything throws an exception. If you do, you can use it to your advantage.
[fmdb beginTransaction];
#try
{
if (![fmdb executeUpdate:#"query1"])
{
// report error
#throw someExcpetion;
}
if (![fmdb executeUpdate:#"query2"])
{
// report error
#throw someExcpetion;
}
[fmdb commit]
}
#catch(NSException* e)
{
[fmdb rollback];
// rethrow if not one of the two exceptions above
}
Swift way:
let queue = FMDatabaseQueue(path: databaseURL.path!)
queue.inTransaction() {
db, rollback in
result = db.executeUpdate("INSERT INTO client VALUES (NULL, ?)", client.name ?? "")
if result {
client.ID = Int(db.lastInsertRowId())
} else {
rollback.initialize(true)
print("\(__FUNCTION__) insert into table failed: \(db.lastErrorMessage())")
}
}
queue.close()
It seems like a valid usage scenario, to which I might add outputting the values of -lastErrorMessage and -lastErrorCode before you perform a rollback, so that you get a sense of what exactly went wrong.
Better yet, make those calls after each -executeUpdate, so you'll know if an error occured after each statement:
[fmdb beginTransaction];
// Run the following query
BOOL res1 = [fmdb executeUpdate:#"query1"];
if (!res1) {
NSLog(#"Error %d - %#", [fmdb lastErrorMessage], [fmdb lastErrorCode]);
}
BOOL res2 = [fmdb executeUpdate:#"query2"];
if (!res2) {
NSLog(#"Error %d - %#", [fmdb lastErrorMessage], [fmdb lastErrorCode]);
}
if(!res1 || !res2) [fmdb rollback];
else [fmdb commit];
Related
Got this lengthy section of code shown below and I've hit a brick wall. Basically the code runs perfectly and does exactly what I want it to do. However, it needs to finish running all the code within this section before printing the "Finished" at the end. However adding semaphores or another dispatch group forces a breakpoint. Might be obvious, but could someone give me a bit of advice on this please?
Note: I cannot use that dispatch at the bottom to call another method. Remember its within a loop.
for (id i in arr) {
searchByName = nil;
if ([i containsString:#"word1"] || [i containsString:#"word2"]) {
NSRange searchFromRange = [i rangeOfString:#"ong>"];
NSRange searchToRange = [i rangeOfString:#"</str"];
NSString *substring = [i substringWithRange:NSMakeRange(searchFromRange.location+searchFromRange.length, searchToRange.location-searchFromRange.location-searchFromRange.length)];
[allergens addObject:substring];
if ([substring isEqualToString:#"Examee"] && veg_lac_ovoSafe == TRUE) {
veg_ovoSafe = FALSE;
vegSafe = FALSE;
}
else if ([substring isEqualToString:#"Example"] && veg_lac_ovoSafe == TRUE) { //USE OF HEURISTICS
veg_lacSafe = FALSE;
vegSafe = FALSE;
}
else if ([substring isEqualToString:#"Exam"]) {
pescetarianSafe = TRUE;
vegSafe = FALSE;
veg_ovoSafe = FALSE;
veg_lacSafe = FALSE;
veg_lac_ovoSafe = FALSE;
pollotarianSafe = FALSE;
}
NSCharacterSet *charactersToRemove = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
NSCharacterSet *numbersToRemove = [NSCharacterSet characterSetWithCharactersInString:#"0123456789"];
substring = [[substring componentsSeparatedByCharactersInSet:charactersToRemove] componentsJoinedByString:#""];
searchByName = [[[substring componentsSeparatedByCharactersInSet:numbersToRemove] componentsJoinedByString:#""] lowercaseString];
}
else {
NSCharacterSet *charactersToRemove = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
NSCharacterSet *numbersToRemove = [NSCharacterSet characterSetWithCharactersInString:#"0123456789"];
NSString *searchItem = [[i componentsSeparatedByCharactersInSet:charactersToRemove] componentsJoinedByString:#""];
searchByName = [[[searchItem componentsSeparatedByCharactersInSet:numbersToRemove] componentsJoinedByString:#""] lowercaseString];
}
if (![searchByName isEqualToString:#" "]) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_enter(_groupSearch);
dispatch_async(queue, ^{
[[self databaseQuery:searchByName] observeEventType:FIRDataEventTypeChildAdded
withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
if (snapshot.value != NULL) {
NSLog(#"%#", snapshot.value);
for (int i=0; i < [[NSString stringWithFormat:#"%#", snapshot.value] length]; i++) {
NSString *x = [NSString stringWithFormat:#"%c", [[NSString stringWithFormat:#"%#", snapshot.value] characterAtIndex:i]];
NSLog(#"%#", x);
if ([x isEqualToString:#"1"]) {
vegSafe = FALSE;
}
else if ([x isEqualToString:#"2"]) {
vegSafe = FALSE;
veg_lacSafe = FALSE;
}
else if ([x isEqualToString:#"3"]) {
vegSafe = FALSE;
veg_ovoSafe = FALSE;
}
else if ([x isEqualToString:#"4"]) { //Could use switch case.
vegSafe = FALSE;
veg_lac_ovoSafe = FALSE;
veg_lacSafe = FALSE;
veg_ovoSafe = FALSE;
}
else if ([x isEqualToString:#"5"]) {
pescetarianSafe = FALSE;
}
else if ([x isEqualToString:#"6"]) {
pollotarianSafe = FALSE;
}
}
}
dispatch_group_leave(_groupSearch);
}
withCancelBlock:^(NSError * _Nonnull error) {
NSLog(#"%#", error.localizedDescription);
dispatch_group_leave(_groupSearch);
}];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_group_wait(_groupSearch, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)));
dispatch_sync(queue, ^{
//Hi
});
});
}
}
NSLog(#"Finished");
Since you're calling dispatch_group_wait() inside a dispatch_async() block, it appears you want to run the completion block asynchronously on the global dispatch queue. The proper way to do this is to use dispatch_group_notify() instead:
dispatch_group_notify(groupSearch, queue, ^{
NSLog(#"Finished")
});
This will run the block on the specified queue once all the blocks submitted to the group have finished, so no dispatch_async() or dispatch_sync() is needed. Additionally, waiting inside dispatch_async() will block a thread in the GCD pool, which is a really bad idea since there are a finite number of them. This is also something that using dispatch_group_notify() instead will avoid.
EDIT: The paragraph below is no longer relevant to your updated code:
Also, as the other answerer pointed out, you probably want to put the call to dispatch_group_notify() after the loop finishes, assuming that you want one completion block to run at the very end of your work. If what you want actually is a whole bunch of separate notifications, one for each run through the loop, then you should probably create a new group within the loop and use that instead. Using one group for the whole shebang and then setting up notifications within the loop is going to cause each notification to wait not only for the dispatch_group_leave() calls for that particular run through the loop, but for all the dispatch_group_enter() calls that have been made on any run through the loop to be balanced. So you'll get nothing until all your work is done, and then you'll suddenly spam a whole bunch of completion blocks all at once, with an accompanying thread explosion since they'll all be submitted on the global queue. This is probably not what you want.
Your use of dispatch_group_wait is in the wrong place. It needs to after the for loop, not inside it. And you can create queue before the loop and reuse it as needed.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
for (id i in arr) {
searchByName = nil;
// Lots of other code here
if (![searchByName isEqualToString:#" "]) {
dispatch_group_enter(_groupSearch);
dispatch_async(queue, ^{
[[self databaseQuery:searchByName] observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
if (snapshot.value != NULL) {
NSLog(#"%#", snapshot.value);
for (int i=0; i < [[NSString stringWithFormat:#"%#", snapshot.value] length]; i++) {
NSString *x = [NSString stringWithFormat:#"%c", [[NSString stringWithFormat:#"%#", snapshot.value] characterAtIndex:i]];
NSLog(#"%#", x);
if ([x isEqualToString:#"1"]) {
vegSafe = FALSE;
}
else if ([x isEqualToString:#"2"]) {
vegSafe = FALSE;
veg_lacSafe = FALSE;
}
else if ([x isEqualToString:#"3"]) {
vegSafe = FALSE;
veg_ovoSafe = FALSE;
}
else if ([x isEqualToString:#"4"]) { //Could use switch case.
vegSafe = FALSE;
veg_lac_ovoSafe = FALSE;
veg_lacSafe = FALSE;
veg_ovoSafe = FALSE;
}
else if ([x isEqualToString:#"5"]) {
pescetarianSafe = FALSE;
}
else if ([x isEqualToString:#"6"]) {
pollotarianSafe = FALSE;
}
}
}
dispatch_group_leave(_groupSearch);
}
withCancelBlock:^(NSError * _Nonnull error) {
NSLog(#"%#", error.localizedDescription);
dispatch_group_leave(_groupSearch);
}];
});
}
}
dispatch_group_wait(_groupSearch, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)));
NSLog(#"Finished");
I have problem with creating CKQuery operation with big batch of data. My query works with 100 results but after more results query fail, because one thread is bad dispatched or something (libdispatch.dylib`dispatch_group_leave:
) i am lost... any idea?
+ (void) fetchAnswersWithRecordId:(CKRecordID *)recordId completionHandler:(CloudKitCompletionHandler)handler {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANSrecordID == %#", recordId];
CKQuery *query = [[CKQuery alloc] initWithRecordType:ckAnswers predicate:predicate];
CKQueryOperation *operation = [[CKQueryOperation alloc] initWithQuery:query];
CKQueryOperation * __weak weakSelf = operation;
operation.resultsLimit = 300;
NSMutableArray *tmp = [[NSMutableArray alloc] init];
operation.recordFetchedBlock = ^(CKRecord *record) {
[tmp addObject:record];
};
operation.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error) {
if (!handler)
return;
NSArray *array = [NSArray arrayWithArray:tmp];
if(cursor != nil) {
CKQueryOperation *newOperation = [[CKQueryOperation alloc] initWithCursor:cursor];
newOperation.recordFetchedBlock = weakSelf.recordFetchedBlock;
newOperation.completionBlock = weakSelf.completionBlock;
[[self publicCloudDatabase] addOperation:newOperation];
} else {
NSLog(#"Results: %lu", [array count]);
dispatch_async(dispatch_get_main_queue(), ^{
handler(array, error);
});
}
};
[[self publicCloudDatabase] addOperation:operation];}
I think your issue lies with the __weak operation and the way you create an operation inside another operation. Here is an example (in swift) of how I do something similar i.e. get additional results, but in a fetch and not a query. Note the use of a instance variable to initialize first time and the use of semi-recursion through GCD dispatch_aync:
private func _fetchRecordChangesFromCloud() {
if !_fetching {
// this is the first and only time this code is called in GCD recusion
// we clean the caches we use to collect the results of the fetch
// so we can then save the record in the correct order so references can be created
_fetchedModifiedRecords = []
_fetchedDeletedRecordIDs = []
// mark fetching has started
_fetching = true
}
let operation = CKFetchRecordChangesOperation(recordZoneID: _customRecordZoneID, previousServerChangeToken: _serverChangeToken)
operation.recordChangedBlock = { (record: CKRecord?) in
if let record = record {
println("Received record to save: \(record)")
self._fetchedModifiedRecords.append(record)
}
}
operation.recordWithIDWasDeletedBlock = { (recordID: CKRecordID?) in
if let recordID = recordID {
println("Received recordID to delete: \(recordID)")
self._fetchedDeletedRecordIDs.append(recordID)
}
}
operation.fetchRecordChangesCompletionBlock = {
(serverChangeToken: CKServerChangeToken?, clientChangeToken: NSData?, error: NSError?) -> Void in
if let error = error {
println("Error in fetching record changes: \(error)")
// try again next sync
self._fetchAfterNextSuccessfullSync = true
self._fetching = false
return
}
// fetched records successfuly
println("fetched records successfuly")
if let serverChangeToken = serverChangeToken {
self._serverChangeToken = serverChangeToken
}
if operation.moreComing {
// we need to create another operation object and do it again
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
self._fetchRecordChangesFromCloud()
}
} else {
// we are finally done
// process the fetched records
self._processFetchedRecords()
// save all changes back to persistent store
self._saveBackgroundContext()
// we are done
self._fetching = false
}
}
self._privateDatabase.addOperation(operation)
}
FYI, dispatch_async() is not necessary, this is a memory management issue. The following works properly for a multi-batch fetch:
//
__block CKQueryOperation* enumerateOperationActive = nil;
//
NSPredicate* predicate = [NSPredicate predicateWithValue:TRUE];
CKQuery* query = [[[CKQuery alloc] initWithRecordType:#"myType" predicate:predicate] autorelease];
CKQueryOperation* enumerateOperation = [[[CKQueryOperation alloc] initWithQuery:query] autorelease];
// DEBUG: fetch only 1 record in order to "force" a nested CKQueryOperation cycle
enumerateOperation.resultsLimit = 1;
enumerateOperation.recordFetchedBlock = ^(CKRecord* recordFetched)
{
// ...
};
enumerateOperation.queryCompletionBlock = ^(CKQueryCursor* cursor, NSError* error)
{
if (error)
{
// ...
}
else
{
if (cursor)
{
CKQueryOperation* enumerateOperationNested = [[[CKQueryOperation alloc] initWithCursor:cursor] autorelease];
// DEBUG: fetch only 1 record in order to "force" a doubly-nested CKQueryOperation cycle
enumerateOperationNested.resultsLimit = 1;
enumerateOperationNested.recordFetchedBlock = /* re-used */ enumerateOperationActive.recordFetchedBlock;
enumerateOperationNested.queryCompletionBlock = /* re-used */ enumerateOperationActive.queryCompletionBlock;
// CRITICAL: keep track of the very last (active) operation
enumerateOperationActive = enumerateOperationNested;
[database addOperation:enumerateOperationNested];
}
}
};
//
// CRITICAL: keep track of the very last (active) operation
enumerateOperationActive = enumerateOperation;
[database addOperation:enumerateOperation];
NOTE: if you attempt to access (the original) enumerateOperation.queryCompletionBlock instead of (the very last) enumerateOperationActive.queryCompletionBlock, the operation will never complete.
I liked the recursive solution on the main thread. Below is my solution in Objective C. I'm using a class level var: _recordArray, allocated ahead of time.
- (void) readRecords_Resurs: (CKDatabase *) database query: (CKQuery *) query cursor: (CKQueryCursor *) cursor {
// Send either query or cursor
CKQueryOperation *operation;
if (query != nil) operation = [[CKQueryOperation alloc] initWithQuery: query];
else operation = [[CKQueryOperation alloc] initWithCursor: cursor];
operation.recordFetchedBlock = ^(CKRecord *record) {
[_recordArray addObject:record];
};
operation.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error) {
if (cursor == nil || error != nil) {
// Done
dispatch_async(dispatch_get_main_queue(), ^{ [self readRecordsDone: error == nil ? nil : [error localizedDescription]]; });
}
else {
// Get next batch using cursor
dispatch_async(dispatch_get_main_queue(), ^{ [self readRecords_Resurs: database query: nil cursor: cursor]; });
}
};
[database addOperation: operation]; // start
}
- (void) readRecordsDone: (NSString *) errorMsg {
}
My solution is a category that uses two operations but both use the same blocks, and you can supply how many results per request you'd like.
#interface CKDatabase (MH)
/* Convenience method for performing a query receiving the results in batches using multiple network calls. Best use max 400 for cursorResultsLimit otherwise server sometimes exceptions telling you to use max 400. Even using CKQueryOperationMaximumResults can cause this exception. */
- (void)mh_performCursoredQuery:(CKQuery *)query cursorResultsLimit:(int)cursorResultsLimit inZoneWithID:(CKRecordZoneID *)zoneID completionHandler:(void (^)(NSArray /* CKRecord */ *results, NSError *error))completionHandler;
#end
#implementation CKDatabase(MH)
- (void)mh_performCursoredQuery:(CKQuery *)query cursorResultsLimit:(int)cursorResultsLimit inZoneWithID:(CKRecordZoneID *)zoneID completionHandler:(void (^)(NSArray /* CKRecord */ *results, NSError *error))completionHandler{
//holds all the records received.
NSMutableArray* records = [NSMutableArray array];
//this block adds records to the result array
void (^recordFetchedBlock)(CKRecord *record) = ^void(CKRecord *record) {
[records addObject:record];
};
//this is used to allow the block to call itself recurively without causing a retain cycle.
void (^queryCompletionBlock)(CKQueryCursor *cursor, NSError *error)
__block __weak typeof(queryCompletionBlock) weakQueryCompletionBlock;
weakQueryCompletionBlock = queryCompletionBlock = ^void(CKQueryCursor *cursor, NSError *error) {
//if any error at all then end with no results. Note its possible that we got some results,
// and even might have got a cursor. However if there is an error then the cursor doesn't work properly so will just return with no results.
if(error){
completionHandler(nil,error);
}
else if(cursor){
CKQueryOperation* cursorOperation = [[CKQueryOperation alloc] initWithCursor:cursor];
cursorOperation.zoneID = zoneID;
cursorOperation.resultsLimit = cursorResultsLimit;
cursorOperation.recordFetchedBlock = recordFetchedBlock;
cursorOperation.queryCompletionBlock = weakQueryCompletionBlock; // use the weak pointer to prevent retain cycle
//start the recursive operation and return.
[self addOperation:cursorOperation];
}
else{
completionHandler(records,nil);
}
};
//start the initial operation.
CKQueryOperation* queryOperation = [[CKQueryOperation alloc] initWithQuery:query];
queryOperation.zoneID = zoneID;
queryOperation.resultsLimit = cursorResultsLimit;
queryOperation.recordFetchedBlock = recordFetchedBlock;
queryOperation.queryCompletionBlock = queryCompletionBlock;
[self addOperation:queryOperation];
}
#end
<\RESOLVED>, Please see the first reply
My mac(10.9) has joined into a AD domain. In my program, I tried to recognize whether the current login user is local account or AD user. I can successfully distinguish them by using the following code.
+ (bool)isLocalUser:(NSString*)user
{
NSError *dirSearchError = nil;
ODRecord *foundUser = findUser(user, &dirSearchError);
if(foundUser !=nil)
{
return YES;
}else
{
return NO;
}
}
ODRecord *findUser(NSString *user, NSError **error)
{
NSLog(#"[MacLogonUI] findUser");
ODNode *searchNode = [ODNode nodeWithSession: [ODSession defaultSession]
type: kODNodeTypeLocalNodes
error: error];
if (searchNode == nil) {
return nil;
}
NSDictionary *nodeInfo = [searchNode nodeDetailsForKeys:nil error:error];
/* query this node for the user record we're interested in.
* We only need one result, which is why maximumResults is set to 1.
*/
ODQuery *userSearch = [ODQuery queryWithNode: searchNode
forRecordTypes: kODRecordTypeUsers
attribute: kODAttributeTypeRecordName
matchType: kODMatchEqualTo
queryValues: user
returnAttributes: kODAttributeTypeStandardOnly
maximumResults: 1
error: error];
if (userSearch == nil) {
return nil;
}
/* For this example we'll use a synchronous search. This could take a while
* so asynchronous searching is preferable.
*/
NSArray *foundRecords = [userSearch resultsAllowingPartial: NO error: error];
if (foundRecords == nil || [foundRecords count] == 0) {
return nil;
}
ODRecord *userRecord = [foundRecords objectAtIndex: 0];
return [[userRecord retain] autorelease];
}
While when the AD user create a mobile card, it is viewed as a managed user(from the System preference -> Users & Groups). The code also recognize this kind of AD user as local. How to deal with this kind of situation?
Do you guys have any idea of this problem?
I have solved this problem by myself. Hope the following code helps:
#import "DasUser.h"
#import <OpenDirectory/OpenDirectory.h>
#import <Collaboration/Collaboration.h>
#implementation DasUser
+ (bool)isLocalUser:(NSString*)user
{
NSError *dirSearchError = nil;
ODRecord *foundUser = findUser(user, &dirSearchError);
if(foundUser !=nil)
{
return YES;
}else
{
return NO;
}
}
ODRecord *findUser(NSString *user, NSError **error)
{
NSLog(#"[MacLogonUI] findUser");
CSIdentityAuthorityRef defaultAuthority = CSGetManagedIdentityAuthority();
CSIdentityClass identityClass = kCSIdentityClassUser;
CSIdentityQueryRef query = CSIdentityQueryCreate(NULL, identityClass, defaultAuthority);
CFErrorRef err = NULL;
CSIdentityQueryExecute(query, 0, &err);
CFArrayRef results = CSIdentityQueryCopyResults(query);
int numResults = CFArrayGetCount(results);
NSMutableArray * managedUsers = [NSMutableArray array];
for (int i = 0; i < numResults; ++i) {
CSIdentityRef identity = (CSIdentityRef)CFArrayGetValueAtIndex(results, i);
CBIdentity * identityObject = [CBIdentity identityWithCSIdentity:identity];
NSString* posixName = [identityObject posixName];
[managedUsers addObject:posixName];
}
CFRelease(results);
CFRelease(query);
ODNode *searchNode = [ODNode nodeWithSession: [ODSession defaultSession]
type: kODNodeTypeLocalNodes
error: error];
if (searchNode == nil) {
return nil;
}
/* query this node for the user record we're interested in.
* We only need one result, which is why maximumResults is set to 1.
*/
ODQuery *userSearch = [ODQuery queryWithNode: searchNode
forRecordTypes: kODRecordTypeUsers
attribute: kODAttributeTypeRecordName
matchType: kODMatchEqualTo
queryValues: user
returnAttributes: kODAttributeTypeStandardOnly
maximumResults: 1
error: error];
if (userSearch == nil) {
return nil;
}
/* For this example we'll use a synchronous search. This could take a while
* so asynchronous searching is preferable.
*/
NSArray *foundRecords = [userSearch resultsAllowingPartial: NO error: error];
if([foundRecords count]>0)
{
NSString *nameStr = [foundRecords[0] recordName];
NSLog(#"[MacLogonUI] findUser nameStr %#", nameStr);
int j;
for( j = 0; j<[managedUsers count]; j++)
{
if([nameStr isEqualToString:managedUsers[j]])
{
break;
}
}
if(j<[managedUsers count])
{
foundRecords = nil;
}
}
if (foundRecords == nil || [foundRecords count] == 0) {
return nil;
}
ODRecord *userRecord = [foundRecords objectAtIndex: 0];
return [[userRecord retain] autorelease];
}
#end
While when network of the mac is disconnected. The managed user can not be listed. Is there anybody has any idea of this?
I have been trying to figure this out for days now but I cannot figure out what is going on. In my app I want to be able to delete an item, and related items from a tableView. So when the delete button in the TableView is pushed the following method is called.
- (BOOL) deleteDuik:(MOODuik *) duik
{
int diveid = [duik duikId];
NSLog(#"Begin Delete");
sqlite3 *db = [self openDatabase];
sqlite3_stmt * deleteStmt = nil;
BOOL succes = TRUE;
#try{
if (db != NULL) {
sqlite3_exec(db, "BEGIN TRANSACTION", 0, 0, 0);
//NSString *sqlString = [NSString stringWithFormat:#"delete from duik where duikid = %d", diveid];
const char *sql = "delete from duik where duikid = ?";
if(sqlite3_prepare_v2(db, sql, -1, &deleteStmt, NULL) == SQLITE_OK)
{
sqlite3_bind_int(deleteStmt, 1, diveid);
sqlite3_step(deleteStmt);
sqlite3_finalize(deleteStmt);
deleteStmt = nil;
sql = "delete from waarneming where duikid = ?";
if(sqlite3_prepare_v2(db, sql, -1, &deleteStmt, NULL) == SQLITE_OK)
{
sqlite3_bind_int(deleteStmt, 1, diveid);
sqlite3_step(deleteStmt);
sqlite3_finalize(deleteStmt);
}
}
} else
{
NSLog(#"Failed to connect to db");
succes = FALSE;
}
if(succes && ( [duik backendid] == 0 || [self deleteDuikServerSide:[duik backendid]]))
{
NSLog(#"End Delete");
if(sqlite3_exec(db, "COMMIT TRANSACTION", 0, 0, 0) !=SQLITE_OK) NSLog(#"SQL Error: %s",sqlite3_errmsg(db));
} else{
NSLog(#"Failed to delete remote copy");
sqlite3_exec(db, "ROLLBACK TRANSACTION", 0, 0, 0);
succes = false;
}
} #catch (NSException *e) {
NSLog(#"Failed to delete: %#", [e description]);
sqlite3_exec(db, "ROLLBACK TRANSACTION", 0, 0, 0);
} #finally {
#try {
sqlite3_finalize(deleteStmt);
sqlite3_close(db);
}
#catch (NSException *exception) {
//REally nothing to do here
}
sqlite3_close(db);
}
return succes;
}
When I try to run this, it keeps telling me the database is locked, yet I have no idea why it is locked. I have checked to see if other actions require the DB at the same time but I cannot find any. Also, if I add a row from the same tableView, I use the same construction with a transaction and that one DOES work. I have tried everything I could come up with, rewritten this chunk of code at least 10 times but so far no luck.
Is there anyone who can tell me what I am doing wrong here?
My app crashes on the following line:
sqlite3_prepare_v2(db, [sql UTF8String], -1, &pStmt, 0);
in the method of the FMDB sqlite wrapper:
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args {
if (![self databaseExists]) {
return 0x00;
}
if (inUse) {
[self warnInUse];
return 0x00;
}
[self setInUse:YES];
FMResultSet *rs = nil;
int rc = 0x00;
sqlite3_stmt *pStmt = 0x00;
FMStatement *statement = 0x00;
if (traceExecution && sql) {
NSLog(#"%# executeQuery: %#", self, sql);
}
if (shouldCacheStatements) {
statement = [self cachedStatementForQuery:sql];
pStmt = statement ? [statement statement] : 0x00;
}
int numberOfRetries = 0;
BOOL retry = NO;
if (!pStmt) {
do {
retry = NO;
const char *sqlStatement = [sql UTF8String];
rc = sqlite3_prepare_v2(db, sqlStatement, -1, &pStmt, 0);
if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
retry = YES;
usleep(20);
if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
NSLog(#"%s:%d Database busy (%#)", __FUNCTION__, __LINE__, [self databasePath]);
NSLog(#"Database busy");
sqlite3_finalize(pStmt);
[self setInUse:NO];
return nil;
}
}
else if (SQLITE_OK != rc) {
if (logsErrors) {
NSLog(#"DB Error: %d \"%#\"", [self lastErrorCode], [self lastErrorMessage]);
NSLog(#"DB Query: %#", sql);
#ifndef NS_BLOCK_ASSERTIONS
if (crashOnErrors) {
NSAssert2(false, #"DB Error: %d \"%#\"", [self lastErrorCode], [self lastErrorMessage]);
}
#endif
}
sqlite3_finalize(pStmt);
[self setInUse:NO];
return nil;
}
}
while (retry);
}
id obj;
int idx = 0;
int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)
while (idx < queryCount) {
if (arrayArgs) {
obj = [arrayArgs objectAtIndex:idx];
}
else {
obj = va_arg(args, id);
}
if (traceExecution) {
NSLog(#"obj: %#", obj);
}
idx++;
[self bindObject:obj toColumn:idx inStatement:pStmt];
}
if (idx != queryCount) {
NSLog(#"Error: the bind count is not correct for the # of variables (executeQuery)");
sqlite3_finalize(pStmt);
[self setInUse:NO];
return nil;
}
[statement retain]; // to balance the release below
if (!statement) {
statement = [[FMStatement alloc] init];
[statement setStatement:pStmt];
if (shouldCacheStatements) {
[self setCachedStatement:statement forQuery:sql];
}
}
// the statement gets closed in rs's dealloc or [rs close];
rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
[rs setQuery:sql];
NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
[openResultSets addObject:openResultSet];
statement.useCount = statement.useCount + 1;
[statement release];
[self setInUse:NO];
return rs;
}
The app crashes with EXC_BAD_ACCESS. I have tried to find out why by debugging with NSZombieEnabled and malloc_history, but it does not give me any answers. Also - the debugger tells me that the sql variable has a retain count of a very large number (which is probably because it is a static NSString) - so the EXC_BAD_ACCESS should not be because of the sql object being over-relesed.
Does anyone have any ideas on how to further debug this to find out what the problem is?
Solution: The problem was that my database was accessed by several threads. And even if all threads had synchronized access to the database handle, for sqlite versions prior to 3.3.1 (iOS uses 3.0) you can not safely use the same database handle across threads.
My solution was to create on-demand handles to the database for each thread that tries to access the database, like this:
- (ADatabaseConnection *)databaseConnection {
NSDictionary *dictionary = [[NSThread currentThread] threadDictionary];
NSString *key = #"aDatabaseConnection";
ADatabaseConnection *connection = [dictionary objectForKey:key];
if (connection == nil) {
connection = [[[ADatabaseConnection alloc] initWithDatabase:self] autorelease];
[dictionary setValue:connection forKey:key];
}
return connection;
}
Note that for sqlite versions >= 3.3.1, this is not needed as the same handle can be used across threads.
Another important thing to remember is that even if you use this approach to safely use the same database across threads, it might be wise to synchronize access to the database so that you do not access it simultaneously anyway to avoid database lock errors. I do both, use one handle for each thread and synchronize on the database.
It's not safe to use FMDatabase from multiple threads at the same time- so I've started work on a new class to help you make queries and updates from multiple threads using a pool. Right now it's on a branch, which you can view here:
https://github.com/ccgus/fmdb/tree/threadtests
Read the section titled "Using FMDatabasePool and Thread Safety."