From 0c91f439a5b23e1b5a6d8139eb47aa5694e2925f Mon Sep 17 00:00:00 2001 From: rowanbeentje Date: Fri, 24 Feb 2012 00:30:18 +0000 Subject: Improvements to SPMySQL framework: - Correctly record affected rows - Fix thread safety/autorelease issues when draining pools during fast iteration - Improve streaming result processing speed --- .../SPMySQLFramework.xcodeproj/project.pbxproj | 7 +- .../Source/SPMySQLArrayAdditions.h | 50 ++++++++++ .../Querying & Preparation.m | 3 +- .../Source/SPMySQLFastStreamingResult.m | 104 +++++++++------------ .../SPMySQLResult Categories/Convenience Methods.m | 2 +- Frameworks/SPMySQLFramework/Source/SPMySQLResult.m | 13 ++- 6 files changed, 112 insertions(+), 67 deletions(-) create mode 100644 Frameworks/SPMySQLFramework/Source/SPMySQLArrayAdditions.h diff --git a/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj index a9f8db0d..933a68b7 100644 --- a/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj +++ b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 586A99FC14F02E21007F82BF /* SPMySQLStreamingResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 586A99FA14F02E21007F82BF /* SPMySQLStreamingResult.m */; }; 586AA16714F30C5F007F82BF /* Convenience Methods.h in Headers */ = {isa = PBXBuildFile; fileRef = 586AA16514F30C5F007F82BF /* Convenience Methods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 586AA16814F30C5F007F82BF /* Convenience Methods.m in Sources */ = {isa = PBXBuildFile; fileRef = 586AA16614F30C5F007F82BF /* Convenience Methods.m */; }; + 586AA81414F6C648007F82BF /* SPMySQLArrayAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 586AA81214F6C648007F82BF /* SPMySQLArrayAdditions.h */; }; 5884127714CC63830078027F /* SPMySQL.h in Headers */ = {isa = PBXBuildFile; fileRef = 5884127614CC63830078027F /* SPMySQL.h */; settings = {ATTRIBUTES = (Public, ); }; }; 588412A814CC7A4D0078027F /* Locking.h in Headers */ = {isa = PBXBuildFile; fileRef = 588412A614CC7A4D0078027F /* Locking.h */; }; 5884133C14CCEC6B0078027F /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5884133B14CCEC6B0078027F /* libz.dylib */; }; @@ -96,6 +97,7 @@ 586AA0E714F1CEC8007F82BF /* Readme.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Readme.txt; sourceTree = ""; }; 586AA16514F30C5F007F82BF /* Convenience Methods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Convenience Methods.h"; path = "Source/SPMySQLResult Categories/Convenience Methods.h"; sourceTree = ""; }; 586AA16614F30C5F007F82BF /* Convenience Methods.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "Convenience Methods.m"; path = "Source/SPMySQLResult Categories/Convenience Methods.m"; sourceTree = ""; }; + 586AA81214F6C648007F82BF /* SPMySQLArrayAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLArrayAdditions.h; path = Source/SPMySQLArrayAdditions.h; sourceTree = ""; }; 5884127614CC63830078027F /* SPMySQL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQL.h; path = Source/SPMySQL.h; sourceTree = ""; }; 588412A614CC7A4D0078027F /* Locking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Locking.h; path = "Source/SPMySQLConnection Categories/Locking.h"; sourceTree = ""; }; 588412A714CC7A4D0078027F /* Locking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Locking.m; path = "Source/SPMySQLConnection Categories/Locking.m"; sourceTree = ""; }; @@ -330,6 +332,7 @@ children = ( 58C009D314E31D3800AC489A /* SPMySQLStringAdditions.h */, 58C009D414E31D3800AC489A /* SPMySQLStringAdditions.m */, + 586AA81214F6C648007F82BF /* SPMySQLArrayAdditions.h */, ); name = "Category Additions"; sourceTree = ""; @@ -383,6 +386,7 @@ 58C00CA514E845D800AC489A /* SPMySQL Private APIs.h in Headers */, 586A99FB14F02E21007F82BF /* SPMySQLStreamingResult.h in Headers */, 586AA16714F30C5F007F82BF /* Convenience Methods.h in Headers */, + 586AA81414F6C648007F82BF /* SPMySQLArrayAdditions.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -549,7 +553,6 @@ GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; GCC_WARN_MISSING_PARENTHESES = YES; - GCC_WARN_PROTOTYPE_CONVERSION = YES; GCC_WARN_SHADOW = YES; GCC_WARN_SIGN_COMPARE = YES; GCC_WARN_STRICT_SELECTOR_MATCH = YES; @@ -581,7 +584,6 @@ GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; GCC_WARN_MISSING_PARENTHESES = YES; - GCC_WARN_PROTOTYPE_CONVERSION = YES; GCC_WARN_SHADOW = YES; GCC_WARN_SIGN_COMPARE = YES; GCC_WARN_STRICT_SELECTOR_MATCH = YES; @@ -612,7 +614,6 @@ GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; GCC_WARN_MISSING_PARENTHESES = YES; - GCC_WARN_PROTOTYPE_CONVERSION = YES; GCC_WARN_SHADOW = YES; GCC_WARN_SIGN_COMPARE = YES; GCC_WARN_STRICT_SELECTOR_MATCH = YES; diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLArrayAdditions.h b/Frameworks/SPMySQLFramework/Source/SPMySQLArrayAdditions.h new file mode 100644 index 00000000..46c6d8e0 --- /dev/null +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLArrayAdditions.h @@ -0,0 +1,50 @@ +// +// $Id$ +// +// SPMySQLArrayAdditions.h +// SPMySQLFramework +// +// Created by Rowan Beentje (rowan.beent.je) on February 23, 2012 +// Copyright (c) 2012 Rowan Beentje. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + +/** + * Set up a static function to allow fast mutable array insertion using + * cached selectors. + * At least in 10.7, inserting items into an array at a known point + * using NSMutableArray methods appears to be the fastest way of adding + * items to a CF/NSMutableArray. + */ +static inline void SPMySQLMutableArrayInsertObject(NSMutableArray *self, id anObject, NSUInteger anIndex) +{ + typedef id (*SPMySQLMutableArrayInsertObjectPtr)(NSMutableArray*, SEL, id, NSUInteger); + static SPMySQLMutableArrayInsertObjectPtr cachedMethodPointer; + static SEL cachedSelector; + + if (!cachedSelector) cachedSelector = @selector(insertObject:atIndex:); + if (!cachedMethodPointer) cachedMethodPointer = (SPMySQLMutableArrayInsertObjectPtr)[self methodForSelector:cachedSelector]; + + cachedMethodPointer(self, cachedSelector, anObject, anIndex); +} diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m index e3ce599a..cb5ce70d 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m @@ -346,9 +346,10 @@ } } - // Update error string and ID + // Update error string and ID, and the rows affected [self _updateLastErrorMessage:theErrorMessage]; [self _updateLastErrorID:theErrorID]; + lastQueryAffectedRowCount = theAffectedRowCount; // Store the result time on the response object [theResult _setQueryExecutionTime:queryExecutionTime]; diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m b/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m index f084dee0..8ba55134 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m @@ -32,8 +32,11 @@ #import "SPMySQLFastStreamingResult.h" #import "SPMySQL Private APIs.h" +#import "SPMySQLArrayAdditions.h" #include +static id NSNullPointer; + /** * This type of streaming result operates in a multithreaded fashion - a worker * thread is set up to download the results as fast as possible in the background, @@ -45,7 +48,7 @@ typedef struct st_spmysqlstreamingrowdata { char *data; - NSUInteger *dataLengths; + unsigned long *dataLengths; struct st_spmysqlstreamingrowdata *nextRow; } SPMySQLStreamingRowData; @@ -61,6 +64,16 @@ typedef struct st_spmysqlstreamingrowdata { #pragma mark - +/** + * In the one-off class initialisation, cache static variables + */ ++ (void)initialize +{ + + // Cached NSNull singleton reference + if (!NSNullPointer) NSNullPointer = [NSNull null]; +} + /** * Standard init method, constructing the SPMySQLStreamingResult around a MySQL * result pointer and the encoding to use when working with the data. @@ -137,9 +150,19 @@ typedef struct st_spmysqlstreamingrowdata { { NSUInteger copiedDataLength = 0; char *theRowData; - NSUInteger *fieldLengths; + unsigned long *fieldLengths; id theReturnData; + // If the target type was unspecified, use the instance default + if (theType == SPMySQLResultRowAsDefault) theType = defaultRowReturnType; + + // Set up the return data as appropriate + if (theType == SPMySQLResultRowAsArray) { + theReturnData = [NSMutableArray arrayWithCapacity:numberOfFields]; + } else { + theReturnData = [NSMutableDictionary dictionaryWithCapacity:numberOfFields]; + } + // Lock the data mutex for safe access of variables and counters pthread_mutex_lock(&dataLock); @@ -164,40 +187,32 @@ typedef struct st_spmysqlstreamingrowdata { theRowData = currentDataStoreEntry->data; fieldLengths = currentDataStoreEntry->dataLengths; - // If the target type was unspecified, use the instance default - if (theType == SPMySQLResultRowAsDefault) theType = defaultRowReturnType; - - // Set up the return data as appropriate - if (theType == SPMySQLResultRowAsArray) { - theReturnData = [NSMutableArray arrayWithCapacity:numberOfFields]; - } else { - theReturnData = [NSMutableDictionary dictionaryWithCapacity:numberOfFields]; - } - // Convert each of the cells in the row in turn + unsigned long fieldLength; + id cellData; + char *rawCellData; for (NSUInteger i = 0; i < numberOfFields; i++) { - char *rawCellData; - NSUInteger fieldLength = fieldLengths[i]; + fieldLength = fieldLengths[i]; // If the length of this cell is NSNotFound, it's a null reference if (fieldLength == NSNotFound) { - rawCellData = NULL; + cellData = nil; // Otherwise grab a reference to that data using pointer arithmetic } else { rawCellData = theRowData + copiedDataLength; copiedDataLength += fieldLength; - } - // Convert to the correct object type - id cellData = SPMySQLResultGetObject(self, rawCellData, fieldLength, fieldTypes[i], i); + // Convert to the correct object type + cellData = SPMySQLResultGetObject(self, rawCellData, fieldLength, fieldTypes[i], i); + } // If object creation failed, display a null - if (!cellData) cellData = [NSNull null]; + if (!cellData) cellData = NSNullPointer; // Add to the result array/dictionary if (theType == SPMySQLResultRowAsArray) { - [(NSMutableArray *)theReturnData addObject:cellData]; + SPMySQLMutableArrayInsertObject(theReturnData, cellData, i); } else { [(NSMutableDictionary *)theReturnData setObject:cellData forKey:fieldNames[i]]; } @@ -211,6 +226,7 @@ typedef struct st_spmysqlstreamingrowdata { // Update the active-data pointer to the next item in the list (which may be NULL) currentDataStoreEntry = currentDataStoreEntry->nextRow; + if (!currentDataStoreEntry) lastDataStoreEntry = NULL; // Increment the processed counter and row index processedRowCount++; @@ -221,7 +237,6 @@ typedef struct st_spmysqlstreamingrowdata { pthread_mutex_unlock(&dataLock); // Free the memory for the processed row - previousDataStoreEntry->nextRow = NULL; free(previousDataStoreEntry->dataLengths); if (previousDataStoreEntry->data != NULL) free(previousDataStoreEntry->data); free(previousDataStoreEntry); @@ -260,6 +275,7 @@ typedef struct st_spmysqlstreamingrowdata { // Update the active-data pointer to the next item in the list (which may be NULL) currentDataStoreEntry = currentDataStoreEntry->nextRow; + if (!currentDataStoreEntry) lastDataStoreEntry = NULL; processedRowCount++; currentRowIndex++; @@ -269,7 +285,6 @@ typedef struct st_spmysqlstreamingrowdata { pthread_mutex_unlock(&dataLock); // Free the memory for the processed row - previousDataStoreEntry->nextRow = NULL; free(previousDataStoreEntry->dataLengths); if (previousDataStoreEntry->data != NULL) free(previousDataStoreEntry->data); free(previousDataStoreEntry); @@ -287,47 +302,20 @@ typedef struct st_spmysqlstreamingrowdata { - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len { - // If all rows have already been processed, return 0 to stop iteration. - if (dataDownloaded && processedRowCount == downloadedRowCount) return 0; + // To avoid lock issues, return one row at a time. + id nextRow = SPMySQLResultGetRow(self, SPMySQLResultRowAsDefault); - // If the MySQL row pointer does not match the requested state, throw an exception - if (state->state != currentRowIndex) { - [NSException raise:NSRangeException format:@"SPMySQLFastStreamingResult results can only be accessed linearly"]; - } - - // Determine how many objects to return. Default to 128 items, or the number of items requested - NSUInteger itemsToReturn = 128; - if (len < 128) itemsToReturn = len; - - // If there are fewer items available in the downloaded-but-processed queue, limit to that - if (downloadedRowCount - processedRowCount < itemsToReturn) { - itemsToReturn = downloadedRowCount - processedRowCount; - } - - // If no rows are available to be processed, wait for a single item to be readied. - if (!itemsToReturn) itemsToReturn = 1; + // If no row was available, return 0 to stop iteration. + if (!nextRow) return 0; - // Retrieve rows and add them to the result stack - NSUInteger i, itemsRetrieved = 0; - id eachRow; - for (i = 0; i < itemsToReturn; i++) { - eachRow = SPMySQLResultGetRow(self, SPMySQLResultRowAsDefault); - - // If nil was returned the end of the result resource has been reached - if (!eachRow) { - if (!itemsRetrieved) return 0; - break; - } - - stackbuf[i] = eachRow; - itemsRetrieved++; - } + // Otherwise, add the item to the buffer and return the appropriate state. + stackbuf[0] = nextRow; - state->state += itemsRetrieved; + state->state += 1; state->itemsPtr = stackbuf; state->mutationsPtr = (unsigned long *)self; - return itemsRetrieved; + return 1; } @end @@ -349,7 +337,7 @@ typedef struct st_spmysqlstreamingrowdata { SPMySQLStreamingRowData *newRowStore; size_t sizeOfStreamingRowData = sizeof(SPMySQLStreamingRowData); - size_t sizeOfDataLengths = (size_t)(sizeof(NSUInteger) * numberOfFields); + size_t sizeOfDataLengths = (size_t)(sizeof(unsigned long) * numberOfFields); size_t sizeOfChar = sizeof(char); // Loop through the rows until the end of the data is reached - indicated via a NULL diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Convenience Methods.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Convenience Methods.m index 7e3619e1..2b049264 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Convenience Methods.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Convenience Methods.m @@ -49,7 +49,7 @@ // If the number of rows is known, pre-set the size; otherwise just create an array if (numberOfRows != NSNotFound) { - rowsToReturn = [[NSMutableArray alloc] initWithCapacity:numberOfRows]; + rowsToReturn = [[NSMutableArray alloc] initWithCapacity:(NSUInteger)numberOfRows]; } else { rowsToReturn = [[NSMutableArray alloc] init]; } diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m index b110958d..3ccd5727 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m @@ -32,8 +32,10 @@ #import "SPMySQLResult.h" #import "SPMySQL Private APIs.h" +#import "SPMySQLArrayAdditions.h" static SPMySQLResultFieldProcessor fieldProcessingMap[256]; +static id NSNullPointer; @implementation SPMySQLResult @@ -52,6 +54,9 @@ static SPMySQLResultFieldProcessor fieldProcessingMap[256]; + (void)initialize { + // Cached NSNull singleton reference + if (!NSNullPointer) NSNullPointer = [NSNull null]; + // Go through the list of enum_field_types in mysql_com.h, mapping each to the method for // processing that result set. fieldProcessingMap[MYSQL_TYPE_DECIMAL] = SPMySQLResultFieldAsString; @@ -266,11 +271,11 @@ static SPMySQLResultFieldProcessor fieldProcessingMap[256]; id cellData = SPMySQLResultGetObject(self, theRow[i], theRowDataLengths[i], fieldTypes[i], i); // If object creation failed, display a null - if (!cellData) cellData = [NSNull null]; + if (!cellData) cellData = NSNullPointer; // Add to the result array/dictionary if (theType == SPMySQLResultRowAsArray) { - [(NSMutableArray *)theReturnData addObject:cellData]; + SPMySQLMutableArrayInsertObject(theReturnData, cellData, i); } else { [(NSMutableDictionary *)theReturnData setObject:cellData forKey:fieldNames[i]]; } @@ -389,7 +394,7 @@ static SPMySQLResultFieldProcessor fieldProcessingMap[256]; { // A NULL pointer for the data indicates a null value; return a NSNull object. - if (bytes == NULL) return [NSNull null]; + if (bytes == NULL) return NSNullPointer; // Determine the field processor to use SPMySQLResultFieldProcessor dataProcessor = fieldProcessingMap[fieldType]; @@ -445,7 +450,7 @@ static SPMySQLResultFieldProcessor fieldProcessingMap[256]; // Convert null types to NSNulls case SPMySQLResultFieldAsNull: - return [NSNull null]; + return NSNullPointer; case SPMySQLResultFieldAsUnhandled: NSLog(@"SPMySQLResult processing encountered an unknown field type (%d), falling back to NSData handling", fieldType); -- cgit v1.2.3