PageRenderTime 3370ms CodeModel.GetById 247ms app.highlight 2977ms RepoModel.GetById 105ms app.codeStats 1ms

/fmdb/FMDatabase.m

http://github.com/petewarden/iPhoneTracker
Objective C | 815 lines | 615 code | 185 blank | 15 comment | 152 complexity | f9f130231a76d62b57bfad5f160f6dd7 MD5 | raw file
  1#import "FMDatabase.h"
  2#import "unistd.h"
  3
  4@implementation FMDatabase
  5
  6+ (id)databaseWithPath:(NSString*)aPath {
  7    return [[[self alloc] initWithPath:aPath] autorelease];
  8}
  9
 10- (id)initWithPath:(NSString*)aPath {
 11    self = [super init];
 12	
 13    if (self) {
 14        databasePath        = [aPath copy];
 15		openResultSets      = [[NSMutableSet alloc] init];
 16        db                  = 0x00;
 17        logsErrors          = 0x00;
 18        crashOnErrors       = 0x00;
 19        busyRetryTimeout    = 0x00;
 20    }
 21	
 22	return self;
 23}
 24
 25- (void)finalize {
 26	[self close];
 27	[super finalize];
 28}
 29
 30- (void)dealloc {
 31	[self close];
 32    
 33	[openResultSets release];
 34    [cachedStatements release];
 35    [databasePath release];
 36	
 37    [super dealloc];
 38}
 39
 40+ (NSString*)sqliteLibVersion {
 41    return [NSString stringWithFormat:@"%s", sqlite3_libversion()];
 42}
 43
 44- (NSString *)databasePath {
 45    return databasePath;
 46}
 47
 48- (sqlite3*)sqliteHandle {
 49    return db;
 50}
 51
 52- (BOOL)open {
 53	if (db) {
 54		return YES;
 55	}
 56	
 57	int err = sqlite3_open((databasePath ? [databasePath fileSystemRepresentation] : ":memory:"), &db );
 58	if(err != SQLITE_OK) {
 59        NSLog(@"error opening!: %d", err);
 60		return NO;
 61	}
 62	
 63	return YES;
 64}
 65
 66#if SQLITE_VERSION_NUMBER >= 3005000
 67- (BOOL)openWithFlags:(int)flags {
 68    int err = sqlite3_open_v2((databasePath ? [databasePath fileSystemRepresentation] : ":memory:"), &db, flags, NULL /* Name of VFS module to use */);
 69	if(err != SQLITE_OK) {
 70		NSLog(@"error opening!: %d", err);
 71		return NO;
 72	}
 73	return YES;
 74}
 75#endif
 76
 77
 78- (BOOL)close {
 79    
 80    [self clearCachedStatements];
 81	[self closeOpenResultSets];
 82    
 83	if (!db) {
 84        return YES;
 85    }
 86    
 87    int  rc;
 88    BOOL retry;
 89    int numberOfRetries = 0;
 90    do {
 91        retry   = NO;
 92        rc      = sqlite3_close(db);
 93        if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
 94            retry = YES;
 95            usleep(20);
 96            if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
 97                NSLog(@"%s:%d", __FUNCTION__, __LINE__);
 98                NSLog(@"Database busy, unable to close");
 99                return NO;
100            }
101        }
102        else if (SQLITE_OK != rc) {
103            NSLog(@"error closing!: %d", rc);
104        }
105    }
106    while (retry);
107    
108	db = nil;
109    return YES;
110}
111
112- (void)clearCachedStatements {
113    
114    NSEnumerator *e = [cachedStatements objectEnumerator];
115    FMStatement *cachedStmt;
116
117    while ((cachedStmt = [e nextObject])) {
118    	[cachedStmt close];
119    }
120    
121    [cachedStatements removeAllObjects];
122}
123
124- (void)closeOpenResultSets {
125	//Copy the set so we don't get mutation errors
126	NSSet *resultSets = [[openResultSets copy] autorelease];
127	
128	NSEnumerator *e = [resultSets objectEnumerator];
129	NSValue *returnedResultSet = nil;
130	
131	while((returnedResultSet = [e nextObject])) {
132		FMResultSet *rs = (FMResultSet *)[returnedResultSet pointerValue];
133		if ([rs respondsToSelector:@selector(close)]) {
134			[rs close];
135		}
136	}
137}
138
139- (void)resultSetDidClose:(FMResultSet *)resultSet {
140	NSValue *setValue = [NSValue valueWithNonretainedObject:resultSet];
141	[openResultSets removeObject:setValue];
142}
143
144- (FMStatement*)cachedStatementForQuery:(NSString*)query {
145    return [cachedStatements objectForKey:query];
146}
147
148- (void)setCachedStatement:(FMStatement*)statement forQuery:(NSString*)query {
149    //NSLog(@"setting query: %@", query);
150    query = [query copy]; // in case we got handed in a mutable string...
151    [statement setQuery:query];
152    [cachedStatements setObject:statement forKey:query];
153    [query release];
154}
155
156
157- (BOOL)rekey:(NSString*)key {
158#ifdef SQLITE_HAS_CODEC
159    if (!key) {
160        return NO;
161    }
162    
163    int rc = sqlite3_rekey(db, [key UTF8String], strlen([key UTF8String]));
164    
165    if (rc != SQLITE_OK) {
166        NSLog(@"error on rekey: %d", rc);
167        NSLog(@"%@", [self lastErrorMessage]);
168    }
169    
170    return (rc == SQLITE_OK);
171#else
172    return NO;
173#endif
174}
175
176- (BOOL)setKey:(NSString*)key {
177#ifdef SQLITE_HAS_CODEC
178    if (!key) {
179        return NO;
180    }
181    
182    int rc = sqlite3_key(db, [key UTF8String], strlen([key UTF8String]));
183    
184    return (rc == SQLITE_OK);
185#else
186    return NO;
187#endif
188}
189
190- (BOOL)goodConnection {
191    
192    if (!db) {
193        return NO;
194    }
195    
196    FMResultSet *rs = [self executeQuery:@"select name from sqlite_master where type='table'"];
197    
198    if (rs) {
199        [rs close];
200        return YES;
201    }
202    
203    return NO;
204}
205
206- (void)compainAboutInUse {
207    NSLog(@"The FMDatabase %@ is currently in use.", self);
208    
209#ifndef NS_BLOCK_ASSERTIONS
210    if (crashOnErrors) {
211        NSAssert1(false, @"The FMDatabase %@ is currently in use.", self);
212    }
213#endif
214}
215
216- (NSString*)lastErrorMessage {
217    return [NSString stringWithUTF8String:sqlite3_errmsg(db)];
218}
219
220- (BOOL)hadError {
221    int lastErrCode = [self lastErrorCode];
222    
223    return (lastErrCode > SQLITE_OK && lastErrCode < SQLITE_ROW);
224}
225
226- (int)lastErrorCode {
227    return sqlite3_errcode(db);
228}
229
230- (sqlite_int64)lastInsertRowId {
231    
232    if (inUse) {
233        [self compainAboutInUse];
234        return NO;
235    }
236    [self setInUse:YES];
237    
238    sqlite_int64 ret = sqlite3_last_insert_rowid(db);
239    
240    [self setInUse:NO];
241    
242    return ret;
243}
244
245- (int)changes {
246	if (inUse) {
247        [self compainAboutInUse];
248        return 0;
249    }
250	
251    [self setInUse:YES];
252    int ret = sqlite3_changes(db);
253    [self setInUse:NO];
254    
255    return ret;
256}
257
258- (void)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt {
259    
260    if ((!obj) || ((NSNull *)obj == [NSNull null])) {
261        sqlite3_bind_null(pStmt, idx);
262    }
263    
264    // FIXME - someday check the return codes on these binds.
265    else if ([obj isKindOfClass:[NSData class]]) {
266        sqlite3_bind_blob(pStmt, idx, [obj bytes], (int)[obj length], SQLITE_STATIC);
267    }
268    else if ([obj isKindOfClass:[NSDate class]]) {
269        sqlite3_bind_double(pStmt, idx, [obj timeIntervalSince1970]);
270    }
271    else if ([obj isKindOfClass:[NSNumber class]]) {
272        
273        if (strcmp([obj objCType], @encode(BOOL)) == 0) {
274            sqlite3_bind_int(pStmt, idx, ([obj boolValue] ? 1 : 0));
275        }
276        else if (strcmp([obj objCType], @encode(int)) == 0) {
277            sqlite3_bind_int64(pStmt, idx, [obj longValue]);
278        }
279        else if (strcmp([obj objCType], @encode(long)) == 0) {
280            sqlite3_bind_int64(pStmt, idx, [obj longValue]);
281        }
282        else if (strcmp([obj objCType], @encode(long long)) == 0) {
283            sqlite3_bind_int64(pStmt, idx, [obj longLongValue]);
284        }
285        else if (strcmp([obj objCType], @encode(float)) == 0) {
286            sqlite3_bind_double(pStmt, idx, [obj floatValue]);
287        }
288        else if (strcmp([obj objCType], @encode(double)) == 0) {
289            sqlite3_bind_double(pStmt, idx, [obj doubleValue]);
290        }
291        else {
292            sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);
293        }
294    }
295    else {
296        sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);
297    }
298}
299
300- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args {
301    
302    if (inUse) {
303        [self compainAboutInUse];
304        return nil;
305    }
306    
307    [self setInUse:YES];
308    
309    FMResultSet *rs = nil;
310    
311    int rc                  = 0x00;;
312    sqlite3_stmt *pStmt     = 0x00;;
313    FMStatement *statement  = 0x00;
314    
315    if (traceExecution && sql) {
316        NSLog(@"%@ executeQuery: %@", self, sql);
317    }
318    
319    if (shouldCacheStatements) {
320        statement = [self cachedStatementForQuery:sql];
321        pStmt = statement ? [statement statement] : 0x00;
322    }
323    
324    int numberOfRetries = 0;
325    BOOL retry          = NO;
326    
327    if (!pStmt) {
328        do {
329            retry   = NO;
330            rc      = sqlite3_prepare_v2(db, [sql UTF8String], -1, &pStmt, 0);
331            
332            if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
333                retry = YES;
334                usleep(20);
335                
336                if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
337                    NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
338                    NSLog(@"Database busy");
339                    sqlite3_finalize(pStmt);
340                    [self setInUse:NO];
341                    return nil;
342                }
343            }
344            else if (SQLITE_OK != rc) {
345                
346                
347                if (logsErrors) {
348                    NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
349                    NSLog(@"DB Query: %@", sql);
350#ifndef NS_BLOCK_ASSERTIONS
351                    if (crashOnErrors) {
352                        NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
353                    }
354#endif
355                }
356                
357                sqlite3_finalize(pStmt);
358                
359                [self setInUse:NO];
360                return nil;
361            }
362        }
363        while (retry);
364    }
365    
366    id obj;
367    int idx = 0;
368    int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)
369    
370    while (idx < queryCount) {
371        
372        if (arrayArgs) {
373            obj = [arrayArgs objectAtIndex:idx];
374        }
375        else {
376            obj = va_arg(args, id);
377        }
378        
379        if (traceExecution) {
380            NSLog(@"obj: %@", obj);
381        }
382        
383        idx++;
384        
385        [self bindObject:obj toColumn:idx inStatement:pStmt];
386    }
387    
388    if (idx != queryCount) {
389        NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)");
390        sqlite3_finalize(pStmt);
391        [self setInUse:NO];
392        return nil;
393    }
394    
395    [statement retain]; // to balance the release below
396    
397    if (!statement) {
398        statement = [[FMStatement alloc] init];
399        [statement setStatement:pStmt];
400        
401        if (shouldCacheStatements) {
402            [self setCachedStatement:statement forQuery:sql];
403        }
404    }
405    
406    // the statement gets closed in rs's dealloc or [rs close];
407    rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
408    [rs setQuery:sql];
409	NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
410	[openResultSets addObject:openResultSet];
411    
412    statement.useCount = statement.useCount + 1;
413    
414    [statement release];    
415    
416    [self setInUse:NO];
417    
418    return rs;
419}
420
421- (FMResultSet *)executeQuery:(NSString*)sql, ... {
422    va_list args;
423    va_start(args, sql);
424    
425    id result = [self executeQuery:sql withArgumentsInArray:nil orVAList:args];
426    
427    va_end(args);
428    return result;
429}
430
431- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments {
432    return [self executeQuery:sql withArgumentsInArray:arguments orVAList:nil];
433}
434
435- (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args {
436	
437    if (inUse) {
438        [self compainAboutInUse];
439        return NO;
440    }
441    
442    [self setInUse:YES];
443    
444    int rc                   = 0x00;
445    sqlite3_stmt *pStmt      = 0x00;
446    FMStatement *cachedStmt = 0x00;
447    
448    if (traceExecution && sql) {
449        NSLog(@"%@ executeUpdate: %@", self, sql);
450    }
451    
452    if (shouldCacheStatements) {
453        cachedStmt = [self cachedStatementForQuery:sql];
454        pStmt = cachedStmt ? [cachedStmt statement] : 0x00;
455    }
456    
457    int numberOfRetries = 0;
458    BOOL retry          = NO;
459    
460    if (!pStmt) {
461        
462        do {
463            retry   = NO;
464            rc      = sqlite3_prepare_v2(db, [sql UTF8String], -1, &pStmt, 0);
465            if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
466                retry = YES;
467                usleep(20);
468                
469                if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
470                    NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
471                    NSLog(@"Database busy");
472                    sqlite3_finalize(pStmt);
473                    [self setInUse:NO];
474                    return NO;
475                }
476            }
477            else if (SQLITE_OK != rc) {
478                
479                
480                if (logsErrors) {
481                    NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
482                    NSLog(@"DB Query: %@", sql);
483#ifndef NS_BLOCK_ASSERTIONS
484                    if (crashOnErrors) {
485                        NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
486                    }
487#endif
488                }
489                
490                sqlite3_finalize(pStmt);
491                [self setInUse:NO];
492                
493                if (outErr) {
494                    *outErr = [NSError errorWithDomain:[NSString stringWithUTF8String:sqlite3_errmsg(db)] code:rc userInfo:nil];
495                }
496                
497                return NO;
498            }
499        }
500        while (retry);
501    }
502    
503    
504    id obj;
505    int idx = 0;
506    int queryCount = sqlite3_bind_parameter_count(pStmt);
507    
508    while (idx < queryCount) {
509        
510        if (arrayArgs) {
511            obj = [arrayArgs objectAtIndex:idx];
512        }
513        else {
514            obj = va_arg(args, id);
515        }
516        
517        
518        if (traceExecution) {
519            NSLog(@"obj: %@", obj);
520        }
521        
522        idx++;
523        
524        [self bindObject:obj toColumn:idx inStatement:pStmt];
525    }
526    
527    if (idx != queryCount) {
528        NSLog(@"Error: the bind count is not correct for the # of variables (%@) (executeUpdate)", sql);
529        sqlite3_finalize(pStmt);
530        [self setInUse:NO];
531        return NO;
532    }
533    
534    /* Call sqlite3_step() to run the virtual machine. Since the SQL being
535     ** executed is not a SELECT statement, we assume no data will be returned.
536     */
537    numberOfRetries = 0;
538    do {
539        rc      = sqlite3_step(pStmt);
540        retry   = NO;
541        
542        if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
543            // this will happen if the db is locked, like if we are doing an update or insert.
544            // in that case, retry the step... and maybe wait just 10 milliseconds.
545            retry = YES;
546			if (SQLITE_LOCKED == rc) {
547				rc = sqlite3_reset(pStmt);
548				if (rc != SQLITE_LOCKED) {
549					NSLog(@"Unexpected result from sqlite3_reset (%d) eu", rc);
550				}
551			}
552            usleep(20);
553            
554            if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
555                NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
556                NSLog(@"Database busy");
557                retry = NO;
558            }
559        }
560        else if (SQLITE_DONE == rc || SQLITE_ROW == rc) {
561            // all is well, let's return.
562        }
563        else if (SQLITE_ERROR == rc) {
564            NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_ERROR", rc, sqlite3_errmsg(db));
565            NSLog(@"DB Query: %@", sql);
566        }
567        else if (SQLITE_MISUSE == rc) {
568            // uh oh.
569            NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_MISUSE", rc, sqlite3_errmsg(db));
570            NSLog(@"DB Query: %@", sql);
571        }
572        else {
573            // wtf?
574            NSLog(@"Unknown error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(db));
575            NSLog(@"DB Query: %@", sql);
576        }
577        
578    } while (retry);
579    
580    assert( rc!=SQLITE_ROW );
581    
582    
583    if (shouldCacheStatements && !cachedStmt) {
584        cachedStmt = [[FMStatement alloc] init];
585        
586        [cachedStmt setStatement:pStmt];
587        
588        [self setCachedStatement:cachedStmt forQuery:sql];
589        
590        [cachedStmt release];
591    }
592    
593    if (cachedStmt) {
594        cachedStmt.useCount = cachedStmt.useCount + 1;
595        rc = sqlite3_reset(pStmt);
596    }
597    else {
598        /* Finalize the virtual machine. This releases all memory and other
599         ** resources allocated by the sqlite3_prepare() call above.
600         */
601        rc = sqlite3_finalize(pStmt);
602    }
603    
604    [self setInUse:NO];
605    
606    return (rc == SQLITE_OK);
607}
608
609
610- (BOOL)executeUpdate:(NSString*)sql, ... {
611    va_list args;
612    va_start(args, sql);
613    
614    BOOL result = [self executeUpdate:sql error:nil withArgumentsInArray:nil orVAList:args];
615    
616    va_end(args);
617    return result;
618}
619
620
621
622- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments {
623    return [self executeUpdate:sql error:nil withArgumentsInArray:arguments orVAList:nil];
624}
625
626- (BOOL)update:(NSString*)sql error:(NSError**)outErr bind:(id)bindArgs, ... {
627    va_list args;
628    va_start(args, bindArgs);
629    
630    BOOL result = [self executeUpdate:sql error:outErr withArgumentsInArray:nil orVAList:args];
631    
632    va_end(args);
633    return result;
634}
635
636- (BOOL)rollback {
637    BOOL b = [self executeUpdate:@"ROLLBACK TRANSACTION;"];
638    if (b) {
639        inTransaction = NO;
640    }
641    return b;
642}
643
644- (BOOL)commit {
645    BOOL b =  [self executeUpdate:@"COMMIT TRANSACTION;"];
646    if (b) {
647        inTransaction = NO;
648    }
649    return b;
650}
651
652- (BOOL)beginDeferredTransaction {
653    BOOL b =  [self executeUpdate:@"BEGIN DEFERRED TRANSACTION;"];
654    if (b) {
655        inTransaction = YES;
656    }
657    return b;
658}
659
660- (BOOL)beginTransaction {
661    BOOL b =  [self executeUpdate:@"BEGIN EXCLUSIVE TRANSACTION;"];
662    if (b) {
663        inTransaction = YES;
664    }
665    return b;
666}
667
668- (BOOL)logsErrors {
669    return logsErrors;
670}
671- (void)setLogsErrors:(BOOL)flag {
672    logsErrors = flag;
673}
674
675- (BOOL)crashOnErrors {
676    return crashOnErrors;
677}
678- (void)setCrashOnErrors:(BOOL)flag {
679    crashOnErrors = flag;
680}
681
682- (BOOL)inUse {
683    return inUse || inTransaction;
684}
685
686- (void)setInUse:(BOOL)b {
687    inUse = b;
688}
689
690- (BOOL)inTransaction {
691    return inTransaction;
692}
693- (void)setInTransaction:(BOOL)flag {
694    inTransaction = flag;
695}
696
697- (BOOL)traceExecution {
698    return traceExecution;
699}
700- (void)setTraceExecution:(BOOL)flag {
701    traceExecution = flag;
702}
703
704- (BOOL)checkedOut {
705    return checkedOut;
706}
707- (void)setCheckedOut:(BOOL)flag {
708    checkedOut = flag;
709}
710
711
712- (int)busyRetryTimeout {
713    return busyRetryTimeout;
714}
715- (void)setBusyRetryTimeout:(int)newBusyRetryTimeout {
716    busyRetryTimeout = newBusyRetryTimeout;
717}
718
719
720- (BOOL)shouldCacheStatements {
721    return shouldCacheStatements;
722}
723
724- (void)setShouldCacheStatements:(BOOL)value {
725    
726    shouldCacheStatements = value;
727    
728    if (shouldCacheStatements && !cachedStatements) {
729        [self setCachedStatements:[NSMutableDictionary dictionary]];
730    }
731    
732    if (!shouldCacheStatements) {
733        [self setCachedStatements:nil];
734    }
735}
736
737- (NSMutableDictionary *)cachedStatements {
738    return cachedStatements;
739}
740
741- (void)setCachedStatements:(NSMutableDictionary *)value {
742    if (cachedStatements != value) {
743        [cachedStatements release];
744        cachedStatements = [value retain];
745    }
746}
747
748
749@end
750
751
752
753@implementation FMStatement
754
755- (void)finalize {
756	[self close];
757	[super finalize];
758}
759
760- (void)dealloc {
761	[self close];
762    [query release];
763	[super dealloc];
764}
765
766
767- (void)close {
768    if (statement) {
769        sqlite3_finalize(statement);
770        statement = 0x00;
771    }
772}
773
774- (void)reset {
775    if (statement) {
776        sqlite3_reset(statement);
777    }
778}
779
780- (sqlite3_stmt *)statement {
781    return statement;
782}
783
784- (void)setStatement:(sqlite3_stmt *)value {
785    statement = value;
786}
787
788- (NSString *)query {
789    return query;
790}
791
792- (void)setQuery:(NSString *)value {
793    if (query != value) {
794        [query release];
795        query = [value retain];
796    }
797}
798
799- (long)useCount {
800    return useCount;
801}
802
803- (void)setUseCount:(long)value {
804    if (useCount != value) {
805        useCount = value;
806    }
807}
808
809- (NSString*)description {
810    return [NSString stringWithFormat:@"%@ %d hit(s) for query %@", [super description], useCount, query];
811}
812
813
814@end
815