#import "FMResultSet.h" #import "FMDatabase.h" #import "unistd.h" #import "sqlite3.h" @interface FMDatabase () - (void)resultSetDidClose:(FMResultSet *)resultSet; @end @interface FMResultSet () @property (nonatomic, readonly) NSDictionary *columnNameToIndexMapNonLowercased; @end @implementation FMResultSet @synthesize query=_query; @synthesize statement=_statement; @synthesize columnNameToIndexMapNonLowercased = _columnNameToIndexMapNonLowercased; + (instancetype)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB { FMResultSet *rs = [[FMResultSet alloc] init]; [rs setStatement:statement]; [rs setParentDB:aDB]; NSParameterAssert(![statement inUse]); [statement setInUse:YES]; // weak reference return FMDBReturnAutoreleased(rs); } - (void)dealloc { [self close]; FMDBRelease(_query); _query = nil; FMDBRelease(_columnNameToIndexMap); _columnNameToIndexMap = nil; #if ! __has_feature(objc_arc) [super dealloc]; #endif } - (void)close { [_statement reset]; FMDBRelease(_statement); _statement = nil; // we don't need this anymore... (i think) //[_parentDB setInUse:NO]; [_parentDB resultSetDidClose:self]; _parentDB = nil; } - (int)columnCount { return sqlite3_column_count([_statement statement]); } - (NSMutableDictionary *)columnNameToIndexMap { if (!_columnNameToIndexMap) { NSDictionary *nonLowercasedMap = self.columnNameToIndexMapNonLowercased; NSMutableDictionary *d = [[NSMutableDictionary alloc] initWithCapacity:nonLowercasedMap.count]; for (NSString *key in nonLowercasedMap.allKeys) { [d setObject:nonLowercasedMap[key] forKey:[self _lowercaseString:key]]; } _columnNameToIndexMap = d; } return _columnNameToIndexMap; } - (NSDictionary *)columnNameToIndexMapNonLowercased { if (!_columnNameToIndexMapNonLowercased) { int columnCount = sqlite3_column_count([_statement statement]); NSMutableDictionary *d = [[NSMutableDictionary alloc] initWithCapacity:(NSUInteger)columnCount]; int columnIdx = 0; for (columnIdx = 0; columnIdx < columnCount; columnIdx++) { [d setObject:[NSNumber numberWithInt:columnIdx] forKey:[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)]]; } _columnNameToIndexMapNonLowercased = d; } return _columnNameToIndexMapNonLowercased; } - (void)kvcMagic:(id)object { int columnCount = sqlite3_column_count([_statement statement]); int columnIdx = 0; for (columnIdx = 0; columnIdx < columnCount; columnIdx++) { const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx); // check for a null row if (c) { NSString *s = [NSString stringWithUTF8String:c]; [object setValue:s forKey:[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)]]; } } } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (NSDictionary*)resultDict { NSUInteger num_cols = (NSUInteger)sqlite3_data_count([_statement statement]); if (num_cols > 0) { NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols]; NSEnumerator *columnNames = [[self columnNameToIndexMap] keyEnumerator]; NSString *columnName = nil; while ((columnName = [columnNames nextObject])) { id objectValue = [self objectForColumnName:columnName]; [dict setObject:objectValue forKey:columnName]; } return FMDBReturnAutoreleased([dict copy]); } else { NSLog(@"Warning: There seem to be no columns in this set."); } return nil; } #pragma clang diagnostic pop - (NSDictionary*)resultDictionary { NSUInteger num_cols = (NSUInteger)sqlite3_data_count([_statement statement]); if (num_cols > 0) { NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols]; int columnCount = sqlite3_column_count([_statement statement]); int columnIdx = 0; for (columnIdx = 0; columnIdx < columnCount; columnIdx++) { NSString *columnName = [NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)]; id objectValue = [self objectForColumnIndex:columnIdx]; [dict setObject:objectValue forKey:columnName]; } return dict; } else { NSLog(@"Warning: There seem to be no columns in this set."); } return nil; } - (BOOL)next { return [self nextWithError:nil]; } - (BOOL)nextWithError:(NSError **)outErr { int rc = sqlite3_step([_statement statement]); if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) { NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [_parentDB databasePath]); NSLog(@"Database busy"); if (outErr) { *outErr = [_parentDB lastError]; } } else if (SQLITE_DONE == rc || SQLITE_ROW == rc) { // all is well, let's return. } else if (SQLITE_ERROR == rc) { NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); if (outErr) { *outErr = [_parentDB lastError]; } } else if (SQLITE_MISUSE == rc) { // uh oh. NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); if (outErr) { if (_parentDB) { *outErr = [_parentDB lastError]; } else { // If 'next' or 'nextWithError' is called after the result set is closed, // we need to return the appropriate error. NSDictionary* errorMessage = [NSDictionary dictionaryWithObject:@"parentDB does not exist" forKey:NSLocalizedDescriptionKey]; *outErr = [NSError errorWithDomain:@"FMDatabase" code:SQLITE_MISUSE userInfo:errorMessage]; } } } else { // wtf? NSLog(@"Unknown error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); if (outErr) { *outErr = [_parentDB lastError]; } } if (rc != SQLITE_ROW) { [self close]; } return (rc == SQLITE_ROW); } - (BOOL)hasAnotherRow { return sqlite3_errcode([_parentDB sqliteHandle]) == SQLITE_ROW; } - (int)columnIndexForName:(NSString*)columnName { NSNumber *n = self.columnNameToIndexMapNonLowercased[columnName]; if (!n) { columnName = [self _lowercaseString:columnName]; n = [[self columnNameToIndexMap] objectForKey:columnName]; } if (n) { return [n intValue]; } NSLog(@"Warning: I could not find the column named '%@'.", columnName); return -1; } - (int)intForColumn:(NSString*)columnName { return [self intForColumnIndex:[self columnIndexForName:columnName]]; } - (int)intForColumnIndex:(int)columnIdx { return sqlite3_column_int([_statement statement], columnIdx); } - (long)longForColumn:(NSString*)columnName { return [self longForColumnIndex:[self columnIndexForName:columnName]]; } - (long)longForColumnIndex:(int)columnIdx { return (long)sqlite3_column_int64([_statement statement], columnIdx); } - (long long int)longLongIntForColumn:(NSString*)columnName { return [self longLongIntForColumnIndex:[self columnIndexForName:columnName]]; } - (long long int)longLongIntForColumnIndex:(int)columnIdx { return sqlite3_column_int64([_statement statement], columnIdx); } - (unsigned long long int)unsignedLongLongIntForColumn:(NSString*)columnName { return [self unsignedLongLongIntForColumnIndex:[self columnIndexForName:columnName]]; } - (unsigned long long int)unsignedLongLongIntForColumnIndex:(int)columnIdx { return (unsigned long long int)[self longLongIntForColumnIndex:columnIdx]; } - (BOOL)boolForColumn:(NSString*)columnName { return [self boolForColumnIndex:[self columnIndexForName:columnName]]; } - (BOOL)boolForColumnIndex:(int)columnIdx { return ([self intForColumnIndex:columnIdx] != 0); } - (double)doubleForColumn:(NSString*)columnName { return [self doubleForColumnIndex:[self columnIndexForName:columnName]]; } - (double)doubleForColumnIndex:(int)columnIdx { return sqlite3_column_double([_statement statement], columnIdx); } - (NSString*)stringForColumnIndex:(int)columnIdx { if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { return nil; } const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx); if (!c) { // null row. return nil; } return [NSString stringWithUTF8String:c]; } - (NSString*)stringForColumn:(NSString*)columnName { return [self stringForColumnIndex:[self columnIndexForName:columnName]]; } - (NSDate*)dateForColumn:(NSString*)columnName { return [self dateForColumnIndex:[self columnIndexForName:columnName]]; } - (NSDate*)dateForColumnIndex:(int)columnIdx { if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { return nil; } return [_parentDB hasDateFormatter] ? [_parentDB dateFromString:[self stringForColumnIndex:columnIdx]] : [NSDate dateWithTimeIntervalSince1970:[self doubleForColumnIndex:columnIdx]]; } - (NSData*)dataForColumn:(NSString*)columnName { return [self dataForColumnIndex:[self columnIndexForName:columnName]]; } - (NSData*)dataForColumnIndex:(int)columnIdx { if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { return nil; } const char *dataBuffer = sqlite3_column_blob([_statement statement], columnIdx); int dataSize = sqlite3_column_bytes([_statement statement], columnIdx); if (dataBuffer == NULL) { return nil; } return [NSData dataWithBytes:(const void *)dataBuffer length:(NSUInteger)dataSize]; } - (NSData*)dataNoCopyForColumn:(NSString*)columnName { return [self dataNoCopyForColumnIndex:[self columnIndexForName:columnName]]; } - (NSData*)dataNoCopyForColumnIndex:(int)columnIdx { if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { return nil; } const char *dataBuffer = sqlite3_column_blob([_statement statement], columnIdx); int dataSize = sqlite3_column_bytes([_statement statement], columnIdx); NSData *data = [NSData dataWithBytesNoCopy:(void *)dataBuffer length:(NSUInteger)dataSize freeWhenDone:NO]; return data; } - (BOOL)columnIndexIsNull:(int)columnIdx { return sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL; } - (BOOL)columnIsNull:(NSString*)columnName { return [self columnIndexIsNull:[self columnIndexForName:columnName]]; } - (const unsigned char *)UTF8StringForColumnIndex:(int)columnIdx { if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { return nil; } return sqlite3_column_text([_statement statement], columnIdx); } - (const unsigned char *)UTF8StringForColumnName:(NSString*)columnName { return [self UTF8StringForColumnIndex:[self columnIndexForName:columnName]]; } - (id)objectForColumnIndex:(int)columnIdx { int columnType = sqlite3_column_type([_statement statement], columnIdx); id returnValue = nil; if (columnType == SQLITE_INTEGER) { returnValue = [NSNumber numberWithLongLong:[self longLongIntForColumnIndex:columnIdx]]; } else if (columnType == SQLITE_FLOAT) { returnValue = [NSNumber numberWithDouble:[self doubleForColumnIndex:columnIdx]]; } else if (columnType == SQLITE_BLOB) { returnValue = [self dataForColumnIndex:columnIdx]; } else { //default to a string for everything else returnValue = [self stringForColumnIndex:columnIdx]; } if (returnValue == nil) { returnValue = [NSNull null]; } return returnValue; } - (id)objectForColumnName:(NSString*)columnName { return [self objectForColumnIndex:[self columnIndexForName:columnName]]; } // returns autoreleased NSString containing the name of the column in the result set - (NSString*)columnNameForIndex:(int)columnIdx { return [NSString stringWithUTF8String: sqlite3_column_name([_statement statement], columnIdx)]; } - (void)setParentDB:(FMDatabase *)newDb { _parentDB = newDb; } - (id)objectAtIndexedSubscript:(int)columnIdx { return [self objectForColumnIndex:columnIdx]; } - (id)objectForKeyedSubscript:(NSString *)columnName { return [self objectForColumnName:columnName]; } // Brent 22 Feb. 2019: Calls to lowerCaseString show up in Instruments too much. // Given that the amount of column names in a given app is going to be pretty small, // we can just cache the lowercase versions - (NSString *)_lowercaseString:(NSString *)s { static NSLock *lock = nil; static NSMutableDictionary *lowercaseStringCache = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ lock = [[NSLock alloc] init]; lowercaseStringCache = [[NSMutableDictionary alloc] init]; }); [lock lock]; NSString *lowercaseString = lowercaseStringCache[s]; if (lowercaseString == nil) { lowercaseString = s.lowercaseString; lowercaseStringCache[s] = lowercaseString; } [lock unlock]; return lowercaseString; } @end