Updating ITFoundation to include updates spurred by Haven development.
[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         }
69         return self;
70 }
71
72 - (void)dealloc {
73         if (sqlite3_close(db) != SQLITE_OK) {
74                 ITDebugLog(@"%@ sqlite3_close(0x%x): %@", ITDebugErrorPrefixForObject(self), db, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
75         }
76         [dbPath release];
77         [super dealloc];
78 }
79
80 - (BOOL)begin {
81         return [self beginTransaction];
82 }
83
84 - (BOOL)beginTransaction {
85         return [self executeQuery:@"BEGIN TRANSACTION;"];
86 }
87
88 - (BOOL)commit {
89         return [self commitTransaction];
90 }
91
92 - (BOOL)commitTransaction {
93         return [self executeQuery:@"COMMIT TRANSACTION;"];
94 }
95
96 - (BOOL)rollback {
97         return [self rollbackTransaction];
98 }
99
100 - (BOOL)rollbackTransaction {
101         return [self executeQuery:@"ROLLBACK TRANSACTION;"];
102 }
103
104 - (BOOL)executeQuery:(NSString *)query va_args:(va_list)args {
105         sqlite3_stmt *statement;
106         
107         if (sqlite3_prepare(db, [query UTF8String], -1, &statement, 0) != SQLITE_OK) {
108                 ITDebugLog(@"%@ sqlite3_prepare(0x%x, \"%@\", -1, 0x%x, 0): %@", ITDebugErrorPrefixForObject(self), db, query, statement, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
109                 return NO;
110         }
111         
112         int argi, argc = sqlite3_bind_parameter_count(statement);
113         for (argi = 0; argi < argc; argi++) {
114                 id arg = va_arg(args, id);
115                 
116                 if (!arg) {
117                         [NSException raise:NSInvalidArgumentException format:@"ITSQLite3Database: -executeQuery expected %i arguments, received %i.", argc, argi];
118                         sqlite3_finalize(statement);
119                         return NO;
120                 }
121                 
122                 sqlite3_bind_objc_object(statement, argi+1, arg);
123         }
124         
125         int stepret = sqlite3_step(statement);
126         int finalizeret = sqlite3_finalize(statement);
127         if (!(stepret == SQLITE_DONE || stepret == SQLITE_ROW)) {
128                 ITDebugLog(@"%@ sqlite3_step(0x%x): %@", ITDebugErrorPrefixForObject(self), statement, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
129                 return NO;
130         }
131         
132         return YES;
133 }
134
135 - (BOOL)executeQuery:(NSString *)query, ... {
136         va_list args;
137         va_start(args, query);
138         
139         BOOL result = [self executeQuery:query va_args:args];
140         
141         va_end(args);
142         return result;
143 }
144
145 - (NSDictionary *)fetchRow:(NSString *)query va_args:(va_list)args {
146         NSArray *table = [self fetchTable:query va_args:args];
147         if ([table count] >= 1) {
148                 return [table objectAtIndex:0];
149         }
150         return nil;
151 }
152
153 - (NSDictionary *)fetchRow:(NSString *)query, ... {
154         va_list args;
155         va_start(args, query);
156         
157         id result = [self fetchRow:query va_args:args];
158         
159         va_end(args);
160         return result;
161 }
162
163 - (NSArray *)fetchTable:(NSString *)query va_args:(va_list)args {
164         sqlite3_stmt *statement;
165         
166         if (sqlite3_prepare(db, [query UTF8String], -1, &statement, 0) != SQLITE_OK) {
167                 ITDebugLog(@"%@ sqlite3_prepare(0x%x, \"%@\", -1, 0x%x, 0): %@", ITDebugErrorPrefixForObject(self), db, query, statement, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
168                 return NO;
169         }
170         
171         int argi, argc = sqlite3_bind_parameter_count(statement);
172         for (argi = 0; argi < argc; argi++) {
173                 id arg = va_arg(args, id);
174                 
175                 if (!arg) {
176                         [NSException raise:NSInvalidArgumentException format:@"ITSQLite3Database: -executeQuery expected %i arguments, received %i.", argc, argi];
177                         sqlite3_finalize(statement);
178                         return NO;
179                 }
180                 
181                 sqlite3_bind_objc_object(statement, argi+1, arg);
182         }
183         
184         NSMutableArray *rowArray = [[NSMutableArray alloc] init];
185         
186         int stepret;
187         while ((stepret = sqlite3_step(statement)) == SQLITE_ROW) {
188                 NSMutableDictionary *row = [[NSMutableDictionary alloc] init];
189                 int coli, cols = sqlite3_column_count(statement);
190                 for (coli = 0; coli < cols; coli++) {
191                         [row setObject:sqlite3_column_objc_object(statement, coli) forKey:[NSString stringWithUTF8String:sqlite3_column_name(statement, coli)]];
192                 }
193                 [rowArray addObject:[row autorelease]];
194         }
195         
196         int finalizeret = sqlite3_finalize(statement);
197         if (stepret != SQLITE_DONE) {
198                 ITDebugLog(@"%@ sqlite3_step(0x%x): %@", ITDebugErrorPrefixForObject(self), statement, [NSString stringWithUTF8String:sqlite3_errmsg(db)]);
199                 return NO;
200         }
201         
202         return [rowArray autorelease];
203 }
204
205 - (NSArray *)fetchTable:(NSString *)query, ... {
206         va_list args;
207         va_start(args, query);
208         
209         id result = [self fetchTable:query va_args:args];
210         
211         va_end(args);
212         return result;
213 }
214
215 @end