Thursday, April 12, 2012

Converting sqllite error to NSError – bridging the Gap

sqllite and Cocoa represent two distinct worlds with different error handling idioms. In order to bridge that semantic gap in my code, I came up with the following helper code:
@implementation DbHelper
 
+ (void)handleSqlError:(sqlite3 *)db error:(NSError **)error
{
    NSLog(@"Error while executing sql statement. '%s'", sqlite3_errmsg(db));
    
    NSError *underlyingError = [[[NSError alloc] initWithDomain:@"db"
                                                           code:sqlite3_errcode(db) userInfo:nil]autorelease];
    // Make and return custom domain error.
    NSArray *objArray = [NSArray arrayWithObjects:[NSString stringWithFormat:@"sqlite error:%s", sqlite3_errmsg(db)], underlyingError, nil];
    NSArray *keyArray = [NSArray arrayWithObjects:NSLocalizedDescriptionKey,
                         NSUnderlyingErrorKey, nil];
    NSDictionary *eDict = [NSDictionary dictionaryWithObjects:objArray
                                                      forKeys:keyArray];
    
    *error = [[[NSError alloc] initWithDomain:@""
                                         code:1 userInfo:eDict] autorelease];
}
Usage in the code would be (“select” is “aselect” on purpose):
...
sqlite3_stmt *checkStmt;
const char *sql = "aselect count(*) from sqlite_master where type='table' AND name=?";
...
 
if(sqlite3_prepare_v2(db, sql, -1, &checkStmt, NULL) == SQLITE_OK)
    {
...
}
else 
        {
            
            [DbHelper handleSqlError:db error:error];
            
            sqlite3_finalize(checkStmt);
            sqlite3_close(db);
            return false;
        }
Unit test to see what it would return in case of a simulated error:
- (void) test_when_invalidstatement_should_return_error
{
    NSError *error = nil;
    
    bool exists = [SchemaUpdater tableExistsWithName:@"note" error:&error];
    
    STAssertFalse(exists, @"When statement is invalid, exists should be false!");
    
    STAssertNotNil(error, @"For this test case error should not be nil!");
    
    NSLog(@"%@", error);
}
Where mine SchemaUpdater is just a simple class with snippet shown above having error in sql statement and calling that handleSqlError method.
The output of NSLog is:
Error Domain= Code=1 
"sqlite error:near "aselect": syntax error" 
UserInfo=0xbd28b60 
{NSUnderlyingError=0xbd28b40 "The operation couldn’t be completed. ( error 1.)", 
NSLocalizedDescription=sqlite error:near "aselect": syntax error}
This is it for a moment. You might be also interested in a follow up post: Check if sqllite table exists
...
Or wait! Here are some links that enlightened me on NSError idioms:
http://weblog.bignerdranch.com/?p=360 (definitely take a look if your way of checking for error is error!=nil)
http://www.cimgf.com/2008/04/04/cocoa-tutorial-using-nserror-to-great-effect/ (probably the most comprehensive one)
Stackoverflow nn risk of the NULL dereference
Official:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSError_Class/Reference/Reference.html
Using and Creating Erorr objects


No comments: