Adding rudimentary locking to ITSQLite3Database to kill errors.
[ITFoundation.git] / ITSQLite3Database.m
1 #import "ITSQLite3Database.h"
2
3 int sqlite3_bind_objc_object(sqlite3_stmt *statement, int index, id object) {
4         int retval;
5         
6         if ([object isKindOfClass:[NSData class]]) {
7                 retval = sqlite3_bind_blob(statement, index, [object bytes], [object length], SQLITE_TRANSIENT);
8         } else if ([object isKindOfClass:[NSDate class]]) {
9                 retval = sqlite3_bind_double(statement, index, [object timeIntervalSince1970]);
10         } else if ([object isKindOfClass:[NSNull class]]) {
11                 retval = sqlite3_bind_null(statement, index);
12         } else if ([object isKindOfClass:[NSNumber class]]) {
13                 if (strcmp([object objCType], @encode(BOOL)) == 0) {
14                         retval = sqlite3_bind_int(statement, index, ([object boolValue] ? 1 : 0));
15                 } else if (strcmp([object objCType], @encode(int)) == 0) {
16                         retval = sqlite3_bind_int64(statement, index, [object longValue]);
17                 } else if (strcmp([object objCType], @encode(long)) == 0) {
18                         retval = sqlite3_bind_int64(statement, index, [object longValue]);
19                 } else if (strcmp([object objCType], @encode(float)) == 0) {
20                         retval = sqlite3_bind_double(statement, index, [object floatValue]);
21                 } else if (strcmp([object objCType], @encode(double)) == 0) {
22                         retval = sqlite3_bind_double(statement, index, [object doubleValue]);
23                 }
24         }
25         
26         if (!retval) {
27                 retval = sqlite3_bind_text(statement, index, [[object description] UTF8String], -1, SQLITE_TRANSIENT);
28         }
29         
30         return retval;
31 }
32
33 id sqlite3_column_objc_object(sqlite3_stmt *statement, int columnIndex) {
34         id retval;
35         
36         switch (sqlite3_column_type(statement, columnIndex)) {
37                 case SQLITE_INTEGER:
38                         retval = [NSNumber numberWithLongLong:sqlite3_column_int64(statement, columnIndex)];
39                         break;
40                 case SQLITE_FLOAT:
41                         retval = [NSNumber numberWithDouble:sqlite3_column_double(statement, columnIndex)];
42                         break;
43                 case SQLITE_TEXT:
44                         retval = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(statement, columnIndex)];
45                         break;
46                 case SQLITE_BLOB:
47                         retval = [NSData dataWithBytes:sqlite3_column_blob(statement, columnIndex) length:sqlite3_column_bytes(statement, columnIndex)];
48                         break;
49                 case SQLITE_NULL:
50                 default:
51                         retval = [NSNull null];
52                         break;
53         }
54         
55         return retval;
56 }
57
58 @implementation ITSQLite3Database
59
60 - (id)initWithPath:(NSString *)path {
61         if (self = [super init]) {
62                 dbPath = [path copy];
63                 if (sqlite3_open([dbPath UTF8String], &db) != SQLITE_OK) {
64                         ITDebugLog(@"%@ sqlite3_open(\"%@\"): %@", ITDebugErrorPrefixForObject(self), dbPath, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
65                         [self release];
66                         return nil;
67                 }
68                 dbLock = [[NSRecursiveLock alloc] init];
69         }
70         return self;
71 }
72
73 - (void)dealloc {
74         [dbLock release];
75         if (sqlite3_close(db) != SQLITE_OK) {
76                 ITDebugLog(@"%@ sqlite3_close(0x%x): %@", ITDebugErrorPrefixForObject(self), db, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
77         }
78         [dbPath release];
79         [super dealloc];
80 }
81
82 - (BOOL)begin {
83         return [self beginTransaction];
84 }
85
86 - (BOOL)beginTransaction {
87         return [self executeQuery:@"BEGIN TRANSACTION;"];
88 }
89
90 - (BOOL)commit {
91         return [self commitTransaction];
92 }
93
94 - (BOOL)commitTransaction {
95         return [self executeQuery:@"COMMIT TRANSACTION;"];
96 }
97
98 - (BOOL)rollback {
99         return [self rollbackTransaction];
100 }
101
102 - (BOOL)rollbackTransaction {
103         return [self executeQuery:@"ROLLBACK TRANSACTION;"];
104 }
105
106 - (BOOL)executeQuery:(NSString *)query va_args:(va_list)args {
107         sqlite3_stmt *statement;
108         
109         if (sqlite3_prepare(db, [query UTF8String], -1, &statement, 0) != SQLITE_OK) {
110                 ITDebugLog(@"%@ sqlite3_prepare(0x%x, \"%@\", -1, 0x%x, 0): %@", ITDebugErrorPrefixForObject(self), db, query, statement, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
111                 return NO;
112         }
113         
114         int argi, argc = sqlite3_bind_parameter_count(statement);
115         for (argi = 0; argi < argc; argi++) {
116                 id arg = va_arg(args, id);
117                 
118                 if (!arg) {
119                         [NSException raise:NSInvalidArgumentException format:@"ITSQLite3Database: -executeQuery expected %i arguments, received %i.", argc, argi];
120                         sqlite3_finalize(statement);
121                         return NO;
122                 }
123                 
124                 sqlite3_bind_objc_object(statement, argi+1, arg);
125         }
126         
127         int stepret = sqlite3_step(statement);
128         int finalizeret = sqlite3_finalize(statement);
129         if (!(stepret == SQLITE_DONE || stepret == SQLITE_ROW)) {
130                 ITDebugLog(@"%@ sqlite3_step(0x%x): %@", ITDebugErrorPrefixForObject(self), statement, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
131                 return NO;
132         }
133         
134         return YES;
135 }
136
137 - (BOOL)executeQuery:(NSString *)query, ... {
138         va_list args;
139         va_start(args, query);
140         
141         [dbLock lock];
142         BOOL result = [self executeQuery:query va_args:args];
143         [dbLock unlock];
144         
145         va_end(args);
146         return result;
147 }
148
149 - (NSDictionary *)fetchRow:(NSString *)query va_args:(va_list)args {
150         NSArray *table = [self fetchTable:query va_args:args];
151         if ([table count] >= 1) {
152                 return [table objectAtIndex:0];
153         }
154         return nil;
155 }
156
157 - (NSDictionary *)fetchRow:(NSString *)query, ... {
158         va_list args;
159         va_start(args, query);
160         
161         [dbLock lock];
162         id result = [self fetchRow:query va_args:args];
163         [dbLock unlock];
164         
165         va_end(args);
166         return result;
167 }
168
169 - (NSArray *)fetchTable:(NSString *)query va_args:(va_list)args {
170         sqlite3_stmt *statement;
171         
172         if (sqlite3_prepare(db, [query UTF8String], -1, &statement, 0) != SQLITE_OK) {
173                 ITDebugLog(@"%@ sqlite3_prepare(0x%x, \"%@\", -1, 0x%x, 0): %@", ITDebugErrorPrefixForObject(self), db, query, statement, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
174                 return NO;
175         }
176         
177         int argi, argc = sqlite3_bind_parameter_count(statement);
178         for (argi = 0; argi < argc; argi++) {
179                 id arg = va_arg(args, id);
180                 
181                 if (!arg) {
182                         [NSException raise:NSInvalidArgumentException format:@"ITSQLite3Database: -executeQuery expected %i arguments, received %i.", argc, argi];
183                         sqlite3_finalize(statement);
184                         return NO;
185                 }
186                 
187                 sqlite3_bind_objc_object(statement, argi+1, arg);
188         }
189         
190         NSMutableArray *rowArray = [[NSMutableArray alloc] init];
191         
192         int stepret;
193         while ((stepret = sqlite3_step(statement)) == SQLITE_ROW) {
194                 NSMutableDictionary *row = [[NSMutableDictionary alloc] init];
195                 int coli, cols = sqlite3_column_count(statement);
196                 for (coli = 0; coli < cols; coli++) {
197                         [row setObject:sqlite3_column_objc_object(statement, coli) forKey:[NSString stringWithUTF8String:sqlite3_column_name(statement, coli)]];
198                 }
199                 [rowArray addObject:[row autorelease]];
200         }
201         
202         int finalizeret = sqlite3_finalize(statement);
203         if (stepret != SQLITE_DONE) {
204                 ITDebugLog(@"%@ sqlite3_step(0x%x): %@", ITDebugErrorPrefixForObject(self), statement, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
205                 return NO;
206         }
207         
208         return [rowArray autorelease];
209 }
210
211 - (NSArray *)fetchTable:(NSString *)query, ... {
212         va_list args;
213         va_start(args, query);
214         
215         [dbLock lock];
216         id result = [self fetchTable:query va_args:args];
217         [dbLock unlock];
218         
219         va_end(args);
220         return result;
221 }
222
223 @end