X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/914fc88e61be54aed6b18205ff2775b48793a3b6..866f8763175ff60e4fa455b92b5eb660a12fe6c7:/Analytics/SQLite/SFSQLiteStatement.m diff --git a/Analytics/SQLite/SFSQLiteStatement.m b/Analytics/SQLite/SFSQLiteStatement.m new file mode 100644 index 00000000..c8de43a8 --- /dev/null +++ b/Analytics/SQLite/SFSQLiteStatement.m @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2017 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#import "SFSQLite.h" +#import "SFSQLiteStatement.h" +#import "SFObjCType.h" + +@interface SFSQLiteStatement () +@property (nonatomic, strong) NSMutableArray *temporaryBoundObjects; +@end +@implementation SFSQLiteStatement + +@synthesize SQLite = _SQLite; +@synthesize SQL = _SQL; +@synthesize handle = _handle; +@synthesize reset = _reset; +@synthesize temporaryBoundObjects = _temporaryBoundObjects; + +- (id)initWithSQLite:(SFSQLite *)SQLite SQL:(NSString *)SQL handle:(sqlite3_stmt *)handle { + if ((self = [super init])) { + _SQLite = SQLite; + _SQL = SQL; + _handle = handle; + _reset = YES; + } + return self; +} + +- (void)finalizeStatement { + SFSQLite *strongSQLite = _SQLite; + + if (!_reset) { + [strongSQLite raise: @"Statement not reset after last use: \"%@\"", _SQL]; + } + if (sqlite3_finalize(_handle)) { + [strongSQLite raise:@"Error finalizing prepared statement: \"%@\"", _SQL]; + } +} + +- (void)resetAfterStepError +{ + if (!_reset) { + (void)sqlite3_reset(_handle); // we expect this to return an error + (void)sqlite3_clear_bindings(_handle); + [_temporaryBoundObjects removeAllObjects]; + _reset = YES; + } +} + +- (BOOL)step { + if (_reset) { + _reset = NO; + } + + int rc = sqlite3_step(_handle); + if ((rc & 0x00FF) == SQLITE_ROW) { + return YES; + } else if ((rc & 0x00FF) == SQLITE_DONE) { + return NO; + } else { + [self resetAfterStepError]; + [_SQLite raise:@"Failed to step (%d): \"%@\"", rc, _SQL]; + return NO; + } +} + +- (void)reset { + SFSQLite *strongSQLite = _SQLite; + + if (!_reset) { + if (sqlite3_reset(_handle)) { + [strongSQLite raise:@"Error resetting prepared statement: \"%@\"", _SQL]; + } + + if (sqlite3_clear_bindings(_handle)) { + [strongSQLite raise:@"Error clearing prepared statement bindings: \"%@\"", _SQL]; + } + [_temporaryBoundObjects removeAllObjects]; + _reset = YES; + } +} + +- (void)bindInt:(SInt32)value atIndex:(NSUInteger)index { + NSAssert(_reset, @"Statement is not reset: \"%@\"", _SQL); + + if (sqlite3_bind_int(_handle, (int)index+1, value)) { + [_SQLite raise:@"Error binding int at %ld: \"%@\"", (unsigned long)index, _SQL]; + } +} + +- (void)bindInt64:(SInt64)value atIndex:(NSUInteger)index { + NSAssert(_reset, @"Statement is not reset: \"%@\"", _SQL); + + if (sqlite3_bind_int64(_handle, (int)index+1, value)) { + [_SQLite raise:@"Error binding int64 at %ld: \"%@\"", (unsigned long)index, _SQL]; + } +} + +- (void)bindDouble:(double)value atIndex:(NSUInteger)index { + NSAssert(_reset, @"Statement is not reset: \"%@\"", _SQL); + + if (sqlite3_bind_double(_handle, (int)index+1, value)) { + [_SQLite raise:@"Error binding double at %ld: \"%@\"", (unsigned long)index, _SQL]; + } +} + +- (void)bindBlob:(NSData *)value atIndex:(NSUInteger)index { + NSAssert(_reset, @"Statement is not reset: \"%@\"", _SQL); + + if (value) { + NS_VALID_UNTIL_END_OF_SCOPE NSData *arcSafeValue = value; + if (sqlite3_bind_blob(_handle, (int)index+1, [arcSafeValue bytes], (int)[arcSafeValue length], NULL)) { + [_SQLite raise:@"Error binding blob at %ld: \"%@\"", (unsigned long)index, _SQL]; + } + } else { + [self bindNullAtIndex:index]; + } +} + +- (void)bindText:(NSString *)value atIndex:(NSUInteger)index { + NSAssert(_reset, @"Statement is not reset: \"%@\"", _SQL); + + if (value) { + NS_VALID_UNTIL_END_OF_SCOPE NSString *arcSafeValue = value; + if (sqlite3_bind_text(_handle, (int)index+1, [arcSafeValue UTF8String], -1, NULL)) { + [_SQLite raise:@"Error binding text at %ld: \"%@\"", (unsigned long)index, _SQL]; + } + } else { + [self bindNullAtIndex:index]; + } +} + +- (void)bindNullAtIndex:(NSUInteger)index { + int rc = sqlite3_bind_null(_handle, (int)index+1); + if ((rc & 0x00FF) != SQLITE_OK) { + [_SQLite raise:@"sqlite3_bind_null error"]; + } +} + +- (id)retainedTemporaryBoundObject:(id)object +{ + if (!_temporaryBoundObjects) { + _temporaryBoundObjects = [NSMutableArray new]; + } + [_temporaryBoundObjects addObject:object]; + return object; +} + +- (void)bindValue:(id)value atIndex:(NSUInteger)index { + if ([value isKindOfClass:[NSNumber class]]) { + SFObjCType *type = [SFObjCType typeForValue:value]; + if (type.isIntegerNumber) { + if (type.size <= 4) { + [self bindInt:[value intValue] atIndex:index]; + } else { + [self bindInt64:[value longLongValue] atIndex:index]; + } + } else { + NSAssert(type.isFloatingPointNumber, @"Expected number type to be either integer or floating point"); + if (type.code == SFObjCTypeFloat) { + [self bindInt:[value intValue] atIndex:index]; + } else { + NSAssert(type.code == SFObjCTypeDouble, @"Unexpected floating point number type: %@", type); + [self bindInt64:[value longLongValue] atIndex:index]; + } + } + } else if ([value isKindOfClass:[NSData class]]) { + [self bindBlob:value atIndex:index]; + } else if ([value isKindOfClass:[NSUUID class]]) { + uuid_t uuid; + [(NSUUID *)value getUUIDBytes:uuid]; + [self bindBlob:[self retainedTemporaryBoundObject:[NSData dataWithBytes:uuid length:sizeof(uuid_t)]] atIndex:index]; + } else if ([value isKindOfClass:[NSString class]]) { + [self bindText:value atIndex:index]; + } else if ([value isKindOfClass:[NSNull class]]) { + [self bindNullAtIndex:index]; + } else if ([value isKindOfClass:[NSDate class]]) { + [self bindDouble:[(NSDate *)value timeIntervalSinceReferenceDate] atIndex:index]; + } else if ([value isKindOfClass:[NSError class]]) { + [self bindBlob:[self retainedTemporaryBoundObject:[NSKeyedArchiver archivedDataWithRootObject:value]] atIndex:index]; + } else if ([value isKindOfClass:[NSURL class]]) { + [self bindText:[self retainedTemporaryBoundObject:[value absoluteString]] atIndex:index]; + } else { + [NSException raise:NSInvalidArgumentException format:@"Can't bind object of type %@", [value class]]; + } +} + +- (void)bindValues:(NSArray *)values { + for (NSUInteger i = 0; i < values.count; i++) { + [self bindValue:values[i] atIndex:i]; + } +} + +- (NSUInteger)columnCount { + NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); + + return sqlite3_column_count(_handle); +} + +- (int)columnTypeAtIndex:(NSUInteger)index { + NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); + + return sqlite3_column_type(_handle, (int)index); +} + +- (NSString *)columnNameAtIndex:(NSUInteger)index { + NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); + + return @(sqlite3_column_name(_handle, (int)index)); +} + +- (SInt32)intAtIndex:(NSUInteger)index { + NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); + + return sqlite3_column_int(_handle, (int)index); +} + +- (SInt64)int64AtIndex:(NSUInteger)index { + NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); + + return sqlite3_column_int64(_handle, (int)index); +} + +- (double)doubleAtIndex:(NSUInteger)index { + NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); + + return sqlite3_column_double(_handle, (int)index); +} + +- (NSData *)blobAtIndex:(NSUInteger)index { + NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); + + const void *bytes = sqlite3_column_blob(_handle, (int)index); + if (bytes) { + int length = sqlite3_column_bytes(_handle, (int)index); + return [NSData dataWithBytes:bytes length:length]; + } else { + return nil; + } +} + +- (NSString *)textAtIndex:(NSUInteger)index { + NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL); + + const char *text = (const char *)sqlite3_column_text(_handle, (int)index); + if (text) { + return @(text); + } else { + return nil; + } +} + +- (id)objectAtIndex:(NSUInteger)index { + int type = [self columnTypeAtIndex:index]; + switch (type) { + case SQLITE_INTEGER: + return @([self int64AtIndex:index]); + + case SQLITE_FLOAT: + return @([self doubleAtIndex:index]); + + case SQLITE_TEXT: + return [self textAtIndex:index]; + + case SQLITE_BLOB: + return [self blobAtIndex:index]; + + case SQLITE_NULL: + return nil; + + default: + [NSException raise:NSGenericException format:@"Unexpected column type: %d", type]; + return nil; + } +} + +- (NSArray *)allObjects { + NSUInteger columnCount = [self columnCount]; + NSMutableArray *objects = [NSMutableArray arrayWithCapacity:columnCount]; + for (NSUInteger i = 0; i < columnCount; i++) { + objects[i] = [self objectAtIndex:i] ?: [NSNull null]; + } + return objects; +} + +- (NSDictionary *)allObjectsByColumnName { + NSUInteger columnCount = [self columnCount]; + NSMutableDictionary *objectsByColumnName = [NSMutableDictionary dictionaryWithCapacity:columnCount]; + for (NSUInteger i = 0; i < columnCount; i++) { + NSString *columnName = [self columnNameAtIndex:i]; + id object = [self objectAtIndex:i]; + if (object) { + objectsByColumnName[columnName] = object; + } + } + return objectsByColumnName; +} + +@end