aboutsummaryrefslogtreecommitdiffstats
path: root/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResult.m
diff options
context:
space:
mode:
authorrowanbeentje <rowan@beent.je>2012-03-17 15:32:00 +0000
committerrowanbeentje <rowan@beent.je>2012-03-17 15:32:00 +0000
commitac8ebfe4bba6d7da6edad87c75b174156d919621 (patch)
tree08a3c2843d3c520090f40e6341b26043a7ccf014 /Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResult.m
parent8886d935e933c6239aa9b35e900d96f7d07527c7 (diff)
parenteab6df4de773259f90dd5a1d25e44ca4d2765bbf (diff)
downloadsequelpro-ac8ebfe4bba6d7da6edad87c75b174156d919621.tar.gz
sequelpro-ac8ebfe4bba6d7da6edad87c75b174156d919621.tar.bz2
sequelpro-ac8ebfe4bba6d7da6edad87c75b174156d919621.zip
Merge in the SPMySQL Framework. This new framework should provide much of the functionality required from MCPKit and is based around its interface for relatively easy integration.
Externally visible changes as a result of this merge: - Speed improvements, particularly when loading large data sets - Stability improvements, particularly related to connection state after the connection is dropped (eg Issue #1256) - Improved support for new MySQL data types, which should address Issue #1052. - Database structure retrieval and query cancellation now use a single persistent helper connection instead of lots of connections on-demand. This should help Issue #1097. - More internal commands now use queries instead of MySQL functions; for example USE queries are now used to trigger database selection, improving transcripts. This addresses Issue #1247. - Improved internal encoding work; while this needs support within the UI, it lays the foundation for issues like Issue #1280. Code improvements: - Much improved class layouts including extensive category usage - Improved documentation across framework methods - Support for fast enumeration across result objects - Rewrite fixes use of a number of deprecate functions - Much less code duplication across result set types - Improved encapsultation within the framework, limiting the number of methods exposed, and also not exposing all the MySQL headers From the Readme file: The SPMySQL Framework is intended to provide a stable MySQL connection framework, with the ability to run text-based queries and rapidly retrieve result sets with conversion from MySQL data types to Cocoa objects. SPMySQL.framework has an interface loosely based around that provided by MCPKit by Serge Cohen and Bertrand Mansion (http://mysql-cocoa.sourceforge.net/), and in particular the heavily modified Sequel Pro version (http://www.sequelpro.com/). It is a full rewrite of the original framework, although it includes code from patches implementing the following Sequel Pro functionality, largely contributed by Hans-Jörg Bibiko, Stuart Connolly, Jakob Egger, and Rowan Beentje: - Connection locking (Jakob et al) - Ping & keepalive (Rowan et al) - Query cancellation (Rowan et al) - Delegate setup (Stuart et al) - SSL support (Rowan et al) - Connection checking (Rowan et al) - Version state (Stuart et al) - Maximum packet size control (Hans et al) - Result multithreading and streaming (Rowan et al) - Improved encoding support & switching (Rowan et al) - Database structure; moved to inside the app (Hans et al) - Query reattempts and error-handling approach (Rowan et al) - Geometry result class (Hans et al) - Connection proxy (Stuart et al)
Diffstat (limited to 'Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResult.m')
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResult.m246
1 files changed, 246 insertions, 0 deletions
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResult.m b/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResult.m
new file mode 100644
index 00000000..b19e5356
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResult.m
@@ -0,0 +1,246 @@
+//
+// $Id$
+//
+// SPMySQLStreamingResult.m
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on February 18, 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/>
+
+#import "SPMySQLStreamingResult.h"
+#import "SPMySQL Private APIs.h"
+
+
+/**
+ * This type of streaming result allows each row to be accessed on-demand; this can
+ * be dangerous as it means a SELECT will tie up the server for longer, as for MyISAM
+ * tables updates (and subsequent reads) must block while a SELECT is still running.
+ * However this can be useful for certain processes such as working with very large
+ * tables to keep memory usage low.
+ */
+
+@implementation SPMySQLStreamingResult
+
+#pragma mark -
+
+/**
+ * Prevent SPMySQLStreamingResults from being init'd as SPMySQLResults.
+ */
+- (id)initWithMySQLResult:(void *)theResult stringEncoding:(NSStringEncoding)theStringEncoding
+{
+ [NSException raise:NSInternalInconsistencyException format:@"SPMySQLFullStreamingResults should not be init'd as SPMySQLResults; use initWithMySQLResult:stringEncoding:connection:withFullStreaming: instead."];
+ return nil;
+}
+
+/**
+ * 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
+ * sets are likely to be larger and processed in loops.
+ */
+- (id)initWithMySQLResult:(void *)theResult stringEncoding:(NSStringEncoding)theStringEncoding connection:(SPMySQLConnection *)theConnection
+{
+
+ // If no result set was passed in, return nil.
+ if (!theResult) return nil;
+
+ if ((self = [super initWithMySQLResult:theResult stringEncoding:theStringEncoding])) {
+ parentConnection = theConnection;
+ numberOfRows = NSNotFound;
+
+ // Start with no rows downloaded
+ downloadedRowCount = 0;
+ dataDownloaded = NO;
+ connectionUnlocked = NO;
+
+ // Cache the isConnected selector and pointer for fast connection checks
+ isConnectedSelector = @selector(isConnected);
+ isConnectedPtr = [parentConnection methodForSelector:isConnectedSelector];
+
+ // Default to returning rows as arrays
+ defaultRowReturnType = SPMySQLResultRowAsArray;
+ }
+
+ return self;
+}
+
+/**
+ * Deallocate the result and ensure the parent connection is unlocked for further use.
+ */
+- (void)dealloc
+{
+
+ // Ensure all data is processed and the parent connection is unlocked
+ [self cancelResultLoad];
+
+ // Throw an exception if in invalid state
+ if (!connectionUnlocked) {
+ [parentConnection _unlockConnection];
+ [NSException raise:NSInternalInconsistencyException format:@"Parent connection remains locked after SPMySQLStreamingResult use"];
+ }
+
+ [super dealloc];
+}
+
+#pragma mark -
+#pragma mark Result set information
+
+/**
+ * Override the return of the number of rows in the data set. If this is used before the
+ * data is fully downloaded, the number of results is still unknown (the server may still
+ * be seeking/matching), so return NSNotFound; otherwise the number of rows is returned.
+ */
+- (unsigned long long)numberOfRows
+{
+ if (!dataDownloaded) return NSNotFound;
+
+ return downloadedRowCount;
+}
+
+#pragma mark -
+#pragma mark Data retrieval
+
+/**
+ * Override seeking behaviour: seeking cannot be used in streaming result sets.
+ */
+- (void)seekToRow:(unsigned long long)targetRow
+{
+ [NSException raise:NSInternalInconsistencyException format:@"Seeking is not supported in streaming SPMySQL result sets."];
+}
+
+/**
+ * Override the convenience selectors so that forwarding works correctly.
+ */
+- (id)getRow
+{
+ return SPMySQLResultGetRow(self, SPMySQLResultRowAsDefault);
+}
+- (NSArray *)getRowAsArray
+{
+ return SPMySQLResultGetRow(self, SPMySQLResultRowAsArray);
+}
+- (NSDictionary *)getRowAsDictionary
+{
+ return SPMySQLResultGetRow(self, SPMySQLResultRowAsDictionary);
+}
+
+/**
+ * Retrieve the next row in the result set, using the internal pointer, in the specified
+ * return format.
+ * If there are no rows remaining in the current iteration, returns nil.
+ */
+- (id)getRowAsType:(SPMySQLResultRowType)theType
+{
+ id theRow = nil;
+
+ // Ensure that the connection is still up before performing a row fetch
+ if ((*isConnectedPtr)(parentConnection, isConnectedSelector)) {
+
+ // The core of result fetching in streaming mode is still based around mysql_fetch_row,
+ // so use the super to perform normal processing.
+ theRow = [super getRowAsType:theType];
+ }
+
+ // If no row was returned, the end of the result set has been reached. Clear markers,
+ // unlock the parent connection, and return nil.
+ if (!theRow) {
+ dataDownloaded = YES;
+ [parentConnection _unlockConnection];
+ connectionUnlocked = YES;
+ return nil;
+ }
+
+ // Otherwise increment the data downloaded counter and return the row
+ downloadedRowCount++;
+
+ return theRow;
+}
+
+/*
+ * Ensure the result set is fully processed and freed without any processing
+ * This method ensures that the connection is unlocked.
+ */
+- (void)cancelResultLoad
+{
+
+ // If data has already been downloaded successfully, no further action is required
+ if (dataDownloaded) return;
+
+ MYSQL_ROW theRow;
+
+ // Loop through all the rows and ensure the rows are fetched.
+ while (1) {
+ theRow = mysql_fetch_row(resultSet);
+
+ // If no data was returned, we're at the end of the result set - return.
+ if (theRow == NULL) {
+ dataDownloaded = YES;
+ if (!connectionUnlocked) {
+ [parentConnection _unlockConnection];
+ connectionUnlocked = YES;
+ }
+ return;
+ }
+
+ downloadedRowCount++;
+ }
+}
+
+#pragma mark -
+#pragma mark Data retrieval for fast enumeration
+
+/**
+ * Implement the fast enumeration endpoint. Rows for fast enumeration are retrieved in
+ * the instance default, as specified in setDefaultRowReturnType: or defaulting to
+ * NSDictionary. Full streaming mode - return one row at a time.
+ */
+- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len
+{
+
+ // If all rows have been retrieved, return 0 to stop iteration.
+ if (dataDownloaded) return 0;
+
+ // If the MySQL row pointer does not match the requested state, throw an exception
+ if (state->state != currentRowIndex) {
+ [NSException raise:NSRangeException format:@"SPMySQLStreamingResult results can only be accessed linearly"];
+ }
+
+ // In full streaming mode return one row at a time. Retrieve the row.
+ id theRow = SPMySQLResultGetRow(self, SPMySQLResultRowAsDefault);
+
+ // If nil was returned the end of the result resource has been reached
+ if (!theRow) return 0;
+
+ // Add the row to the result stack and update state
+ stackbuf[0] = theRow;
+ state->state += 1;
+ state->itemsPtr = stackbuf;
+ state->mutationsPtr = (unsigned long *)self;
+
+ return 1;
+}
+
+@end