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