]>
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" | |
8a50f688 | 27 | #import "debugging.h" |
866f8763 A |
28 | |
29 | @interface SFSQLiteStatement () | |
30 | @property (nonatomic, strong) NSMutableArray *temporaryBoundObjects; | |
31 | @end | |
32 | @implementation SFSQLiteStatement | |
33 | ||
34 | @synthesize SQLite = _SQLite; | |
35 | @synthesize SQL = _SQL; | |
36 | @synthesize handle = _handle; | |
37 | @synthesize reset = _reset; | |
38 | @synthesize temporaryBoundObjects = _temporaryBoundObjects; | |
39 | ||
40 | - (id)initWithSQLite:(SFSQLite *)SQLite SQL:(NSString *)SQL handle:(sqlite3_stmt *)handle { | |
41 | if ((self = [super init])) { | |
42 | _SQLite = SQLite; | |
43 | _SQL = SQL; | |
44 | _handle = handle; | |
45 | _reset = YES; | |
46 | } | |
47 | return self; | |
48 | } | |
49 | ||
50 | - (void)finalizeStatement { | |
866f8763 | 51 | if (!_reset) { |
8a50f688 A |
52 | secerror("sfsqlite: Statement not reset after last use: \"%@\"", _SQL); |
53 | return; | |
866f8763 A |
54 | } |
55 | if (sqlite3_finalize(_handle)) { | |
8a50f688 A |
56 | secerror("sfsqlite: Error finalizing prepared statement: \"%@\"", _SQL); |
57 | return; | |
866f8763 A |
58 | } |
59 | } | |
60 | ||
61 | - (void)resetAfterStepError | |
62 | { | |
63 | if (!_reset) { | |
64 | (void)sqlite3_reset(_handle); // we expect this to return an error | |
65 | (void)sqlite3_clear_bindings(_handle); | |
66 | [_temporaryBoundObjects removeAllObjects]; | |
67 | _reset = YES; | |
68 | } | |
69 | } | |
70 | ||
71 | - (BOOL)step { | |
72 | if (_reset) { | |
73 | _reset = NO; | |
74 | } | |
75 | ||
76 | int rc = sqlite3_step(_handle); | |
77 | if ((rc & 0x00FF) == SQLITE_ROW) { | |
78 | return YES; | |
79 | } else if ((rc & 0x00FF) == SQLITE_DONE) { | |
80 | return NO; | |
81 | } else { | |
82 | [self resetAfterStepError]; | |
8a50f688 | 83 | secerror("sfsqlite: Failed to step (%d): \"%@\"", rc, _SQL); |
866f8763 A |
84 | return NO; |
85 | } | |
86 | } | |
87 | ||
88 | - (void)reset { | |
866f8763 A |
89 | if (!_reset) { |
90 | if (sqlite3_reset(_handle)) { | |
8a50f688 A |
91 | secerror("sfsqlite: Error resetting prepared statement: \"%@\"", _SQL); |
92 | return; | |
866f8763 A |
93 | } |
94 | ||
95 | if (sqlite3_clear_bindings(_handle)) { | |
8a50f688 A |
96 | secerror("sfsqlite: Error clearing prepared statement bindings: \"%@\"", _SQL); |
97 | return; | |
866f8763 A |
98 | } |
99 | [_temporaryBoundObjects removeAllObjects]; | |
100 | _reset = YES; | |
101 | } | |
102 | } | |
103 | ||
104 | - (void)bindInt:(SInt32)value atIndex:(NSUInteger)index { | |
8a50f688 A |
105 | if (!_reset) { |
106 | secerror("sfsqlite: Statement is not reset: \"%@\"", _SQL); | |
107 | return; | |
108 | } | |
866f8763 A |
109 | |
110 | if (sqlite3_bind_int(_handle, (int)index+1, value)) { | |
8a50f688 A |
111 | secerror("sfsqlite: Error binding int at %ld: \"%@\"", (unsigned long)index, _SQL); |
112 | return; | |
866f8763 A |
113 | } |
114 | } | |
115 | ||
116 | - (void)bindInt64:(SInt64)value atIndex:(NSUInteger)index { | |
8a50f688 A |
117 | if (!_reset) { |
118 | secerror("sfsqlite: Statement is not reset: \"%@\"", _SQL); | |
119 | return; | |
120 | } | |
866f8763 A |
121 | |
122 | if (sqlite3_bind_int64(_handle, (int)index+1, value)) { | |
8a50f688 A |
123 | secerror("sfsqlite: Error binding int64 at %ld: \"%@\"", (unsigned long)index, _SQL); |
124 | return; | |
866f8763 A |
125 | } |
126 | } | |
127 | ||
128 | - (void)bindDouble:(double)value atIndex:(NSUInteger)index { | |
8a50f688 A |
129 | if (!_reset) { |
130 | secerror("sfsqlite: Statement is not reset: \"%@\"", _SQL); | |
131 | return; | |
132 | } | |
866f8763 A |
133 | |
134 | if (sqlite3_bind_double(_handle, (int)index+1, value)) { | |
8a50f688 A |
135 | secerror("sfsqlite: Error binding double at %ld: \"%@\"", (unsigned long)index, _SQL); |
136 | return; | |
866f8763 A |
137 | } |
138 | } | |
139 | ||
140 | - (void)bindBlob:(NSData *)value atIndex:(NSUInteger)index { | |
8a50f688 A |
141 | if (!_reset) { |
142 | secerror("sfsqlite: Statement is not reset: \"%@\"", _SQL); | |
143 | return; | |
144 | } | |
866f8763 A |
145 | |
146 | if (value) { | |
147 | NS_VALID_UNTIL_END_OF_SCOPE NSData *arcSafeValue = value; | |
148 | if (sqlite3_bind_blob(_handle, (int)index+1, [arcSafeValue bytes], (int)[arcSafeValue length], NULL)) { | |
8a50f688 A |
149 | secerror("sfsqlite: Error binding blob at %ld: \"%@\"", (unsigned long)index, _SQL); |
150 | return; | |
866f8763 A |
151 | } |
152 | } else { | |
153 | [self bindNullAtIndex:index]; | |
154 | } | |
155 | } | |
156 | ||
157 | - (void)bindText:(NSString *)value atIndex:(NSUInteger)index { | |
8a50f688 A |
158 | if (!_reset) { |
159 | secerror("sfsqlite: Statement is not reset: \"%@\"", _SQL); | |
160 | return; | |
161 | } | |
162 | ||
866f8763 A |
163 | if (value) { |
164 | NS_VALID_UNTIL_END_OF_SCOPE NSString *arcSafeValue = value; | |
165 | if (sqlite3_bind_text(_handle, (int)index+1, [arcSafeValue UTF8String], -1, NULL)) { | |
8a50f688 A |
166 | secerror("sfsqlite: Error binding text at %ld: \"%@\"", (unsigned long)index, _SQL); |
167 | return; | |
866f8763 A |
168 | } |
169 | } else { | |
170 | [self bindNullAtIndex:index]; | |
171 | } | |
172 | } | |
173 | ||
174 | - (void)bindNullAtIndex:(NSUInteger)index { | |
175 | int rc = sqlite3_bind_null(_handle, (int)index+1); | |
176 | if ((rc & 0x00FF) != SQLITE_OK) { | |
8a50f688 A |
177 | secerror("sfsqlite: sqlite3_bind_null error"); |
178 | return; | |
866f8763 A |
179 | } |
180 | } | |
181 | ||
182 | - (id)retainedTemporaryBoundObject:(id)object | |
183 | { | |
184 | if (!_temporaryBoundObjects) { | |
185 | _temporaryBoundObjects = [NSMutableArray new]; | |
186 | } | |
187 | [_temporaryBoundObjects addObject:object]; | |
188 | return object; | |
189 | } | |
190 | ||
191 | - (void)bindValue:(id)value atIndex:(NSUInteger)index { | |
192 | if ([value isKindOfClass:[NSNumber class]]) { | |
193 | SFObjCType *type = [SFObjCType typeForValue:value]; | |
194 | if (type.isIntegerNumber) { | |
195 | if (type.size <= 4) { | |
196 | [self bindInt:[value intValue] atIndex:index]; | |
197 | } else { | |
198 | [self bindInt64:[value longLongValue] atIndex:index]; | |
199 | } | |
200 | } else { | |
201 | NSAssert(type.isFloatingPointNumber, @"Expected number type to be either integer or floating point"); | |
202 | if (type.code == SFObjCTypeFloat) { | |
203 | [self bindInt:[value intValue] atIndex:index]; | |
204 | } else { | |
205 | NSAssert(type.code == SFObjCTypeDouble, @"Unexpected floating point number type: %@", type); | |
206 | [self bindInt64:[value longLongValue] atIndex:index]; | |
207 | } | |
208 | } | |
209 | } else if ([value isKindOfClass:[NSData class]]) { | |
210 | [self bindBlob:value atIndex:index]; | |
211 | } else if ([value isKindOfClass:[NSUUID class]]) { | |
212 | uuid_t uuid; | |
213 | [(NSUUID *)value getUUIDBytes:uuid]; | |
214 | [self bindBlob:[self retainedTemporaryBoundObject:[NSData dataWithBytes:uuid length:sizeof(uuid_t)]] atIndex:index]; | |
215 | } else if ([value isKindOfClass:[NSString class]]) { | |
216 | [self bindText:value atIndex:index]; | |
217 | } else if ([value isKindOfClass:[NSNull class]]) { | |
218 | [self bindNullAtIndex:index]; | |
219 | } else if ([value isKindOfClass:[NSDate class]]) { | |
220 | [self bindDouble:[(NSDate *)value timeIntervalSinceReferenceDate] atIndex:index]; | |
221 | } else if ([value isKindOfClass:[NSError class]]) { | |
222 | [self bindBlob:[self retainedTemporaryBoundObject:[NSKeyedArchiver archivedDataWithRootObject:value]] atIndex:index]; | |
223 | } else if ([value isKindOfClass:[NSURL class]]) { | |
224 | [self bindText:[self retainedTemporaryBoundObject:[value absoluteString]] atIndex:index]; | |
225 | } else { | |
8a50f688 A |
226 | secerror("sfsqlite: Can't bind object of type %@", [value class]); |
227 | return; | |
866f8763 A |
228 | } |
229 | } | |
230 | ||
231 | - (void)bindValues:(NSArray *)values { | |
232 | for (NSUInteger i = 0; i < values.count; i++) { | |
233 | [self bindValue:values[i] atIndex:i]; | |
234 | } | |
235 | } | |
236 | ||
237 | - (NSUInteger)columnCount { | |
238 | NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); | |
239 | ||
240 | return sqlite3_column_count(_handle); | |
241 | } | |
242 | ||
243 | - (int)columnTypeAtIndex:(NSUInteger)index { | |
244 | NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); | |
245 | ||
246 | return sqlite3_column_type(_handle, (int)index); | |
247 | } | |
248 | ||
249 | - (NSString *)columnNameAtIndex:(NSUInteger)index { | |
250 | NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); | |
251 | ||
252 | return @(sqlite3_column_name(_handle, (int)index)); | |
253 | } | |
254 | ||
255 | - (SInt32)intAtIndex:(NSUInteger)index { | |
256 | NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); | |
257 | ||
258 | return sqlite3_column_int(_handle, (int)index); | |
259 | } | |
260 | ||
261 | - (SInt64)int64AtIndex:(NSUInteger)index { | |
262 | NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); | |
263 | ||
264 | return sqlite3_column_int64(_handle, (int)index); | |
265 | } | |
266 | ||
267 | - (double)doubleAtIndex:(NSUInteger)index { | |
268 | NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); | |
269 | ||
270 | return sqlite3_column_double(_handle, (int)index); | |
271 | } | |
272 | ||
273 | - (NSData *)blobAtIndex:(NSUInteger)index { | |
274 | NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); | |
275 | ||
276 | const void *bytes = sqlite3_column_blob(_handle, (int)index); | |
277 | if (bytes) { | |
278 | int length = sqlite3_column_bytes(_handle, (int)index); | |
279 | return [NSData dataWithBytes:bytes length:length]; | |
280 | } else { | |
281 | return nil; | |
282 | } | |
283 | } | |
284 | ||
285 | - (NSString *)textAtIndex:(NSUInteger)index { | |
286 | NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); | |
287 | ||
288 | const char *text = (const char *)sqlite3_column_text(_handle, (int)index); | |
289 | if (text) { | |
290 | return @(text); | |
291 | } else { | |
292 | return nil; | |
293 | } | |
294 | } | |
295 | ||
296 | - (id)objectAtIndex:(NSUInteger)index { | |
297 | int type = [self columnTypeAtIndex:index]; | |
298 | switch (type) { | |
299 | case SQLITE_INTEGER: | |
300 | return @([self int64AtIndex:index]); | |
301 | ||
302 | case SQLITE_FLOAT: | |
303 | return @([self doubleAtIndex:index]); | |
304 | ||
305 | case SQLITE_TEXT: | |
306 | return [self textAtIndex:index]; | |
307 | ||
308 | case SQLITE_BLOB: | |
309 | return [self blobAtIndex:index]; | |
310 | ||
311 | case SQLITE_NULL: | |
312 | return nil; | |
313 | ||
314 | default: | |
8a50f688 | 315 | secerror("sfsqlite: Unexpected column type: %d", type); |
866f8763 A |
316 | return nil; |
317 | } | |
318 | } | |
319 | ||
320 | - (NSArray *)allObjects { | |
321 | NSUInteger columnCount = [self columnCount]; | |
322 | NSMutableArray *objects = [NSMutableArray arrayWithCapacity:columnCount]; | |
323 | for (NSUInteger i = 0; i < columnCount; i++) { | |
324 | objects[i] = [self objectAtIndex:i] ?: [NSNull null]; | |
325 | } | |
326 | return objects; | |
327 | } | |
328 | ||
329 | - (NSDictionary *)allObjectsByColumnName { | |
330 | NSUInteger columnCount = [self columnCount]; | |
331 | NSMutableDictionary *objectsByColumnName = [NSMutableDictionary dictionaryWithCapacity:columnCount]; | |
332 | for (NSUInteger i = 0; i < columnCount; i++) { | |
333 | NSString *columnName = [self columnNameAtIndex:i]; | |
334 | id object = [self objectAtIndex:i]; | |
335 | if (object) { | |
336 | objectsByColumnName[columnName] = object; | |
337 | } | |
338 | } | |
339 | return objectsByColumnName; | |
340 | } | |
341 | ||
342 | @end |