Enabling garbage collection support.
[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 @interface ITSQLite3Database (Internals)
59 - (BOOL)executeQuery:(NSString *)query va_args:(va_list)args;
60 - (NSDictionary *)fetchRow:(NSString *)query va_args:(va_list)args;
61 - (NSArray *)fetchTable:(NSString *)query va_args:(va_list)args;
62 @end
63
64 @implementation ITSQLite3Database
65
66 - (id)initWithPath:(NSString *)path {
67         if (self = [super init]) {
68                 dbPath = [path copy];
69                 if (sqlite3_open([dbPath UTF8String], &db) != SQLITE_OK) {
70                         ITDebugLog(@"%@ sqlite3_open(\"%@\"): %@", ITDebugErrorPrefixForObject(self), dbPath, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
71                         [self release];
72                         return nil;
73                 }
74                 dbLock = [[NSRecursiveLock alloc] init];
75         }
76         return self;
77 }
78
79 - (void)dealloc {
80         [dbLock release];
81         if (sqlite3_close(db) != SQLITE_OK) {
82                 ITDebugLog(@"%@ sqlite3_close(0x%x): %@", ITDebugErrorPrefixForObject(self), db, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
83         }
84         [dbPath release];
85         [super dealloc];
86 }
87
88 - (BOOL)begin {
89         return [self beginTransaction];
90 }
91
92 - (BOOL)beginTransaction {
93         return [self executeQuery:@"BEGIN TRANSACTION;"];
94 }
95
96 - (BOOL)commit {
97         return [self commitTransaction];
98 }
99
100 - (BOOL)commitTransaction {
101         return [self executeQuery:@"COMMIT TRANSACTION;"];
102 }
103
104 - (BOOL)rollback {
105         return [self rollbackTransaction];
106 }
107
108 - (BOOL)rollbackTransaction {
109         return [self executeQuery:@"ROLLBACK TRANSACTION;"];
110 }
111
112 - (BOOL)executeQuery:(NSString *)query va_args:(va_list)args {
113         sqlite3_stmt *statement;
114         
115         if (sqlite3_prepare(db, [query UTF8String], -1, &statement, 0) != SQLITE_OK) {
116                 ITDebugLog(@"%@ sqlite3_prepare(0x%x, \"%@\", -1, 0x%x, 0): %@", ITDebugErrorPrefixForObject(self), db, query, statement, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
117                 return NO;
118         }
119         
120         int argi, argc = sqlite3_bind_parameter_count(statement);
121         for (argi = 0; argi < argc; argi++) {
122                 id arg = va_arg(args, id);
123                 
124                 if (!arg) {
125                         [NSException raise:NSInvalidArgumentException format:@"ITSQLite3Database: -executeQuery expected %i arguments, received %i.", argc, argi];
126                         sqlite3_finalize(statement);
127                         return NO;
128                 }
129                 
130                 sqlite3_bind_objc_object(statement, argi+1, arg);
131         }
132         
133         int stepret = sqlite3_step(statement);
134         int finalizeret = sqlite3_finalize(statement);
135         if (!(stepret == SQLITE_DONE || stepret == SQLITE_ROW)) {
136                 ITDebugLog(@"%@ sqlite3_step(0x%x): %@", ITDebugErrorPrefixForObject(self), statement, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
137                 return NO;
138         }
139         
140         return YES;
141 }
142
143 - (BOOL)executeQuery:(NSString *)query, ... {
144         va_list args;
145         va_start(args, query);
146         
147         [dbLock lock];
148         BOOL result = [self executeQuery:query va_args:args];
149         [dbLock unlock];
150         
151         va_end(args);
152         return result;
153 }
154
155 - (NSDictionary *)fetchRow:(NSString *)query va_args:(va_list)args {
156         NSArray *table = [self fetchTable:query va_args:args];
157         if ([table count] >= 1) {
158                 return [table objectAtIndex:0];
159         }
160         return nil;
161 }
162
163 - (NSDictionary *)fetchRow:(NSString *)query, ... {
164         va_list args;
165         va_start(args, query);
166         
167         [dbLock lock];
168         NSDictionary *result = [self fetchRow:query va_args:args];
169         [dbLock unlock];
170         
171         va_end(args);
172         return result;
173 }
174
175 - (NSArray *)fetchTable:(NSString *)query va_args:(va_list)args {
176         sqlite3_stmt *statement;
177         
178         if (sqlite3_prepare(db, [query UTF8String], -1, &statement, 0) != SQLITE_OK) {
179                 ITDebugLog(@"%@ sqlite3_prepare(0x%x, \"%@\", -1, 0x%x, 0): %@", ITDebugErrorPrefixForObject(self), db, query, statement, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
180                 return NO;
181         }
182         
183         int argi, argc = sqlite3_bind_parameter_count(statement);
184         for (argi = 0; argi < argc; argi++) {
185                 id arg = va_arg(args, id);
186                 
187                 if (!arg) {
188                         [NSException raise:NSInvalidArgumentException format:@"ITSQLite3Database: -executeQuery expected %i arguments, received %i.", argc, argi];
189                         sqlite3_finalize(statement);
190                         return NO;
191                 }
192                 
193                 sqlite3_bind_objc_object(statement, argi+1, arg);
194         }
195         
196         NSMutableArray *rowArray = [[NSMutableArray alloc] init];
197         
198         int stepret;
199         while ((stepret = sqlite3_step(statement)) == SQLITE_ROW) {
200                 NSMutableDictionary *row = [[NSMutableDictionary alloc] init];
201                 int coli, cols = sqlite3_column_count(statement);
202                 for (coli = 0; coli < cols; coli++) {
203                         [row setObject:sqlite3_column_objc_object(statement, coli) forKey:[NSString stringWithUTF8String:sqlite3_column_name(statement, coli)]];
204                 }
205                 [rowArray addObject:[row autorelease]];
206         }
207         
208         int finalizeret = sqlite3_finalize(statement);
209         if (stepret != SQLITE_DONE) {
210                 ITDebugLog(@"%@ sqlite3_step(0x%x): %@", ITDebugErrorPrefixForObject(self), statement, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
211                 return NO;
212         }
213         
214         return [rowArray autorelease];
215 }
216
217 - (NSArray *)fetchTable:(NSString *)query, ... {
218         va_list args;
219         va_start(args, query);
220         
221         [dbLock lock];
222         NSArray *result = [self fetchTable:query va_args:args];
223         [dbLock unlock];
224         
225         va_end(args);
226         return result;
227 }
228
229 - (id)fetchRowColumn:(NSString *)query, ... {
230         va_list args;
231         va_start(args, query);
232         
233         [dbLock lock];
234         NSDictionary *result = [self fetchRow:query va_args:args];
235         [dbLock unlock];
236         
237         va_end(args);
238         
239         if (result) {
240                 if ([[result allKeys] count] >= 1) {
241                         return [result objectForKey:[[result allKeys] objectAtIndex:0]];
242                 }
243         }
244         
245         return nil;
246 }
247
248 - (NSArray *)fetchTableColumn:(NSString *)query, ... {
249         va_list args;
250         va_start(args, query);
251         
252         [dbLock lock];
253         NSArray *result = [self fetchTable:query va_args:args];
254         [dbLock unlock];
255         
256         va_end(args);
257         
258         if (result) {
259                 NSMutableArray *columnArray = [[NSMutableArray alloc] init];
260                 NSEnumerator *enumerator = [result objectEnumerator];
261                 NSDictionary *row;
262                 while (row = (NSDictionary *)[enumerator nextObject]) {
263                         [columnArray addObject:[row objectForKey:[[row allKeys] objectAtIndex:0]]];
264                 }
265                 return [columnArray autorelease];
266         }
267         
268         return nil;
269 }
270
271 @end