removed dependencies to FMDatabaseAdditions
[fmdb.git] / src / FMDatabase.m
blobcea37cd09af0cc23892e1e98d0a4fc23eb48bf89
1 #import "FMDatabase.h"
3 @implementation FMDatabase
5 + (id)databaseWithPath:(NSString*)aPath {
6     return [[[FMDatabase alloc] initWithPath:aPath] autorelease];
9 - (id)initWithPath:(NSString*)aPath {
10     self = [super init];
11         
12     if (self) {
13         databasePath        = [aPath copy];
14         db                  = 0x00;
15         logsErrors          = 0x00;
16         crashOnErrors       = 0x00;
17         busyRetryTimeout    = 0x00;
18     }
19         
20         return self;
23 - (void)dealloc {
24         [self close];
25         [databasePath release];
26         [super dealloc];
29 + (NSString*) sqliteLibVersion {
30     return [NSString stringWithFormat:@"%s", sqlite3_libversion()];
33 - (NSString *) databasePath {
34     return databasePath;
37 - (sqlite3*) sqliteHandle {
38     return db;
41 - (BOOL) open {
42         int err = sqlite3_open( [databasePath fileSystemRepresentation], &db );
43         if(err != SQLITE_OK) {
44         NSLog(@"error opening!: %d", err);
45                 return NO;
46         }
47         
48         return YES;
51 - (void) close {
52         if (!db) {
53         return;
54     }
55     
56     int  rc;
57     BOOL retry;
58     int numberOfRetries = 0;
59     do {
60         retry   = NO;
61         rc      = sqlite3_close(db);
62         if (SQLITE_BUSY == rc) {
63             retry = YES;
64             usleep(20);
65             if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
66                 NSLog(@"%s:%d", __FUNCTION__, __LINE__);
67                 NSLog(@"Database busy, unable to close");
68                 return;
69             }
70         }
71         else if (SQLITE_OK != rc) {
72             NSLog(@"error closing!: %d", rc);
73         }
74     }
75     while (retry);
76     
77         db = nil;
80 #ifdef SQLITE_HAS_CODEC
82 - (BOOL) rekey:(NSString*)key {
83     
84     if (!key) {
85         return NO;
86     }
87     
88     int rc = sqlite3_rekey(db, [key UTF8String], strlen([key UTF8String]));
89     
90     if (rc != SQLITE_OK) {
91         NSLog(@"error on rekey: %d", rc);
92         NSLog(@"%@", [self lastErrorMessage]);
93     }
94     
95     return (rc == SQLITE_OK);
98 - (BOOL) setKey:(NSString*)key {
99     if (!key) {
100         return NO;
101     }
102     
103     int rc = sqlite3_key(db, [key UTF8String], strlen([key UTF8String]));
104     
105     return (rc == SQLITE_OK);
108 #endif
110 - (BOOL) goodConnection {
111     
112     if (!db) {
113         return NO;
114     }
115     
116     FMResultSet *rs = [self executeQuery:@"select name from sqlite_master where type='table'"];
117     
118     if (rs) {
119         [rs close];
120         return YES;
121     }
122     
123     return NO;
126 - (void) compainAboutInUse {
127     NSLog(@"The FMDatabase %@ is currently in use.", self);
128     
129     if (crashOnErrors) {
130         *(long*)0 = 0xDEADBEEF;
131     }
134 - (NSString*) lastErrorMessage {
135     return [NSString stringWithUTF8String:sqlite3_errmsg(db)];
138 - (BOOL) hadError {
139     return ([self lastErrorCode] != SQLITE_OK);
142 - (int) lastErrorCode {
143     return sqlite3_errcode(db);
146 - (sqlite_int64) lastInsertRowId {
147     
148     if (inUse) {
149         [self compainAboutInUse];
150         return NO;
151     }
152     [self setInUse:YES];
153     
154     sqlite_int64 ret = sqlite3_last_insert_rowid(db);
155     
156     [self setInUse:NO];
157     
158     return ret;
161 - (void) bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt; {
162     
163     // FIXME - someday check the return codes on these binds.
164     if ([obj isKindOfClass:[NSData class]]) {
165         sqlite3_bind_blob(pStmt, idx, [obj bytes], [obj length], SQLITE_STATIC);
166     }
167     else if ([obj isKindOfClass:[NSDate class]]) {
168         sqlite3_bind_double(pStmt, idx, [obj timeIntervalSince1970]);
169     }
170     else if ([obj isKindOfClass:[NSNumber class]]) {
171         
172         if (strcmp([obj objCType], @encode(BOOL)) == 0) {
173             sqlite3_bind_int(pStmt, idx, ([obj boolValue] ? 1 : 0));
174         }
175         else if (strcmp([obj objCType], @encode(int)) == 0) {
176             sqlite3_bind_int64(pStmt, idx, [obj longValue]);
177         }
178         else if (strcmp([obj objCType], @encode(float)) == 0) {
179             sqlite3_bind_double(pStmt, idx, [obj floatValue]);
180         }
181         else if (strcmp([obj objCType], @encode(double)) == 0) {
182             sqlite3_bind_double(pStmt, idx, [obj doubleValue]);
183         }
184         else {
185             sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);
186         }
187     }
188     else {
189         sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);
190     }
193 - (id) executeQuery:(NSString*)objs, ... {
194     
195     if (inUse) {
196         [self compainAboutInUse];
197         return nil;
198     }
199     
200     [self setInUse:YES];
201     
202     FMResultSet *rs = nil;
203     
204     NSString *sql = objs;
205     int rc;
206     sqlite3_stmt *pStmt;
207     
208     if (traceExecution && sql) {
209         NSLog(@"%@ executeQuery: %@", self, sql);
210     }
211     
212     NSLog(@"sql: %@", sql);
213     
214     int numberOfRetries = 0;
215     BOOL retry;
216     do {
217         retry   = NO;
218         rc      = sqlite3_prepare(db, [sql UTF8String], -1, &pStmt, 0);
219         
220         if (SQLITE_BUSY == rc) {
221             retry = YES;
222             usleep(20);
223             
224             if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
225                 NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
226                 NSLog(@"Database busy");
227                 return nil;
228             }
229         }
230         else if (SQLITE_OK != rc) {
231             
232             rc = sqlite3_finalize(pStmt);
233             
234             if (logsErrors) {
235                 NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
236                 NSLog(@"DB Query: %@", sql);
237                 if (crashOnErrors) {
238 #ifdef __BIG_ENDIAN__
239                     asm{ trap };
240 #endif
241                     *(long*)0 = 0xDEADBEEF;
242                 }
243             }
244             
245             [self setInUse:NO];
246             return nil;
247         }
248     }
249     while (retry);
250     
251     id obj;
252     int idx = 0;
253     int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)
254     va_list argList;
255     va_start(argList, objs);
256     
257     while (idx < queryCount) {
258         obj = va_arg(argList, id);
259         
260         if (!obj) {
261             break;
262         }
263         
264         NSLog(@"obj: %@", obj);
265         
266         idx++;
267         
268         [self bindObject:obj toColumn:idx inStatement:pStmt];
269     }
270     
271     va_end(argList);
272     
273     if (idx != queryCount) {
274         NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)");
275         sqlite3_finalize(pStmt);
276         [self setInUse:NO];
277         return nil;
278     }
279     
280     // the statement gets close in rs's dealloc or [rs close];
281     rs = [FMResultSet resultSetWithStatement:pStmt usingParentDatabase:self];
282     [rs setQuery:sql];
283     
284     return rs;
288 - (BOOL) executeUpdate:(NSString*)objs, ... {
289     
290     if (inUse) {
291         [self compainAboutInUse];
292         return NO;
293     }
294     [self setInUse:YES];
295     
296     NSString *sql       = objs;
297     int rc              = 0x00;
298     sqlite3_stmt *pStmt = 0x00;
299     
300     if (traceExecution && sql) {
301         NSLog(@"%@ executeUpdate: %@", self, sql);
302     }
303     
304     int numberOfRetries = 0;
305     BOOL retry;
306     do {
307         retry   = NO;
308         rc      = sqlite3_prepare(db, [sql UTF8String], -1, &pStmt, 0);
309         if (SQLITE_BUSY == rc) {
310             retry = YES;
311             usleep(20);
312             if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
313                 NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
314                 NSLog(@"Database busy");
315                 return NO;
316             }
317         }
318         else if (SQLITE_OK != rc) {
319             int ret = rc;
320             rc = sqlite3_finalize(pStmt);
321             
322             if (logsErrors) {
323                 NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
324                 NSLog(@"DB Query: %@", sql);
325                 if (crashOnErrors) {
326                     #ifdef __BIG_ENDIAN__
327                     asm{ trap };
328                     #endif
329                     *(long*)0 = 0xDEADBEEF;
330                 }
331             }
332             
333             [self setInUse:NO];
334             return ret;
335         }
336     }
337     while (retry);
338     
339     
340     id obj;
341     int idx = 0;
342     int queryCount = sqlite3_bind_parameter_count(pStmt);
343     va_list argList;
344     va_start(argList, objs);
345     
346     while (idx < queryCount) {
347         
348         obj = va_arg(argList, id);
349         
350         if (!obj) {
351             break;
352         }
353         
354         idx++;
355         
356         [self bindObject:obj toColumn:idx inStatement:pStmt];
357     }
358     
359     va_end(argList);
360     
361     
362     if (idx != queryCount) {
363         NSLog(@"Error: the bind count is not correct for the # of variables (%@) (executeUpdate)", sql);
364         sqlite3_finalize(pStmt);
365         [self setInUse:NO];
366         return NO;
367     }
368     
369     /* Call sqlite3_step() to run the virtual machine. Since the SQL being
370     ** executed is not a SELECT statement, we assume no data will be returned.
371     */
372     numberOfRetries = 0;
373     do {
374         rc      = sqlite3_step(pStmt);
375         retry   = NO;
376         
377         if (SQLITE_BUSY == rc) {
378             // this will happen if the db is locked, like if we are doing an update or insert.
379             // in that case, retry the step... and maybe wait just 10 milliseconds.
380             retry = YES;
381             usleep(20);
382             if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
383                 NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
384                 NSLog(@"Database busy");
385                 return NO;
386             }
387         }
388         else if (SQLITE_DONE == rc || SQLITE_ROW == rc) {
389             // all is well, let's return.
390         }
391         else if (SQLITE_ERROR == rc) {
392             NSLog(@"Error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(db));
393             NSLog(@"DB Query: %@", sql);
394         }
395         else if (SQLITE_MISUSE == rc) {
396             // uh oh.
397             NSLog(@"Error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(db));
398             NSLog(@"DB Query: %@", sql);
399         }
400         else {
401             // wtf?
402             NSLog(@"Unknown error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(db));
403             NSLog(@"DB Query: %@", sql);
404         }
405         
406     } while (retry);
407     
408     assert( rc!=SQLITE_ROW );
409     
410     /* Finalize the virtual machine. This releases all memory and other
411     ** resources allocated by the sqlite3_prepare() call above.
412     */
413     rc = sqlite3_finalize(pStmt);
414     
415     [self setInUse:NO];
416     
417     return (rc == SQLITE_OK);
420 - (BOOL) rollback {
421     BOOL b = [self executeUpdate:@"ROLLBACK TRANSACTION;"];
422     if (b) {
423         inTransaction = NO;
424     }
425     return b;
428 - (BOOL) commit {
429     BOOL b =  [self executeUpdate:@"COMMIT TRANSACTION;"];
430     if (b) {
431         inTransaction = NO;
432     }
433     return b;
436 - (BOOL) beginDeferredTransaction {
437     BOOL b =  [self executeUpdate:@"BEGIN DEFERRED TRANSACTION;"];
438     if (b) {
439         inTransaction = YES;
440     }
441     return b;
444 - (BOOL) beginTransaction {
445     BOOL b =  [self executeUpdate:@"BEGIN EXCLUSIVE TRANSACTION;"];
446     if (b) {
447         inTransaction = YES;
448     }
449     return b;
452 - (BOOL)logsErrors {
453     return logsErrors;
455 - (void)setLogsErrors:(BOOL)flag {
456     logsErrors = flag;
459 - (BOOL)crashOnErrors {
460     return crashOnErrors;
462 - (void)setCrashOnErrors:(BOOL)flag {
463     crashOnErrors = flag;
466 - (BOOL)inUse {
467     return inUse || inTransaction;
469 - (void)setInUse:(BOOL)flag {
470     
471     inUse = flag;
474 - (BOOL)inTransaction {
475     return inTransaction;
477 - (void)setInTransaction:(BOOL)flag {
478     inTransaction = flag;
481 - (BOOL)traceExecution {
482     return traceExecution;
484 - (void)setTraceExecution:(BOOL)flag {
485     traceExecution = flag;
488 - (BOOL)checkedOut {
489     return checkedOut;
491 - (void)setCheckedOut:(BOOL)flag {
492     checkedOut = flag;
496 - (int)busyRetryTimeout {
497     return busyRetryTimeout;
499 - (void)setBusyRetryTimeout:(int)newBusyRetryTimeout {
500     busyRetryTimeout = newBusyRetryTimeout;
504 @end