SFSQLiteStatement.m
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 #if __OBJC2__ 25 26 #import <Foundation/NSKeyedArchiver_Private.h> 27 #import "SFSQLite.h" 28 #import "SFSQLiteStatement.h" 29 #import "SFObjCType.h" 30 #import "utilities/debugging.h" 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 { 54 if (!_reset) { 55 secerror("sfsqlite: Statement not reset after last use: \"%@\"", _SQL); 56 return; 57 } 58 if (sqlite3_finalize(_handle)) { 59 secerror("sfsqlite: Error finalizing prepared statement: \"%@\"", _SQL); 60 return; 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]; 86 secerror("sfsqlite: Failed to step (%d): \"%@\"", rc, _SQL); 87 return NO; 88 } 89 } 90 91 - (void)reset { 92 if (!_reset) { 93 if (sqlite3_reset(_handle)) { 94 secerror("sfsqlite: Error resetting prepared statement: \"%@\"", _SQL); 95 return; 96 } 97 98 if (sqlite3_clear_bindings(_handle)) { 99 secerror("sfsqlite: Error clearing prepared statement bindings: \"%@\"", _SQL); 100 return; 101 } 102 [_temporaryBoundObjects removeAllObjects]; 103 _reset = YES; 104 } 105 } 106 107 - (void)bindInt:(SInt32)value atIndex:(NSUInteger)index { 108 if (!_reset) { 109 secerror("sfsqlite: Statement is not reset: \"%@\"", _SQL); 110 return; 111 } 112 113 if (sqlite3_bind_int(_handle, (int)index+1, value)) { 114 secerror("sfsqlite: Error binding int at %ld: \"%@\"", (unsigned long)index, _SQL); 115 return; 116 } 117 } 118 119 - (void)bindInt64:(SInt64)value atIndex:(NSUInteger)index { 120 if (!_reset) { 121 secerror("sfsqlite: Statement is not reset: \"%@\"", _SQL); 122 return; 123 } 124 125 if (sqlite3_bind_int64(_handle, (int)index+1, value)) { 126 secerror("sfsqlite: Error binding int64 at %ld: \"%@\"", (unsigned long)index, _SQL); 127 return; 128 } 129 } 130 131 - (void)bindDouble:(double)value atIndex:(NSUInteger)index { 132 if (!_reset) { 133 secerror("sfsqlite: Statement is not reset: \"%@\"", _SQL); 134 return; 135 } 136 137 if (sqlite3_bind_double(_handle, (int)index+1, value)) { 138 secerror("sfsqlite: Error binding double at %ld: \"%@\"", (unsigned long)index, _SQL); 139 return; 140 } 141 } 142 143 - (void)bindBlob:(NSData *)value atIndex:(NSUInteger)index { 144 if (!_reset) { 145 secerror("sfsqlite: Statement is not reset: \"%@\"", _SQL); 146 return; 147 } 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)) { 152 secerror("sfsqlite: Error binding blob at %ld: \"%@\"", (unsigned long)index, _SQL); 153 return; 154 } 155 } else { 156 [self bindNullAtIndex:index]; 157 } 158 } 159 160 - (void)bindText:(NSString *)value atIndex:(NSUInteger)index { 161 if (!_reset) { 162 secerror("sfsqlite: Statement is not reset: \"%@\"", _SQL); 163 return; 164 } 165 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)) { 169 secerror("sfsqlite: Error binding text at %ld: \"%@\"", (unsigned long)index, _SQL); 170 return; 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) { 180 secerror("sfsqlite: sqlite3_bind_null error"); 181 return; 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"); 205 NSAssert(type.code == SFObjCTypeDouble || type.code == SFObjCTypeFloat, @"Unexpected floating point number type: %@", type); 206 [self bindDouble:[value doubleValue] atIndex:index]; 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]]) { 221 [self bindBlob:[self retainedTemporaryBoundObject:[NSKeyedArchiver archivedDataWithRootObject:value requiringSecureCoding:YES error:nil]] atIndex:index]; 222 } else if ([value isKindOfClass:[NSURL class]]) { 223 [self bindText:[self retainedTemporaryBoundObject:[value absoluteString]] atIndex:index]; 224 } else { 225 secerror("sfsqlite: Can't bind object of type %@", [value class]); 226 return; 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: 314 secerror("sfsqlite: Unexpected column type: %d", type); 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 342 343 #endif