aboutsummaryrefslogtreecommitdiffstats
path: root/Frameworks/SPMySQLFramework/Source
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 /Frameworks/SPMySQLFramework/Source
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
Diffstat (limited to 'Frameworks/SPMySQLFramework/Source')
-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
5 files changed, 108 insertions, 64 deletions
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);