]>
Commit | Line | Data |
---|---|---|
866f8763 A |
1 | /* |
2 | * Copyright (c) 2017 Apple Inc. All Rights Reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | ||
24 | #import "SFSQLite.h" | |
25 | #import "SFSQLiteStatement.h" | |
26 | #import "SFObjCType.h" | |
27 | ||
28 | @interface SFSQLiteStatement () | |
29 | @property (nonatomic, strong) NSMutableArray *temporaryBoundObjects; | |
30 | @end | |
31 | @implementation SFSQLiteStatement | |
32 | ||
33 | @synthesize SQLite = _SQLite; | |
34 | @synthesize SQL = _SQL; | |
35 | @synthesize handle = _handle; | |
36 | @synthesize reset = _reset; | |
37 | @synthesize temporaryBoundObjects = _temporaryBoundObjects; | |
38 | ||
39 | - (id)initWithSQLite:(SFSQLite *)SQLite SQL:(NSString *)SQL handle:(sqlite3_stmt *)handle { | |
40 | if ((self = [super init])) { | |
41 | _SQLite = SQLite; | |
42 | _SQL = SQL; | |
43 | _handle = handle; | |
44 | _reset = YES; | |
45 | } | |
46 | return self; | |
47 | } | |
48 | ||
49 | - (void)finalizeStatement { | |
50 | SFSQLite *strongSQLite = _SQLite; | |
51 | ||
52 | if (!_reset) { | |
53 | [strongSQLite raise: @"Statement not reset after last use: \"%@\"", _SQL]; | |
54 | } | |
55 | if (sqlite3_finalize(_handle)) { | |
56 | [strongSQLite raise:@"Error finalizing prepared statement: \"%@\"", _SQL]; | |
57 | } | |
58 | } | |
59 | ||
60 | - (void)resetAfterStepError | |
61 | { | |
62 | if (!_reset) { | |
63 | (void)sqlite3_reset(_handle); // we expect this to return an error | |
64 | (void)sqlite3_clear_bindings(_handle); | |
65 | [_temporaryBoundObjects removeAllObjects]; | |
66 | _reset = YES; | |
67 | } | |
68 | } | |
69 | ||
70 | - (BOOL)step { | |
71 | if (_reset) { | |
72 | _reset = NO; | |
73 | } | |
74 | ||
75 | int rc = sqlite3_step(_handle); | |
76 | if ((rc & 0x00FF) == SQLITE_ROW) { | |
77 | return YES; | |
78 | } else if ((rc & 0x00FF) == SQLITE_DONE) { | |
79 | return NO; | |
80 | } else { | |
81 | [self resetAfterStepError]; | |
82 | [_SQLite raise:@"Failed to step (%d): \"%@\"", rc, _SQL]; | |
83 | return NO; | |
84 | } | |
85 | } | |
86 | ||
87 | - (void)reset { | |
88 | SFSQLite *strongSQLite = _SQLite; | |
89 | ||
90 | if (!_reset) { | |
91 | if (sqlite3_reset(_handle)) { | |
92 | [strongSQLite raise:@"Error resetting prepared statement: \"%@\"", _SQL]; | |
93 | } | |
94 | ||
95 | if (sqlite3_clear_bindings(_handle)) { | |
96 | [strongSQLite raise:@"Error clearing prepared statement bindings: \"%@\"", _SQL]; | |
97 | } | |
98 | [_temporaryBoundObjects removeAllObjects]; | |
99 | _reset = YES; | |
100 | } | |
101 | } | |
102 | ||
103 | - (void)bindInt:(SInt32)value atIndex:(NSUInteger)index { | |
104 | NSAssert(_reset, @"Statement is not reset: \"%@\"", _SQL); | |
105 | ||
106 | if (sqlite3_bind_int(_handle, (int)index+1, value)) { | |
107 | [_SQLite raise:@"Error binding int at %ld: \"%@\"", (unsigned long)index, _SQL]; | |
108 | } | |
109 | } | |
110 | ||
111 | - (void)bindInt64:(SInt64)value atIndex:(NSUInteger)index { | |
112 | NSAssert(_reset, @"Statement is not reset: \"%@\"", _SQL); | |
113 | ||
114 | if (sqlite3_bind_int64(_handle, (int)index+1, value)) { | |
115 | [_SQLite raise:@"Error binding int64 at %ld: \"%@\"", (unsigned long)index, _SQL]; | |
116 | } | |
117 | } | |
118 | ||
119 | - (void)bindDouble:(double)value atIndex:(NSUInteger)index { | |
120 | NSAssert(_reset, @"Statement is not reset: \"%@\"", _SQL); | |
121 | ||
122 | if (sqlite3_bind_double(_handle, (int)index+1, value)) { | |
123 | [_SQLite raise:@"Error binding double at %ld: \"%@\"", (unsigned long)index, _SQL]; | |
124 | } | |
125 | } | |
126 | ||
127 | - (void)bindBlob:(NSData *)value atIndex:(NSUInteger)index { | |
128 | NSAssert(_reset, @"Statement is not reset: \"%@\"", _SQL); | |
129 | ||
130 | if (value) { | |
131 | NS_VALID_UNTIL_END_OF_SCOPE NSData *arcSafeValue = value; | |
132 | if (sqlite3_bind_blob(_handle, (int)index+1, [arcSafeValue bytes], (int)[arcSafeValue length], NULL)) { | |
133 | [_SQLite raise:@"Error binding blob at %ld: \"%@\"", (unsigned long)index, _SQL]; | |
134 | } | |
135 | } else { | |
136 | [self bindNullAtIndex:index]; | |
137 | } | |
138 | } | |
139 | ||
140 | - (void)bindText:(NSString *)value atIndex:(NSUInteger)index { | |
141 | NSAssert(_reset, @"Statement is not reset: \"%@\"", _SQL); | |
142 | ||
143 | if (value) { | |
144 | NS_VALID_UNTIL_END_OF_SCOPE NSString *arcSafeValue = value; | |
145 | if (sqlite3_bind_text(_handle, (int)index+1, [arcSafeValue UTF8String], -1, NULL)) { | |
146 | [_SQLite raise:@"Error binding text at %ld: \"%@\"", (unsigned long)index, _SQL]; | |
147 | } | |
148 | } else { | |
149 | [self bindNullAtIndex:index]; | |
150 | } | |
151 | } | |
152 | ||
153 | - (void)bindNullAtIndex:(NSUInteger)index { | |
154 | int rc = sqlite3_bind_null(_handle, (int)index+1); | |
155 | if ((rc & 0x00FF) != SQLITE_OK) { | |
156 | [_SQLite raise:@"sqlite3_bind_null error"]; | |
157 | } | |
158 | } | |
159 | ||
160 | - (id)retainedTemporaryBoundObject:(id)object | |
161 | { | |
162 | if (!_temporaryBoundObjects) { | |
163 | _temporaryBoundObjects = [NSMutableArray new]; | |
164 | } | |
165 | [_temporaryBoundObjects addObject:object]; | |
166 | return object; | |
167 | } | |
168 | ||
169 | - (void)bindValue:(id)value atIndex:(NSUInteger)index { | |
170 | if ([value isKindOfClass:[NSNumber class]]) { | |
171 | SFObjCType *type = [SFObjCType typeForValue:value]; | |
172 | if (type.isIntegerNumber) { | |
173 | if (type.size <= 4) { | |
174 | [self bindInt:[value intValue] atIndex:index]; | |
175 | } else { | |
176 | [self bindInt64:[value longLongValue] atIndex:index]; | |
177 | } | |
178 | } else { | |
179 | NSAssert(type.isFloatingPointNumber, @"Expected number type to be either integer or floating point"); | |
180 | if (type.code == SFObjCTypeFloat) { | |
181 | [self bindInt:[value intValue] atIndex:index]; | |
182 | } else { | |
183 | NSAssert(type.code == SFObjCTypeDouble, @"Unexpected floating point number type: %@", type); | |
184 | [self bindInt64:[value longLongValue] atIndex:index]; | |
185 | } | |
186 | } | |
187 | } else if ([value isKindOfClass:[NSData class]]) { | |
188 | [self bindBlob:value atIndex:index]; | |
189 | } else if ([value isKindOfClass:[NSUUID class]]) { | |
190 | uuid_t uuid; | |
191 | [(NSUUID *)value getUUIDBytes:uuid]; | |
192 | [self bindBlob:[self retainedTemporaryBoundObject:[NSData dataWithBytes:uuid length:sizeof(uuid_t)]] atIndex:index]; | |
193 | } else if ([value isKindOfClass:[NSString class]]) { | |
194 | [self bindText:value atIndex:index]; | |
195 | } else if ([value isKindOfClass:[NSNull class]]) { | |
196 | [self bindNullAtIndex:index]; | |
197 | } else if ([value isKindOfClass:[NSDate class]]) { | |
198 | [self bindDouble:[(NSDate *)value timeIntervalSinceReferenceDate] atIndex:index]; | |
199 | } else if ([value isKindOfClass:[NSError class]]) { | |
200 | [self bindBlob:[self retainedTemporaryBoundObject:[NSKeyedArchiver archivedDataWithRootObject:value]] atIndex:index]; | |
201 | } else if ([value isKindOfClass:[NSURL class]]) { | |
202 | [self bindText:[self retainedTemporaryBoundObject:[value absoluteString]] atIndex:index]; | |
203 | } else { | |
204 | [NSException raise:NSInvalidArgumentException format:@"Can't bind object of type %@", [value class]]; | |
205 | } | |
206 | } | |
207 | ||
208 | - (void)bindValues:(NSArray *)values { | |
209 | for (NSUInteger i = 0; i < values.count; i++) { | |
210 | [self bindValue:values[i] atIndex:i]; | |
211 | } | |
212 | } | |
213 | ||
214 | - (NSUInteger)columnCount { | |
215 | NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); | |
216 | ||
217 | return sqlite3_column_count(_handle); | |
218 | } | |
219 | ||
220 | - (int)columnTypeAtIndex:(NSUInteger)index { | |
221 | NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); | |
222 | ||
223 | return sqlite3_column_type(_handle, (int)index); | |
224 | } | |
225 | ||
226 | - (NSString *)columnNameAtIndex:(NSUInteger)index { | |
227 | NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); | |
228 | ||
229 | return @(sqlite3_column_name(_handle, (int)index)); | |
230 | } | |
231 | ||
232 | - (SInt32)intAtIndex:(NSUInteger)index { | |
233 | NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); | |
234 | ||
235 | return sqlite3_column_int(_handle, (int)index); | |
236 | } | |
237 | ||
238 | - (SInt64)int64AtIndex:(NSUInteger)index { | |
239 | NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); | |
240 | ||
241 | return sqlite3_column_int64(_handle, (int)index); | |
242 | } | |
243 | ||
244 | - (double)doubleAtIndex:(NSUInteger)index { | |
245 | NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); | |
246 | ||
247 | return sqlite3_column_double(_handle, (int)index); | |
248 | } | |
249 | ||
250 | - (NSData *)blobAtIndex:(NSUInteger)index { | |
251 | NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); | |
252 | ||
253 | const void *bytes = sqlite3_column_blob(_handle, (int)index); | |
254 | if (bytes) { | |
255 | int length = sqlite3_column_bytes(_handle, (int)index); | |
256 | return [NSData dataWithBytes:bytes length:length]; | |
257 | } else { | |
258 | return nil; | |
259 | } | |
260 | } | |
261 | ||
262 | - (NSString *)textAtIndex:(NSUInteger)index { | |
263 | NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); | |
264 | ||
265 | const char *text = (const char *)sqlite3_column_text(_handle, (int)index); | |
266 | if (text) { | |
267 | return @(text); | |
268 | } else { | |
269 | return nil; | |
270 | } | |
271 | } | |
272 | ||
273 | - (id)objectAtIndex:(NSUInteger)index { | |
274 | int type = [self columnTypeAtIndex:index]; | |
275 | switch (type) { | |
276 | case SQLITE_INTEGER: | |
277 | return @([self int64AtIndex:index]); | |
278 | ||
279 | case SQLITE_FLOAT: | |
280 | return @([self doubleAtIndex:index]); | |
281 | ||
282 | case SQLITE_TEXT: | |
283 | return [self textAtIndex:index]; | |
284 | ||
285 | case SQLITE_BLOB: | |
286 | return [self blobAtIndex:index]; | |
287 | ||
288 | case SQLITE_NULL: | |
289 | return nil; | |
290 | ||
291 | default: | |
292 | [NSException raise:NSGenericException format:@"Unexpected column type: %d", type]; | |
293 | return nil; | |
294 | } | |
295 | } | |
296 | ||
297 | - (NSArray *)allObjects { | |
298 | NSUInteger columnCount = [self columnCount]; | |
299 | NSMutableArray *objects = [NSMutableArray arrayWithCapacity:columnCount]; | |
300 | for (NSUInteger i = 0; i < columnCount; i++) { | |
301 | objects[i] = [self objectAtIndex:i] ?: [NSNull null]; | |
302 | } | |
303 | return objects; | |
304 | } | |
305 | ||
306 | - (NSDictionary *)allObjectsByColumnName { | |
307 | NSUInteger columnCount = [self columnCount]; | |
308 | NSMutableDictionary *objectsByColumnName = [NSMutableDictionary dictionaryWithCapacity:columnCount]; | |
309 | for (NSUInteger i = 0; i < columnCount; i++) { | |
310 | NSString *columnName = [self columnNameAtIndex:i]; | |
311 | id object = [self objectAtIndex:i]; | |
312 | if (object) { | |
313 | objectsByColumnName[columnName] = object; | |
314 | } | |
315 | } | |
316 | return objectsByColumnName; | |
317 | } | |
318 | ||
319 | @end |