aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrowanbeentje <rowan@beent.je>2012-02-24 00:30:18 +0000
committerrowanbeentje <rowan@beent.je>2012-02-24 00:30:18 +0000
commit0c91f439a5b23e1b5a6d8139eb47aa5694e2925f (patch)
treec6d5b81773bfa78458dc249d582b04066f2c4373
parent05f1612cbb7e33cf9135a346fc2505cc0e87e853 (diff)
downloadsequelpro-0c91f439a5b23e1b5a6d8139eb47aa5694e2925f.tar.gz
sequelpro-0c91f439a5b23e1b5a6d8139eb47aa5694e2925f.tar.bz2
sequelpro-0c91f439a5b23e1b5a6d8139eb47aa5694e2925f.zip
Improvements to SPMySQL framework:
- Correctly record affected rows - Fix thread safety/autorelease issues when draining pools during fast iteration - Improve streaming result processing speed
-rw-r--r--Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj7
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLArrayAdditions.h50
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m3
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m104
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Convenience Methods.m2
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLResult.m13
6 files changed, 112 insertions, 67 deletions
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 = "<group>"; };
586AA16514F30C5F007F82BF /* Convenience Methods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Convenience Methods.h"; path = "Source/SPMySQLResult Categories/Convenience Methods.h"; sourceTree = "<group>"; };
586AA16614F30C5F007F82BF /* Convenience Methods.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "Convenience Methods.m"; path = "Source/SPMySQLResult Categories/Convenience Methods.m"; sourceTree = "<group>"; };
+ 586AA81214F6C648007F82BF /* SPMySQLArrayAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLArrayAdditions.h; path = Source/SPMySQLArrayAdditions.h; sourceTree = "<group>"; };
5884127614CC63830078027F /* SPMySQL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQL.h; path = Source/SPMySQL.h; sourceTree = "<group>"; };
588412A614CC7A4D0078027F /* Locking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Locking.h; path = "Source/SPMySQLConnection Categories/Locking.h"; sourceTree = "<group>"; };
588412A714CC7A4D0078027F /* Locking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Locking.m; path = "Source/SPMySQLConnection Categories/Locking.m"; sourceTree = "<group>"; };
@@ -330,6 +332,7 @@
children = (
58C009D314E31D3800AC489A /* SPMySQLStringAdditions.h */,
58C009D414E31D3800AC489A /* SPMySQLStringAdditions.m */,
+ 586AA81214F6C648007F82BF /* SPMySQLArrayAdditions.h */,
);
name = "Category Additions";
sourceTree = "<group>";
@@ -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 <http://code.google.com/p/sequel-pro/>
+
+/**
+ * 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 <pthread.h>
+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;
@@ -62,6 +65,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.
* As opposed to SPMySQLResult, defaults to returning rows as arrays, as the result
@@ -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);