3 @implementation FMDatabase
5 + (id)databaseWithPath:(NSString*)aPath {
6 return [[[FMDatabase alloc] initWithPath:aPath] autorelease];
9 - (id)initWithPath:(NSString*)aPath {
13 databasePath = [aPath copy];
17 busyRetryTimeout = 0x00;
26 [cachedStatements release];
27 [databasePath release];
32 + (NSString*) sqliteLibVersion {
33 return [NSString stringWithFormat:@"%s", sqlite3_libversion()];
36 - (NSString *) databasePath {
40 - (sqlite3*) sqliteHandle {
45 int err = sqlite3_open( [databasePath fileSystemRepresentation], &db );
46 if(err != SQLITE_OK) {
47 NSLog(@"error opening!: %d", err);
56 [self clearCachedStatements];
64 int numberOfRetries = 0;
67 rc = sqlite3_close(db);
68 if (SQLITE_BUSY == rc) {
71 if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
72 NSLog(@"%s:%d", __FUNCTION__, __LINE__);
73 NSLog(@"Database busy, unable to close");
77 else if (SQLITE_OK != rc) {
78 NSLog(@"error closing!: %d", rc);
86 - (void) clearCachedStatements {
88 NSEnumerator *e = [cachedStatements objectEnumerator];
89 FMStatement *cachedStmt;
91 while ((cachedStmt = [e nextObject])) {
95 [cachedStatements removeAllObjects];
98 - (FMStatement*) cachedStatementForQuery:(NSString*)query {
99 return [cachedStatements objectForKey:query];
102 - (void) setCachedStatement:(FMStatement*)statement forQuery:(NSString*)query {
103 //NSLog(@"setting query: %@", query);
104 query = [query copy]; // in case we got handed in a mutable string...
105 [statement setQuery:query];
106 [cachedStatements setObject:statement forKey:query];
111 - (BOOL) rekey:(NSString*)key {
112 #ifdef SQLITE_HAS_CODEC
117 int rc = sqlite3_rekey(db, [key UTF8String], strlen([key UTF8String]));
119 if (rc != SQLITE_OK) {
120 NSLog(@"error on rekey: %d", rc);
121 NSLog(@"%@", [self lastErrorMessage]);
124 return (rc == SQLITE_OK);
130 - (BOOL) setKey:(NSString*)key {
131 #ifdef SQLITE_HAS_CODEC
136 int rc = sqlite3_key(db, [key UTF8String], strlen([key UTF8String]));
138 return (rc == SQLITE_OK);
144 - (BOOL) goodConnection {
150 FMResultSet *rs = [self executeQuery:@"select name from sqlite_master where type='table'"];
160 - (void) compainAboutInUse {
161 NSLog(@"The FMDatabase %@ is currently in use.", self);
164 NSAssert1(false, @"The FMDatabase %@ is currently in use.", self);
168 - (NSString*) lastErrorMessage {
169 return [NSString stringWithUTF8String:sqlite3_errmsg(db)];
173 return ([self lastErrorCode] != SQLITE_OK);
176 - (int) lastErrorCode {
177 return sqlite3_errcode(db);
180 - (sqlite_int64) lastInsertRowId {
183 [self compainAboutInUse];
188 sqlite_int64 ret = sqlite3_last_insert_rowid(db);
195 - (void) bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt; {
197 if ((!obj) || ((NSNull *)obj == [NSNull null])) {
198 sqlite3_bind_null(pStmt, idx);
201 // FIXME - someday check the return codes on these binds.
202 else if ([obj isKindOfClass:[NSData class]]) {
203 sqlite3_bind_blob(pStmt, idx, [obj bytes], [obj length], SQLITE_STATIC);
205 else if ([obj isKindOfClass:[NSDate class]]) {
206 sqlite3_bind_double(pStmt, idx, [obj timeIntervalSince1970]);
208 else if ([obj isKindOfClass:[NSNumber class]]) {
210 if (strcmp([obj objCType], @encode(BOOL)) == 0) {
211 sqlite3_bind_int(pStmt, idx, ([obj boolValue] ? 1 : 0));
213 else if (strcmp([obj objCType], @encode(int)) == 0) {
214 sqlite3_bind_int64(pStmt, idx, [obj longValue]);
216 else if (strcmp([obj objCType], @encode(long)) == 0) {
217 sqlite3_bind_int64(pStmt, idx, [obj longValue]);
219 else if (strcmp([obj objCType], @encode(float)) == 0) {
220 sqlite3_bind_double(pStmt, idx, [obj floatValue]);
222 else if (strcmp([obj objCType], @encode(double)) == 0) {
223 sqlite3_bind_double(pStmt, idx, [obj doubleValue]);
226 sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);
230 sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);
234 - (id) executeQuery:(NSString *)sql arguments:(va_list)args {
237 [self compainAboutInUse];
243 FMResultSet *rs = nil;
246 sqlite3_stmt *pStmt = 0x00;;
247 FMStatement *statement = 0x00;
249 if (traceExecution && sql) {
250 NSLog(@"%@ executeQuery: %@", self, sql);
253 if (shouldCacheStatements) {
254 statement = [self cachedStatementForQuery:sql];
255 pStmt = statement ? [statement statement] : 0x00;
258 int numberOfRetries = 0;
264 rc = sqlite3_prepare(db, [sql UTF8String], -1, &pStmt, 0);
266 if (SQLITE_BUSY == rc) {
270 if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
271 NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
272 NSLog(@"Database busy");
273 sqlite3_finalize(pStmt);
278 else if (SQLITE_OK != rc) {
282 NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
283 NSLog(@"DB Query: %@", sql);
285 #ifdef __BIG_ENDIAN__
288 NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
292 sqlite3_finalize(pStmt);
303 int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)
305 while (idx < queryCount) {
306 obj = va_arg(args, id);
308 if (traceExecution) {
309 NSLog(@"obj: %@", obj);
314 [self bindObject:obj toColumn:idx inStatement:pStmt];
317 if (idx != queryCount) {
318 NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)");
319 sqlite3_finalize(pStmt);
324 [statement retain]; // to balance the release below
327 statement = [[FMStatement alloc] init];
328 [statement setStatement:pStmt];
330 if (shouldCacheStatements) {
331 [self setCachedStatement:statement forQuery:sql];
335 // the statement gets close in rs's dealloc or [rs close];
336 rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
339 statement.useCount = statement.useCount + 1;
348 - (id) executeQuery:(NSString*)sql, ... {
352 id result = [self executeQuery:sql arguments:args];
359 - (BOOL) executeUpdate:(NSString*)sql arguments:(va_list)args {
362 [self compainAboutInUse];
369 sqlite3_stmt *pStmt = 0x00;
370 FMStatement *cachedStmt = 0x00;
372 if (traceExecution && sql) {
373 NSLog(@"%@ executeUpdate: %@", self, sql);
376 if (shouldCacheStatements) {
377 cachedStmt = [self cachedStatementForQuery:sql];
378 pStmt = cachedStmt ? [cachedStmt statement] : 0x00;
381 int numberOfRetries = 0;
388 rc = sqlite3_prepare(db, [sql UTF8String], -1, &pStmt, 0);
389 if (SQLITE_BUSY == rc) {
393 if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
394 NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
395 NSLog(@"Database busy");
396 sqlite3_finalize(pStmt);
401 else if (SQLITE_OK != rc) {
405 NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
406 NSLog(@"DB Query: %@", sql);
408 #ifdef __BIG_ENDIAN__
411 NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
415 sqlite3_finalize(pStmt);
427 int queryCount = sqlite3_bind_parameter_count(pStmt);
429 while (idx < queryCount) {
431 obj = va_arg(args, id);
433 if (traceExecution) {
434 NSLog(@"obj: %@", obj);
439 [self bindObject:obj toColumn:idx inStatement:pStmt];
442 if (idx != queryCount) {
443 NSLog(@"Error: the bind count is not correct for the # of variables (%@) (executeUpdate)", sql);
444 sqlite3_finalize(pStmt);
449 /* Call sqlite3_step() to run the virtual machine. Since the SQL being
450 ** executed is not a SELECT statement, we assume no data will be returned.
454 rc = sqlite3_step(pStmt);
457 if (SQLITE_BUSY == rc) {
458 // this will happen if the db is locked, like if we are doing an update or insert.
459 // in that case, retry the step... and maybe wait just 10 milliseconds.
463 if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
464 NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
465 NSLog(@"Database busy");
469 else if (SQLITE_DONE == rc || SQLITE_ROW == rc) {
470 // all is well, let's return.
472 else if (SQLITE_ERROR == rc) {
473 NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_ERROR", rc, sqlite3_errmsg(db));
474 NSLog(@"DB Query: %@", sql);
476 else if (SQLITE_MISUSE == rc) {
478 NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_MISUSE", rc, sqlite3_errmsg(db));
479 NSLog(@"DB Query: %@", sql);
483 NSLog(@"Unknown error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(db));
484 NSLog(@"DB Query: %@", sql);
489 assert( rc!=SQLITE_ROW );
492 if (shouldCacheStatements && !cachedStmt) {
493 cachedStmt = [[FMStatement alloc] init];
495 [cachedStmt setStatement:pStmt];
497 [self setCachedStatement:cachedStmt forQuery:sql];
499 [cachedStmt release];
503 cachedStmt.useCount = cachedStmt.useCount + 1;
504 rc = sqlite3_reset(pStmt);
507 /* Finalize the virtual machine. This releases all memory and other
508 ** resources allocated by the sqlite3_prepare() call above.
510 rc = sqlite3_finalize(pStmt);
515 return (rc == SQLITE_OK);
518 - (BOOL) executeUpdate:(NSString*)sql, ... {
522 BOOL result = [self executeUpdate:sql arguments:args];
531 BOOL b = [self executeUpdate:@"ROLLBACK TRANSACTION;"];
539 BOOL b = [self executeUpdate:@"COMMIT TRANSACTION;"];
546 - (BOOL) beginDeferredTransaction {
547 BOOL b = [self executeUpdate:@"BEGIN DEFERRED TRANSACTION;"];
554 - (BOOL) beginTransaction {
555 BOOL b = [self executeUpdate:@"BEGIN EXCLUSIVE TRANSACTION;"];
565 - (void)setLogsErrors:(BOOL)flag {
569 - (BOOL)crashOnErrors {
570 return crashOnErrors;
572 - (void)setCrashOnErrors:(BOOL)flag {
573 crashOnErrors = flag;
577 return inUse || inTransaction;
580 - (void) setInUse:(BOOL)b {
584 - (BOOL)inTransaction {
585 return inTransaction;
587 - (void)setInTransaction:(BOOL)flag {
588 inTransaction = flag;
591 - (BOOL)traceExecution {
592 return traceExecution;
594 - (void)setTraceExecution:(BOOL)flag {
595 traceExecution = flag;
601 - (void)setCheckedOut:(BOOL)flag {
606 - (int)busyRetryTimeout {
607 return busyRetryTimeout;
609 - (void)setBusyRetryTimeout:(int)newBusyRetryTimeout {
610 busyRetryTimeout = newBusyRetryTimeout;
614 - (BOOL)shouldCacheStatements {
615 return shouldCacheStatements;
618 - (void)setShouldCacheStatements:(BOOL)value {
620 shouldCacheStatements = value;
622 if (shouldCacheStatements && !cachedStatements) {
623 [self setCachedStatements:[NSMutableDictionary dictionary]];
626 if (!shouldCacheStatements) {
627 [self setCachedStatements:nil];
631 - (NSMutableDictionary *) cachedStatements {
632 return cachedStatements;
635 - (void)setCachedStatements:(NSMutableDictionary *)value {
636 if (cachedStatements != value) {
637 [cachedStatements release];
638 cachedStatements = [value retain];
647 @implementation FMStatement
658 sqlite3_finalize(statement);
665 sqlite3_reset(statement);
669 - (sqlite3_stmt *) statement {
673 - (void)setStatement:(sqlite3_stmt *)value {
677 - (NSString *) query {
681 - (void)setQuery:(NSString *)value {
682 if (query != value) {
684 query = [value retain];
692 - (void)setUseCount:(long)value {
693 if (useCount != value) {
700 - (NSString*) description {
701 return [NSString stringWithFormat:@"%@ %d hit(s) for query %@", [super description], useCount, query];