aboutsummaryrefslogtreecommitdiffstats
path: root/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories
diff options
context:
space:
mode:
authorrowanbeentje <rowan@beent.je>2012-02-20 00:51:47 +0000
committerrowanbeentje <rowan@beent.je>2012-02-20 00:51:47 +0000
commitcc3c436432e592d15c46a4a9e38c6cb689eca0ee (patch)
tree2b0c17791b8a62ff2ab707a8bb082e563bec8c80 /Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories
parentbe5b96f6d79fecec53da93be6bb4d10df90e9ef5 (diff)
downloadsequelpro-cc3c436432e592d15c46a4a9e38c6cb689eca0ee.tar.gz
sequelpro-cc3c436432e592d15c46a4a9e38c6cb689eca0ee.tar.bz2
sequelpro-cc3c436432e592d15c46a4a9e38c6cb689eca0ee.zip
Initial commit of the new SPMySQL Framework, which is added to the project and ready for use but not yet integrated. This new framework should provide much of the functionality required from MCPKit and is based around its interface for relatively easy integration. The largest missing component is Hans' structure code which I believe is better placed outside the framework.
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/SPMySQLConnection Categories')
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Conversion.h60
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Conversion.m97
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Databases & Tables.h49
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Databases & Tables.m258
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Delegate & Proxy.h36
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Delegate & Proxy.m133
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.h53
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m414
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Locking.h41
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Locking.m104
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Max Packet Size.h40
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Max Packet Size.m196
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h58
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m227
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.h103
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m622
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h50
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m175
18 files changed, 2716 insertions, 0 deletions
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Conversion.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Conversion.h
new file mode 100644
index 00000000..0309ebdb
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Conversion.h
@@ -0,0 +1,60 @@
+//
+// $Id$
+//
+// Encoding.m
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on January 22, 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.
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// More info at <http://code.google.com/p/sequel-pro/>
+
+// This class is private to the framework.
+
+@interface SPMySQLConnection (Conversion)
+
++ (const char *)_cStringForString:(NSString *)aString usingEncoding:(NSStringEncoding)anEncoding returningLengthAs:(NSUInteger *)cStringLengthPointer;
+
+- (const char *)_cStringForString:(NSString *)aString;
+- (NSString *)_stringForCString:(const char *)cString;
+
+@end
+
+
+/**
+ * Set up a static function to allow fast calling with cached selectors
+ */
+static inline const char* _cStringForStringWithEncoding(NSString* aString, NSStringEncoding anEncoding, NSUInteger *cStringLengthPointer)
+{
+ static Class cachedClass;
+ static IMP cachedMethodPointer;
+ static SEL cachedSelector;
+
+ if (!cachedClass) cachedClass = [SPMySQLConnection class];
+ if (!cachedSelector) cachedSelector = @selector(_cStringForString:usingEncoding:returningLengthAs:);
+ if (!cachedMethodPointer) cachedMethodPointer = [SPMySQLConnection methodForSelector:cachedSelector];
+
+ return (const char *)(*cachedMethodPointer)(cachedClass, cachedSelector, aString, anEncoding, cStringLengthPointer);
+}
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Conversion.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Conversion.m
new file mode 100644
index 00000000..0a3cf99b
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Conversion.m
@@ -0,0 +1,97 @@
+//
+// $Id$
+//
+// Encoding.m
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on January 22, 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/>
+
+// This class is private to the framework.
+
+#import "Conversion.h"
+
+@implementation SPMySQLConnection (Conversion)
+
+/**
+ * Converts an NSString to a null-terminated C string, using the supplied encoding.
+ * Uses lossy conversion, so if a string cannot be entirely converted using
+ * the current encoding, a representation will be returned rather than null.
+ * The returned cString will correctly preserve any nul characters within the string,
+ * which prevents the use of faster functions like [NSString cStringUsingEncoding:].
+ * Pass in the third parameter to receive the length of the converted string, or pass
+ * in NULL if you do not want this information.
+ */
++ (const char *)_cStringForString:(NSString *)aString usingEncoding:(NSStringEncoding)anEncoding returningLengthAs:(NSUInteger *)cStringLengthPointer
+{
+
+ // Don't try and convert nil strings
+ if (!aString) return NULL;
+
+ // Perform a lossy conversion, using NSData to do the hard work
+ NSData *convertedData = [aString dataUsingEncoding:anEncoding allowLossyConversion:YES];
+ NSUInteger convertedDataLength = [convertedData length];
+
+ // Take the converted data - not null-terminated - and copy it to a null-terminated buffer
+ char *cStringBytes = malloc(convertedDataLength + 1);
+ memcpy(cStringBytes, [convertedData bytes], convertedDataLength);
+ cStringBytes[convertedDataLength] = 0L;
+
+ if (cStringLengthPointer) *cStringLengthPointer = convertedDataLength+1;
+
+ // Ensure the memory is autoreleased when needed, and return.
+ [NSData dataWithBytesNoCopy:cStringBytes length:convertedDataLength+1 freeWhenDone:YES];
+ return cStringBytes;
+}
+
+/**
+ * Converts an NSString to a null-terminated C string, using the current
+ * connection encoding.
+ */
+- (const char *)_cStringForString:(NSString *)aString
+{
+
+ // Use a cached reference to avoid dynamic method overhead
+ return _cStringForStringWithEncoding(aString, stringEncoding, NULL);
+}
+
+/**
+ * Converts a C string to an NSString using the supplied encoding.
+ * This method *will not* correctly preserve nul characters within c strings; instead
+ * the first nul character within the string will be treated as the line ending. This
+ * is unavoidable without supplying a string length, so this method should not be widely
+ * used for actual data conversion.
+ */
+- (NSString *)_stringForCString:(const char *)cString
+{
+
+ // Don't try and convert null strings
+ if (cString == NULL) return nil;
+
+ return [NSString stringWithCString:cString encoding:stringEncoding];
+}
+
+@end \ No newline at end of file
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Databases & Tables.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Databases & Tables.h
new file mode 100644
index 00000000..332b2680
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Databases & Tables.h
@@ -0,0 +1,49 @@
+//
+// $Id$
+//
+// Databases & Tables.h
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on February 11, 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/>
+
+
+@interface SPMySQLConnection (Databases_and_Tables)
+
+// Database selection
+- (BOOL)selectDatabase:(NSString *)aDatabase;
+
+// Database lists
+- (NSArray *)databases;
+- (NSArray *)databasesLike:(NSString *)nameLikeString;
+
+// Table lists
+- (NSArray *)tables;
+- (NSArray *)tablesLike:(NSString *)nameLikeString;
+- (NSArray *)tablesFromDatabase:(NSString *)aDatabase;
+- (NSArray *)tablesLike:(NSString *)nameLikeString fromDatabase:(NSString *)aDatabase;
+
+@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Databases & Tables.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Databases & Tables.m
new file mode 100644
index 00000000..a95e060e
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Databases & Tables.m
@@ -0,0 +1,258 @@
+//
+// $Id$
+//
+// Databases & Tables.m
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on February 11, 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 "Databases & Tables.h"
+#import "SPMySQL Private APIs.h"
+
+@implementation SPMySQLConnection (Databases_and_Tables)
+
+#pragma mark -
+#pragma mark Database selection
+
+/**
+ * Selects the database the connection should work with. Typically, a database should be
+ * set on a connection before any database-specific queries are run.
+ * Returns whether the database was correctly set or not.
+ * As MySQL does not support deselecting databases, a nil databaseName will return NO.
+ */
+- (BOOL)selectDatabase:(NSString *)aDatabase
+{
+
+ // If no database was supplied, can't deselected - return NO.
+ if (!aDatabase) return NO;
+
+ // Database selection should be made in UTF8 to avoid name encoding issues
+ BOOL encodingChangeRequired = [self _storeAndAlterEncodingToUTF8IfRequired];
+
+ // Attempt to select the supplied database
+ [self queryString:[NSString stringWithFormat:@"USE %@", [aDatabase mySQLBacktickQuotedString]]];
+
+ // If selecting the database failed, return failure.
+ if ([self queryErrored]) {
+
+ // If the encoding needs to be restored, the error message and ID have to be stored so the
+ // actual error is still available to inspect on the class...
+ if (encodingChangeRequired) {
+ NSString *theErrorString = [self lastErrorMessage];
+ NSUInteger theErrorID = [self lastErrorID];
+
+ [self restoreStoredEncoding];
+
+ [self _updateLastErrorMessage:theErrorString];
+ [self _updateLastErrorID:theErrorID];
+ }
+
+ return NO;
+ }
+
+ // Restore the connection encoding if necessary
+ if (encodingChangeRequired) [self restoreStoredEncoding];
+
+ // Store new database name and return success
+ if (database) [database release];
+ database = [[NSString alloc] initWithString:aDatabase];
+
+ return YES;
+}
+
+#pragma mark -
+#pragma mark Database lists
+
+/**
+ * Retrieve an array of databases available to the current user, ordered as MySQL
+ * returns them.
+ * If an error occurred while retrieving the list of databases, nil will be returned;
+ * if no databases are available, an empty array will be returned.
+ */
+- (NSArray *)databases
+{
+
+ // Wrap the related databasesLike: function to avoid code duplication
+ return [self databasesLike:nil];
+}
+
+/**
+ * Retrieve an array of databases whose names are matched against the supplied name
+ * using MySQL LIKE syntax (with wildcard support for % and _). If no name is supplied,
+ * all databases will be returned, in the order that MySQL returns them.
+ * If an error occurred while retrieving the list of databases, nil will be returned;
+ * if no matching databases are available, an empty array will be returned.
+ */
+- (NSArray *)databasesLike:(NSString *)nameLikeString
+{
+ NSMutableArray *databaseList = nil;
+
+ // Database display should be made in UTF8 to avoid name encoding issues
+ BOOL encodingChangeRequired = [self _storeAndAlterEncodingToUTF8IfRequired];
+
+ // Build the query as appropriate
+ NSMutableString *databaseQuery = [NSMutableString stringWithString:@"SHOW DATABASES"];
+ if ([nameLikeString length]) {
+ [databaseQuery appendFormat:@" LIKE %@", [nameLikeString mySQLTickQuotedString]];
+ }
+
+ // Perform the query and record state
+ SPMySQLResult *databaseResult = [self queryString:databaseQuery];
+ [databaseResult setDefaultRowReturnType:SPMySQLResultRowAsArray];
+
+ // Retrieve the result into an array if the query was successful
+ if (![self queryErrored]) {
+ databaseList = [NSMutableArray arrayWithCapacity:(NSUInteger)[databaseResult numberOfRows]];
+ for (NSArray *dbRow in databaseResult) {
+ [databaseList addObject:[dbRow objectAtIndex:0]];
+ }
+ }
+
+ // Restore the connection encoding if necessary
+ if (encodingChangeRequired) [self restoreStoredEncoding];
+
+ return databaseList;
+}
+
+#pragma mark -
+#pragma mark Table lists
+
+/**
+ * Retrieve an array of tables in the currently selected database, ordered as MySQL
+ * returns them.
+ * If an error occurred while retrieving the list of tables, nil will be returned;
+ * if no tables are present, an empty array will be returned.
+ */
+- (NSArray *)tables
+{
+
+ // Wrap the related tablesLike:fromDatabase: function to avoid code duplication
+ return [self tablesLike:nil fromDatabase:nil];
+}
+
+/**
+ * Retrieve an array of tables in the currently selected database whose names are
+ * matched against the supplied name using MySQL LIKE syntax (with wildcard
+ * support for % and _). If no name is supplied, all tables in the selected
+ * database will be returned, in the order that MySQL returns them.
+ * If an error occurred while retrieving the list of tables, nil will be returned;
+ * if no matching tables are present, an empty array will be returned.
+ */
+- (NSArray *)tablesLike:(NSString *)nameLikeString
+{
+
+ // Wrap the related tablesLike:fromDatabase: function to avoid code duplication
+ return [self tablesLike:nameLikeString fromDatabase:nil];
+
+}
+
+/**
+ * Retrieve an array of tables in the specified database, ordered as MySQL returns them.
+ * If no database is specified, the current database will be used.
+ * If an error occurred while retrieving the list of tables, nil will be returned;
+ * if no tables are present in the specified database, an empty array will be returned.
+ */
+- (NSArray *)tablesFromDatabase:(NSString *)aDatabase
+{
+
+ // Wrap the related tablesLike:fromDatabase: function to avoid code duplication
+ return [self tablesLike:nil fromDatabase:aDatabase];
+
+}
+
+/**
+ * Retrieve an array of tables in the specified database whose names are matched
+ * against the supplied name using MySQL LIKE syntax (with wildcard support
+ * for % and _). If no name is supplied, all tables in the specified database
+ * will be returned, in the order that MySQL returns them.
+ * If no database is specified, the current database will be used.
+ * If an error occurred while retrieving the list of tables, nil will be returned;
+ * if no matching tables are present in the specified database, an empty array
+ * will be returned.
+ */
+- (NSArray *)tablesLike:(NSString *)nameLikeString fromDatabase:(NSString *)aDatabase
+{
+ NSMutableArray *tableList = nil;
+
+ // Table display should be made in UTF8 to avoid name encoding issues
+ BOOL encodingChangeRequired = [self _storeAndAlterEncodingToUTF8IfRequired];
+
+ // Build up the table lookup query
+ NSMutableString *tableQuery = [NSMutableString stringWithString:@"SHOW TABLES"];
+ if ([aDatabase length]) {
+ [tableQuery appendFormat:@" FROM %@", [aDatabase mySQLBacktickQuotedString]];
+ }
+ if ([nameLikeString length]) {
+ [tableQuery appendFormat:@" LIKE %@", [nameLikeString mySQLTickQuotedString]];
+ }
+
+ // Perform the query and record state
+ SPMySQLResult *tableResult = [self queryString:tableQuery];
+ [tableResult setDefaultRowReturnType:SPMySQLResultRowAsArray];
+
+ // Retrieve the result into an array if the query was successful
+ if (![self queryErrored]) {
+ tableList = [NSMutableArray arrayWithCapacity:(NSUInteger)[tableResult numberOfRows]];
+ for (NSArray *tableRow in tableResult) {
+ [tableList addObject:[tableRow objectAtIndex:0]];
+ }
+ }
+
+ // Restore the connection encoding if necessary
+ if (encodingChangeRequired) [self restoreStoredEncoding];
+
+ return tableList;
+}
+
+@end
+
+#pragma mark -
+#pragma mark Private API
+
+@implementation SPMySQLConnection (Databases_and_Tables_Private_API)
+
+/**
+ * A number of queries regarding database or table information have to be made in UTF8, not
+ * in the connection encoding, so that names can be fully displayed and used even if they
+ * use a different encoding. This provides a convenience method to check whether a change
+ * is required; if so, the current encoding is stored, the encoding is changed, and YES is
+ * returned so the process can be reversed afterwards.
+ */
+- (BOOL)_storeAndAlterEncodingToUTF8IfRequired
+{
+
+ // If the encoding is already UTF8, no change is required.
+ if ([encoding isEqualToString:@"utf8"] && !encodingUsesLatin1Transport) return NO;
+
+ // Store the current encoding for restoration afterwards, and update encoding
+ [self storeEncodingForRestoration];
+ [self setEncoding:@"utf8"];
+
+ return YES;
+}
+
+@end \ No newline at end of file
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Delegate & Proxy.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Delegate & Proxy.h
new file mode 100644
index 00000000..cf132fcf
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Delegate & Proxy.h
@@ -0,0 +1,36 @@
+//
+// $Id$
+//
+// Delegate & Proxy.h
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on February 9, 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/>
+
+
+@interface SPMySQLConnection (Delegate_and_Proxy)
+
+@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Delegate & Proxy.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Delegate & Proxy.m
new file mode 100644
index 00000000..3ac013cc
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Delegate & Proxy.m
@@ -0,0 +1,133 @@
+//
+// $Id$
+//
+// Delegate & Proxy.m
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on February 9, 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 "Delegate & Proxy.h"
+#import "SPMySQL Private APIs.h"
+
+@implementation SPMySQLConnection (Delegate_and_Proxy)
+
+#pragma mark -
+#pragma mark Connection delegate
+
+/**
+ * Override the synthesized delegate setter, to allow optimisations to oft-made
+ * checks by precacheing availability.
+ */
+- (void)setDelegate:(NSObject <SPMySQLConnectionDelegate> *)aDelegate
+{
+ delegate = aDelegate;
+
+ // Cache whether the delegate implements certain delegate methods
+ delegateSupportsWillQueryString = [delegate respondsToSelector:@selector(willQueryString:connection:)];
+ delegateSupportsConnectionLost = [delegate respondsToSelector:@selector(connectionLost:)];
+}
+
+#pragma mark -
+#pragma mark Connection proxy
+
+/**
+ * Override the synthesized proxy setter, to record the initial state and to
+ * set the state change selector.
+ */
+- (void)setProxy:(NSObject <SPMySQLConnectionProxy> *)aProxy
+{
+ proxy = [aProxy retain];
+ previousProxyState = [aProxy state];
+
+ [proxy setConnectionStateChangeSelector:@selector(_proxyStateChange:) delegate:self];
+}
+
+@end
+
+#pragma mark -
+
+@implementation SPMySQLConnection (Delegate_and_Proxy_Private_API)
+
+/**
+ * Handle any state changes in the associated connection proxy.
+ */
+- (void)_proxyStateChange:(NSObject <SPMySQLConnectionProxy> *)aProxy
+{
+
+ // Perform no actions if this isn't the current connection proxy, or if notifications
+ // are currently set to be ignored
+ if (aProxy != proxy || proxyStateChangeNotificationsIgnored) return;
+
+ SPMySQLConnectionProxyState newState = [aProxy state];
+
+ // If the connection proxy disconnects, trigger a reconnect; use a new thread to allow the
+ // main thread to process events as required.
+ if (state == SPMySQLConnected && newState == SPMySQLProxyIdle && previousProxyState == SPMySQLProxyConnected) {
+
+ // Clear the state change selector on the proxy until a connection is re-established
+ proxyStateChangeNotificationsIgnored = YES;
+
+ // Trigger a reconnect
+ [NSThread detachNewThreadSelector:@selector(reconnect) toTarget:self withObject:nil];
+ }
+
+ // Update the state record
+ previousProxyState = newState;
+}
+
+/**
+ * Ask the delegate for the connection lost decision. This can be called from
+ * any thread, and will call itself on the main thread if necessary, updating a global
+ * variable which is then returned on the child thread.
+ */
+- (SPMySQLConnectionLostDecision)_delegateDecisionForLostConnection
+{
+ SPMySQLConnectionLostDecision theDecision = SPMySQLConnectionLostDisconnect;
+
+ // If on the main thread, ask the delegate directly.
+ if ([NSThread isMainThread]) {
+ [delegateDecisionLock lock];
+ lastDelegateDecisionForLostConnection = [delegate connectionLost:self];
+ theDecision = lastDelegateDecisionForLostConnection;
+ [delegateDecisionLock unlock];
+
+ // Otherwise call ourself on the main thread, waiting until the reply is received.
+ } else {
+
+ // First check whether the application is in a modal state; if so, wait
+ while ([NSApp modalWindow]) usleep(100000);
+
+ [self performSelectorOnMainThread:@selector(_delegateDecisionForLostConnection) withObject:nil waitUntilDone:YES];
+ [delegateDecisionLock lock];
+ theDecision = lastDelegateDecisionForLostConnection;
+ [delegateDecisionLock unlock];
+ }
+
+ return theDecision;
+}
+
+@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.h
new file mode 100644
index 00000000..bb5bf25d
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.h
@@ -0,0 +1,53 @@
+//
+// $Id$
+//
+// Encoding.h
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on January 14, 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/>
+
+
+@interface SPMySQLConnection (Encoding)
+
+// Current connection encoding information
+- (NSString *)encoding;
+- (NSStringEncoding)stringEncoding;
+- (BOOL)encodingUsesLatin1Transport;
+
+// Setting connection encoding
+- (BOOL)setEncoding:(NSString *)theEncoding;
+- (BOOL)setEncodingUsesLatin1Transport:(BOOL)useLatin1;
+
+// Encoding storage and restoration
+- (void)storeEncodingForRestoration;
+- (void)restoreStoredEncoding;
+
+// Encoding conversion
++ (NSStringEncoding)stringEncodingForMySQLCharset:(const char *)mysqlCharset;
++ (NSString *)mySQLCharsetForStringEncoding:(NSStringEncoding)aStringEncoding;
+
+@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m
new file mode 100644
index 00000000..665e7697
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m
@@ -0,0 +1,414 @@
+//
+// $Id$
+//
+// Encoding.m
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on January 14, 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 "Encoding.h"
+
+@implementation SPMySQLConnection (Encoding)
+
+#pragma mark -
+#pragma mark Current connection encoding information
+
+/**
+ * Returns the name of the current encoding - the MySQL character set - in
+ * use by the connection.
+ */
+- (NSString *)encoding
+{
+ return [NSString stringWithString:encoding];
+}
+
+/**
+ * Returns the NSStringEncoding currently in use by the connection to process
+ * queries and results.
+ */
+- (NSStringEncoding)stringEncoding
+{
+ return stringEncoding;
+}
+
+/**
+ * Returns whether the connection is set to use Latin1 transport for queries and
+ * results.
+ * Latin1 transport is a compatibility mode in place for compatibility with older
+ * incorrect setups, where databases and clients might both be set to use UTF8 (or
+ * other encodings) for storing and retrieving data, but the MySQL link was never
+ * set to UTF8 mode; as a result, multibyte characters where split by the connection
+ * into pairs of characters, resulting in malformed storage. The data works
+ * correctly if written and read in the same way, so this mode allows correct display
+ * of that data.
+ */
+- (BOOL)encodingUsesLatin1Transport
+{
+ return encodingUsesLatin1Transport;
+}
+
+#pragma mark -
+#pragma mark Setting connection encoding
+
+/**
+ * Set the name of the encoding - the MySQL character set - that the connection
+ * should use. If an encoding not recognised by the server is supplied, NO is
+ * returned.
+ * Calling this resets whether the connection should use Latin1 transport to NO.
+ */
+- (BOOL)setEncoding:(NSString *)theEncoding
+{
+
+ // MySQL versions prior to 4.1 don't support encoding changes; return NO on those
+ // versions.
+ if (![self serverVersionIsGreaterThanOrEqualTo:4 minorVersion:1 releaseVersion:0]) {
+ return NO;
+ }
+
+ // If the supplied encoding is already set, return success
+ if ([encoding isEqualToString:theEncoding] && !encodingUsesLatin1Transport) {
+ return YES;
+ }
+
+ // Run a query to set the connection encoding
+ [self queryString:[NSString stringWithFormat:@"SET NAMES %@", [theEncoding mySQLTickQuotedString]]];
+
+ // If the query errored, no encoding change occurred - return failure.
+ if ([self queryErrored]) return NO;
+
+ // Connection encoding was successfully set, update the instance settings,
+ // and return success.
+ [encoding release];
+ encoding = [[NSString alloc] initWithString:theEncoding];
+ stringEncoding = [SPMySQLConnection stringEncodingForMySQLCharset:[theEncoding UTF8String]];
+ encodingUsesLatin1Transport = NO;
+
+ return YES;
+}
+
+/**
+ * Sets the connection to use Latin1 transport for queries and results or not. All
+ * encodings will default to not use Latin1 transport..
+ * Latin1 transport is a compatibility mode in place for compatibility with older
+ * incorrect setups, where databases and clients might both be set to use UTF8 (or
+ * other encodings) for storing and retrieving data, but the MySQL link was never
+ * set to UTF8 mode; as a result, multibyte characters where split by the connection
+ * into pairs of characters, resulting in malformed storage. The data works
+ * correctly if written and read in the same way, so this mode allows correct display
+ * of that data.
+ */
+- (BOOL)setEncodingUsesLatin1Transport:(BOOL)useLatin1
+{
+
+ // MySQL versions prior to 4.1 don't support encoding changes; return NO on those
+ // versions.
+ if (![self serverVersionIsGreaterThanOrEqualTo:4 minorVersion:1 releaseVersion:0]) {
+ return NO;
+ }
+
+ // If the Latin1 mode is already set, return success
+ if (encodingUsesLatin1Transport == useLatin1) {
+ return YES;
+ }
+
+ // If disabling Latin1 transport, just restore the connection encoding
+ if (!useLatin1) {
+ return [self setEncoding:encoding];
+ }
+
+ // Otherwise attempt to set Latin1 transport. First, the result set encoding.
+ [self queryString:@"SET CHARACTER_SET_RESULTS=latin1"];
+
+ // If that failed, no encoding change occurred - return failure.
+ if ([self queryErrored]) return NO;
+
+ // Next, change the client character set, to also amend queries sent.
+ [self queryString:@"SET CHARACTER_SET_CLIENT=latin1"];
+
+ // If that failed, encoding details are in a partial state - attempt to restore
+ // the original details before returning failure.
+ if ([self queryErrored]) {
+ [self setEncoding:encoding];
+ return NO;
+ }
+
+ // Connecting encoding transport was successfully set, update the instance settings,
+ // and return success.
+ encodingUsesLatin1Transport = YES;
+ return YES;
+}
+
+#pragma mark -
+#pragma mark Encoding storage and restoration
+
+
+/**
+ * Store a previous encoding setting, to allow it to be easily restored
+ * later - used when the encoding needs to be temporarily changed.
+ */
+- (void)storeEncodingForRestoration
+{
+ if (previousEncoding) [previousEncoding release];
+ previousEncoding = [[NSString alloc] initWithString:encoding];
+ previousEncodingUsesLatin1Transport = encodingUsesLatin1Transport;
+}
+
+/**
+ * Restore a previously stored encoding setting, if available. Used in
+ * conjunection with -storeEncodingForRestoration for when the encoding needs
+ * to be temporarily changed.
+ */
+- (void)restoreStoredEncoding
+{
+ if (!previousEncoding || state == SPMySQLDisconnected || state == SPMySQLDisconnecting) {
+ return;
+ }
+
+ [self setEncoding:previousEncoding];
+ [self setEncodingUsesLatin1Transport:previousEncodingUsesLatin1Transport];
+}
+
+#pragma mark -
+#pragma mark Encoding conversion
+
+/**
+ * Map MySQL encodings to NSStringEncodings, using the list of encodings sourced
+ * from http://dev.mysql.com/doc/refman/5.6/en/charset-charsets.html and the same
+ * list on previous MySQL versions. Older versions also had less-standard lists,
+ * such as the charset options listed on
+ * http://dev.mysql.com/doc/refman/4.1/en/charset-map.html .
+ * For each, the equivalent NSStringEncoding, or conversion from CfStringEncoding,
+ * was found.
+ * If a supplied character set can not be matched, logs an error and falls back
+ * to UTF8 encoding.
+ */
++ (NSStringEncoding)stringEncodingForMySQLCharset:(const char *)mysqlCharset
+{
+
+ // Handle the most common cases first
+ if (!strcmp(mysqlCharset, "utf8")) {
+ return NSUTF8StringEncoding;
+ } else if (!strcmp(mysqlCharset, "latin1")) {
+ return NSISOLatin1StringEncoding;
+ } else if (!strcmp(mysqlCharset, "ascii")) {
+ return NSASCIIStringEncoding;
+
+ // Work down the rest of the 4.1+ charsets
+ } else if (!strcmp(mysqlCharset, "big5")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingBig5);
+ } else if (!strcmp(mysqlCharset, "dec8")) {
+ return NSISOLatin1StringEncoding; // Not exact, but very close
+ } else if (!strcmp(mysqlCharset, "cp850")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingDOSLatin1);
+ } else if (!strcmp(mysqlCharset, "koi8r")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingKOI8_R);
+ } else if (!strcmp(mysqlCharset, "latin2")) {
+ return NSISOLatin2StringEncoding;
+ } else if (!strcmp(mysqlCharset, "ujis")) {
+ return NSJapaneseEUCStringEncoding;
+ } else if (!strcmp(mysqlCharset, "sjis")) {
+ return NSShiftJISStringEncoding;
+ } else if (!strcmp(mysqlCharset, "hebrew")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingISOLatinHebrew);
+ } else if (!strcmp(mysqlCharset, "tis620")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingISOLatinThai);
+ } else if (!strcmp(mysqlCharset, "euckr")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingEUC_KR);
+ } else if (!strcmp(mysqlCharset, "koi8u")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingKOI8_U);
+ } else if (!strcmp(mysqlCharset, "gb2312")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_2312_80);
+ } else if (!strcmp(mysqlCharset, "greek")) {
+ return NSWindowsCP1253StringEncoding;
+ } else if (!strcmp(mysqlCharset, "cp1250")) {
+ return NSWindowsCP1250StringEncoding;
+ } else if (!strcmp(mysqlCharset, "gbk")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGBK_95);
+ } else if (!strcmp(mysqlCharset, "latin5")) {
+ return NSWindowsCP1254StringEncoding;
+ } else if (!strcmp(mysqlCharset, "ucs2")) {
+ return NSUnicodeStringEncoding;
+ } else if (!strcmp(mysqlCharset, "cp866")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingDOSRussian);
+ } else if (!strcmp(mysqlCharset, "macce")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingMacCentralEurRoman);
+ } else if (!strcmp(mysqlCharset, "macroman")) {
+ return NSMacOSRomanStringEncoding;
+ } else if (!strcmp(mysqlCharset, "cp852")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingDOSLatin2);
+ } else if (!strcmp(mysqlCharset, "latin7")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingISOLatin7);
+ } else if (!strcmp(mysqlCharset, "utf8mb4")) {
+ return NSUnicodeStringEncoding; // Is this correct?
+ } else if (!strcmp(mysqlCharset, "cp1251")) {
+ return NSWindowsCP1251StringEncoding;
+ } else if (!strcmp(mysqlCharset, "utf16")) {
+ return NSUnicodeStringEncoding;
+ } else if (!strcmp(mysqlCharset, "utf16le")) {
+ return NSUTF16LittleEndianStringEncoding;
+ } else if (!strcmp(mysqlCharset, "cp1256")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingWindowsArabic);
+ } else if (!strcmp(mysqlCharset, "cp1257")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingWindowsBalticRim);
+ } else if (!strcmp(mysqlCharset, "utf32")) {
+ return NSUTF32StringEncoding;
+ } else if (!strcmp(mysqlCharset, "binary")) {
+ return NSUTF8StringEncoding;
+ } else if (!strcmp(mysqlCharset, "cp932")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingDOSJapanese);
+ } else if (!strcmp(mysqlCharset, "eucjpms")) {
+ return CFStringConvertEncodingToNSStringEncoding(NSJapaneseEUCStringEncoding);
+
+ // Continue with old < 4.1 mappings
+ } else if (!strcmp(mysqlCharset, "czech")) {
+ return NSISOLatin2StringEncoding;
+ } else if (!strcmp(mysqlCharset, "dos")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingDOSLatin1);
+ } else if (!strcmp(mysqlCharset, "german1")) {
+ return NSISOLatin1StringEncoding;
+ } else if (!strcmp(mysqlCharset, "usa7")) {
+ return NSASCIIStringEncoding;
+ } else if (!strcmp(mysqlCharset, "danish")) {
+ return NSISOLatin1StringEncoding;
+ } else if (!strcmp(mysqlCharset, "win1251")) {
+ return NSWindowsCP1251StringEncoding;
+ } else if (!strcmp(mysqlCharset, "euc_kr")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingEUC_KR);
+ } else if (!strcmp(mysqlCharset, "estonia")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingISOLatin7);
+ } else if (!strcmp(mysqlCharset, "hungarian")) {
+ return NSISOLatin2StringEncoding;
+ } else if (!strcmp(mysqlCharset, "koi8_ru")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingKOI8_R);
+ } else if (!strcmp(mysqlCharset, "koi8_ukr")) {
+ return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingKOI8_U);
+ } else if (!strcmp(mysqlCharset, "win1251ukr")) {
+ return NSWindowsCP1251StringEncoding;
+ } else if (!strcmp(mysqlCharset, "win1250")) {
+ return NSWindowsCP1250StringEncoding;
+ } else if (!strcmp(mysqlCharset, "croat")) {
+ return NSISOLatin2StringEncoding;
+ } else if (!strcmp(mysqlCharset, "latin1_de")) {
+ return NSISOLatin1StringEncoding;
+ }
+
+ /**
+ * Finally, certain other encodings, including the following:
+ * hp8
+ * swe7
+ * armscii8
+ * keybcs2
+ * geostd8
+ * ...don't appear to have OS X equivalents; for these and unhandled, log and
+ * fall back to UTF8 handling.
+ */
+ NSLog(@"SPMySQL Framework has encountered the MySQL encoding '%s' which it is unable to process correctly; falling back to UTF8 mapping.", mysqlCharset);
+ return NSUTF8StringEncoding;
+}
+
+/**
+ * Match a supplied NSStringEncoding to a MySQL character set, returning the MySQL
+ * name of that character set as an NSString.
+ * If the supplied NSStringEncoding could not be matched, logs an error and returns nil.
+ */
++ (NSString *)mySQLCharsetForStringEncoding:(NSStringEncoding)aStringEncoding
+{
+
+ // Switch through the list of NSStringEncodings from NSString, returning the most
+ // appropriate encoding for each
+ switch (aStringEncoding) {
+
+ case NSASCIIStringEncoding:
+ return @"ascii";
+
+ case NSJapaneseEUCStringEncoding:
+ return @"ujis";
+
+ case NSUTF8StringEncoding:
+ return @"utf8";
+
+ case NSISOLatin1StringEncoding:
+ return @"latin1";
+
+ case NSNonLossyASCIIStringEncoding:
+ return @"utf8";
+
+ case NSShiftJISStringEncoding:
+ return @"sjis";
+
+ case NSISOLatin2StringEncoding:
+ return @"latin2";
+
+ case NSUnicodeStringEncoding:
+ return @"ucs2";
+
+ case NSWindowsCP1251StringEncoding:
+ return @"cp1251";
+
+ case NSWindowsCP1252StringEncoding:
+ return @"latin1";
+
+ case NSWindowsCP1253StringEncoding:
+ return @"greek";
+
+ case NSWindowsCP1254StringEncoding:
+ return @"latin5";
+
+ case NSWindowsCP1250StringEncoding:
+ return @"cp1250";
+
+ case NSMacOSRomanStringEncoding:
+ return @"macroman";
+
+ case NSUTF16BigEndianStringEncoding:
+ return @"utf16";
+
+ case NSUTF16LittleEndianStringEncoding:
+ return @"utf16le";
+
+ case NSUTF32StringEncoding:
+ return @"utf32";
+
+ case NSUTF32BigEndianStringEncoding:
+ return @"utf32";
+ }
+
+ /**
+ * Certain string encodings, including the following:
+ * NSNEXTSTEPStringEncoding
+ * NSSymbolStringEncoding
+ * NSISO2022JPStringEncoding
+ * NSUTF32LittleEndianStringEncoding
+ *
+ * ...don't have equivalents; similarly, many CFStringEncodings aren't yet
+ * matched. For those, log and return nil.
+ */
+ NSLog(@"SPMySQL Framework was asked for the MySQL charset for the string encoding '%llu', which is currently unhandled.", (unsigned long long)aStringEncoding);
+ return nil;
+}
+@end \ No newline at end of file
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Locking.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Locking.h
new file mode 100644
index 00000000..90e11179
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Locking.h
@@ -0,0 +1,41 @@
+//
+// $Id$
+//
+// Locking.h
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on January 22, 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/>
+
+// This class is private to the framework.
+
+@interface SPMySQLConnection (Locking)
+
+- (void)_lockConnection;
+- (BOOL)_tryLockConnection;
+- (void)_unlockConnection;
+
+@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Locking.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Locking.m
new file mode 100644
index 00000000..d654066b
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Locking.m
@@ -0,0 +1,104 @@
+//
+// $Id$
+//
+// Locking.m
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on January 22, 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/>
+
+// This class is private to the framework.
+
+#import "Locking.h"
+#import "SPMySQL Private APIs.h"
+
+@implementation SPMySQLConnection (Locking)
+
+
+/**
+ * Lock the connection. This must be done before performing any operation
+ * that is not thread safe, eg. performing queries or pinging.
+ */
+- (void)_lockConnection
+{
+
+ // We can only start a query when the condition is SPMySQLConnectionIdle
+ [connectionLock lockWhenCondition:SPMySQLConnectionIdle];
+
+ // Set the condition to SPMySQLConnectionBusy
+ [connectionLock unlockWithCondition:SPMySQLConnectionBusy];
+}
+
+/**
+ * Attempt to lock the connection. If the connection is idle (unlocked), this method
+ * locks the connection and returns YES for success. The connection must afterward
+ * be unlocked using unlockConnection. If the connection is currently busy (locked),
+ * this method immediately returns NO and doesn't lock the connection.
+ */
+- (BOOL)_tryLockConnection
+{
+
+ // If the connection is already is use, return failure
+ if (![connectionLock tryLockWhenCondition:SPMySQLConnectionIdle]) {
+ return NO;
+ }
+
+ // We're allowed to use the connection; set it to busy, and return success
+ [connectionLock unlockWithCondition:SPMySQLConnectionBusy];
+ return YES;
+}
+
+
+/**
+ * Unlock the connection.
+ */
+- (void)_unlockConnection
+{
+
+ // Always lock the conditional lock before proceeding
+ [connectionLock lock];
+
+ // Check if the connection actually was busy. If it wasn't busy,
+ // it means the connection may have been unlocked twice. This is
+ // potentially dangerous, so we log this to the console
+ if ([connectionLock condition] != SPMySQLConnectionBusy) {
+ NSLog(@"SPMySQLConnection: Tried to unlock the connection, but it wasn't locked.");
+ }
+
+ // Since we connected with CLIENT_MULTI_RESULT, we must make sure there are not more results!
+ // This is still a bit of a dirty hack
+ if (state == SPMySQLConnected
+ && mySQLConnection && mySQLConnection->net.vio && mySQLConnection->net.buff && mysql_more_results(mySQLConnection))
+ {
+ NSLog(@"SPMySQLConnection: Discarding unretrieved results. This is currently normal when using CALL.");
+ [self _flushMultipleResultSets];
+ }
+
+ // Tell everyone that the connection is available again
+ [connectionLock unlockWithCondition:SPMySQLConnectionIdle];
+}
+
+@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Max Packet Size.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Max Packet Size.h
new file mode 100644
index 00000000..faa667d8
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Max Packet Size.h
@@ -0,0 +1,40 @@
+//
+// $Id$
+//
+// Max Packet Size.h
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on February 9, 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/>
+
+
+@interface SPMySQLConnection (Max_Packet_Size)
+
+- (NSUInteger)maxQuerySize;
+- (BOOL)isMaxQuerySizeEditable;
+- (NSUInteger)setGlobalMaxQuerySize:(NSUInteger)newMaxSize;
+
+@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Max Packet Size.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Max Packet Size.m
new file mode 100644
index 00000000..e0bfef52
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Max Packet Size.m
@@ -0,0 +1,196 @@
+//
+// $Id$
+//
+// Max Packet Size.m
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on February 9, 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 "Max Packet Size.h"
+#import "SPMySQL Private APIs.h"
+
+@implementation SPMySQLConnection (Max_Packet_Size)
+
+/**
+ * Retrieve the current maximum query size (MySQL's max_allowed_packet), as cached
+ * by the class. If the connection has been unable to retrieve this value, the
+ * default of 1MB will be returned.
+ */
+- (NSUInteger)maxQuerySize
+{
+ return maxQuerySize;
+}
+
+/**
+ * Retrieve whether the server's maximum query size (MySQL's max_allowed_packet) is
+ * editable by the current user.
+ */
+- (BOOL)isMaxQuerySizeEditable
+{
+ if (!maxQuerySizeEditabilityChecked) {
+ [self _updateMaxQuerySizeEditability];
+ }
+
+ return maxQuerySizeIsEditable;
+}
+
+/**
+ * Set the servers's global maximum query size - MySQL's max_allowed_packed - to the
+ * supplied size. Note that this *does not* affect the current connection; a reconnection
+ * is required to pick up the new size setting. As a result it may be important to restore
+ * the connection size after use.
+ * Validates the supplied size (eg 1GB limit) and applies it if appropriate, returning
+ * the set query size or NSNotFound on error.
+ */
+- (NSUInteger)setGlobalMaxQuerySize:(NSUInteger)newMaxSize
+{
+
+ // Perform basic validation. First, ensure the max query size is editable
+ if (![self isMaxQuerySizeEditable]) return NSNotFound;
+
+ // Validate sizes
+ if (newMaxSize < 1024) return NSNotFound;
+ if (newMaxSize > (1024 * 1024 * 1024)) newMaxSize = 1024 * 1024 * 1024;
+
+ // Perform a standard query to set the new size
+ [self queryString:[NSString stringWithFormat:@"SET GLOBAL max_allowed_packet = %lu", newMaxSize]];
+
+ // On failure, return NSNotFound - error state will have automatically been set
+ if ([self queryErrored]) return NSNotFound;
+
+ // Otherwise, set the local instance variable and return success
+ maxQuerySize = newMaxSize;
+ return maxQuerySize;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SPMySQLConnection (Max_Packet_Size_Private_API)
+
+/**
+ * Update the max_allowed_packet size - the largest supported query size - from the server.
+ */
+- (void)_updateMaxQuerySize
+{
+
+ // Determine which query to run based on server version
+ NSString *packetQueryString;
+ if ([self serverMajorVersion] == 3) {
+ packetQueryString = @"SHOW VARIABLES LIKE 'max_allowed_packet'";
+ } else {
+ packetQueryString = @"SELECT @@global.max_allowed_packet";
+ }
+
+ // Make a standard query to the server to retrieve the information
+ SPMySQLResult *result = [self queryString:packetQueryString];
+ [result setReturnDataAsStrings:YES];
+
+ // Get the maximum size string
+ NSString *maxQuerySizeString = nil;
+ if ([self serverMajorVersion] == 3) {
+ maxQuerySizeString = [[result getRowAsArray] objectAtIndex:1];
+ } else {
+ maxQuerySizeString = [[result getRowAsArray] objectAtIndex:0];
+ }
+
+ // If a valid size was returned, update the instance variable
+ if (maxQuerySizeString) {
+ maxQuerySize = (NSUInteger)[maxQuerySizeString integerValue];
+ }
+}
+
+/**
+ * Perform a query to determine whether the current user has permission to edit the
+ * max_allowed_packet setting for their connection.
+ */
+- (void)_updateMaxQuerySizeEditability
+{
+ [self queryString:@"SET GLOBAL max_allowed_packet = @@global.max_allowed_packet"];
+ maxQuerySizeIsEditable = ![self queryErrored];
+ maxQuerySizeEditabilityChecked = YES;
+}
+
+/**
+ * Attempts to change the maximum query size in order to allow a query to be performed.
+ * Returns whether the change was successfully made.
+ */
+- (BOOL)_attemptMaxQuerySizeIncreaseTo:(NSUInteger)targetSize
+{
+
+ // If the query size is editable, attempt to increase the size
+ if ([self isMaxQuerySizeEditable]) {
+ NSUInteger newSize = [self setGlobalMaxQuerySize:targetSize];
+ if (newSize != NSNotFound) {
+
+ // Successfully increased the global size - reconnect to use it, and return success
+ [self reconnect];
+ return YES;
+ }
+ }
+
+ // Can not, or failed to, increase the max query size. Record an error message.
+ NSString *errorMessage = [NSString stringWithFormat:NSLocalizedString(@"The query length of %lu bytes is larger than max_allowed_packet size (%lu).", @"error message if max_allowed_packet < query size"), targetSize, maxQuerySize];
+ [self _updateLastErrorMessage:errorMessage];
+
+ // Update delegate error if it supports the protocol
+ if ([delegate respondsToSelector:@selector(queryGaveError:connection:)]) {
+ [delegate queryGaveError:errorMessage connection:self];
+ }
+
+ // Display an alert as this is a special failure
+ if ([delegate respondsToSelector:@selector(showErrorWithTitle:message:)]) {
+ [delegate showErrorWithTitle:NSLocalizedString(@"Error", @"error") message:errorMessage];
+ } else {
+ NSRunAlertPanel(NSLocalizedString(@"Error", @"error"), errorMessage, @"OK", nil, nil);
+ }
+
+ return NO;
+}
+
+/**
+ * Restore a maximum query size after temporarily increasing it for a query. This action
+ * may be called directly after a query, or may be before the next query if a streaming result
+ * had to be used.
+ */
+- (void)_restoreMaximumQuerySizeAfterQuery
+{
+
+ // Return if no action needs to be performed
+ if (queryActionShouldRestoreMaxQuerySize == NSNotFound) return;
+
+ // Move the target size to a local variable to prevent looping
+ NSUInteger targetMaxQuerySize = queryActionShouldRestoreMaxQuerySize;
+ queryActionShouldRestoreMaxQuerySize = NSNotFound;
+
+ // Enact the change
+ [self setGlobalMaxQuerySize:targetMaxQuerySize];
+}
+
+@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h
new file mode 100644
index 00000000..3788c653
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h
@@ -0,0 +1,58 @@
+//
+// $Id$
+//
+// Ping & KeepAlive.h
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on January 14, 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/>
+
+// This class is private to the framework.
+
+typedef struct {
+ MYSQL *mySQLConnection;
+ BOOL *keepAlivePingActivePointer;
+ BOOL *keepAliveLastPingSuccessPointer;
+} SPMySQLConnectionPingDetails;
+
+@interface SPMySQLConnection (Ping_and_KeepAlive)
+
+// Setup functions
+- (void)_initKeepAlivePingTimer;
+
+// Keepalive ping initialisation
+- (void)_keepAlive:(NSTimer *)theTimer;
+- (void)_threadedKeepAlive;
+
+// Master ping method
+- (BOOL)_pingConnectionUsingLoopDelay:(NSUInteger)loopDelay;
+
+// Ping thread internals
+void _backgroundPingTask(void *ptr);
+void _forceThreadExit(int signalNumber);
+void _pingThreadCleanup(void *pingDetails);
+
+@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m
new file mode 100644
index 00000000..9e25edcb
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m
@@ -0,0 +1,227 @@
+//
+// $Id$
+//
+// Ping & KeepAlive.m
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on January 14, 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 "Ping & KeepAlive.h"
+#import "Locking.h"
+#import <pthread.h>
+
+@implementation SPMySQLConnection (Ping_and_KeepAlive)
+
+#pragma mark -
+#pragma mark Setup functions
+
+/**
+ * Set up the keepalive timer; this should be called on the main
+ * thread, to ensure the timer isn't descheduled when child threads
+ * terminate.
+ */
+- (void)_initKeepAlivePingTimer
+{
+ keepAliveTimer = [[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(_keepAlive:) userInfo:nil repeats:YES] retain];
+}
+
+#pragma mark -
+#pragma mark Keepalive ping initialisation
+
+/**
+ * Keeps the connection alive by running a ping.
+ * This method is called every ten seconds and spawns a thread which determines
+ * whether or not it should perform a ping.
+ */
+- (void)_keepAlive:(NSTimer *)theTimer
+{
+
+ // Do nothing if not connected or if keepalive is disabled
+ if (state != SPMySQLConnected || !useKeepAlive) return;
+
+ // Check to see whether a ping is required. First, compare the last query
+ // and keepalive times against the keepalive interval.
+ // Compare against interval-1 to allow default keepalive intervals to repeat
+ // at the correct intervals (eg no timer interval delay).
+ uint64_t currentTime = mach_absolute_time();
+ if (_elapsedSecondsSinceAbsoluteTime(lastConnectionUsedTime) < keepAliveInterval - 1
+ || _elapsedSecondsSinceAbsoluteTime(lastKeepAliveTime) < keepAliveInterval - 1)
+ {
+ return;
+ }
+
+ // Attempt to lock the connection. If the connection is currently busy,
+ // we don't need a ping.
+ if (![self _tryLockConnection]) return;
+ [self _unlockConnection];
+
+ // Store the ping time
+ lastKeepAliveTime = currentTime;
+
+ [NSThread detachNewThreadSelector:@selector(_threadedKeepAlive) toTarget:self withObject:nil];
+}
+
+/**
+ * A threaded keepalive to avoid blocking the interface. Performs safety
+ * checks, and then creates a child pthread to actually ping the connection,
+ * forcing the thread to close after the timeout if it hasn't closed already.
+ */
+- (void)_threadedKeepAlive
+{
+
+ // If the maximum number of ping failures has been reached, trigger a reconnect
+ if (keepAliveLastPingBlocked || keepAlivePingFailures >= 3) {
+ [self reconnect];
+ return;
+ }
+
+ // Otherwise, perform a background ping.
+ BOOL pingResult = [self _pingConnectionUsingLoopDelay:10000];
+ if (pingResult) {
+ keepAlivePingFailures = 0;
+ } else {
+ keepAlivePingFailures++;
+ }
+}
+
+#pragma mark -
+#pragma mark Master ping method
+
+/**
+ * This function provides a method of pinging the remote server while also enforcing
+ * the specified connection time. This is required because low-level net reads can
+ * block indefinitely if the remote server disappears or on network issues - setting
+ * the MYSQL_OPT_READ_TIMEOUT (and the WRITE equivalent) would "fix" ping, but cause
+ * long queries to be terminated.
+ * The supplied loop delay number controls how tight the thread checking loop is, in
+ * microseconds, to allow differentiating foreground and background pings.
+ * Unlike mysql_ping, this function returns FALSE on failure and TRUE on success.
+ */
+- (BOOL)_pingConnectionUsingLoopDelay:(NSUInteger)loopDelay
+{
+ if (state != SPMySQLConnected) return NO;
+
+ uint64_t pingStartTime_t;
+ double pingElapsedTime;
+ BOOL threadCancelled = NO;
+
+ // Set up a query lock
+ [self _lockConnection];
+
+ keepAliveLastPingSuccess = NO;
+ keepAliveLastPingBlocked = NO;
+ keepAlivePingThreadActive = YES;
+
+ // Use a ping timeout defaulting to thirty seconds, but using the connection timeout if set
+ NSUInteger pingTimeout = 30;
+ if (timeout > 0) pingTimeout = timeout;
+
+ // Set up a struct containing details the ping task will need
+ SPMySQLConnectionPingDetails pingDetails;
+ pingDetails.mySQLConnection = mySQLConnection;
+ pingDetails.keepAliveLastPingSuccessPointer = &keepAliveLastPingSuccess;
+ pingDetails.keepAlivePingActivePointer = &keepAlivePingThreadActive;
+
+ // Create a pthread for the ping
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ pthread_create(&keepAlivePingThread, &attr, (void *)&_backgroundPingTask, &pingDetails);
+
+ // Record the ping start time
+ pingStartTime_t = mach_absolute_time();
+
+ // Loop until the ping completes
+ do {
+ usleep((useconds_t)loopDelay);
+ pingElapsedTime = _elapsedSecondsSinceAbsoluteTime(pingStartTime_t);
+
+ // If the ping timeout has been exceeded, force a timeout; double-check that the
+ // thread is still active.
+ if (pingElapsedTime > pingTimeout && keepAlivePingThreadActive && !threadCancelled) {
+ pthread_cancel(keepAlivePingThread);
+ threadCancelled = YES;
+
+ // If the timeout has been exceeded by an additional two seconds, and the thread is
+ // still active, kill the thread. This can occur in certain network conditions causing
+ // a blocking read.
+ } else if (pingElapsedTime > (pingTimeout + 2) && keepAlivePingThreadActive) {
+ pthread_kill(keepAlivePingThread, SIGUSR1);
+ keepAlivePingThreadActive = NO;
+ keepAliveLastPingBlocked = YES;
+ }
+ } while (keepAlivePingThreadActive);
+
+ // Clean up
+ keepAlivePingThread = NULL;
+ pthread_attr_destroy(&attr);
+
+ // Unlock the connection
+ [self _unlockConnection];
+
+ return keepAliveLastPingSuccess;
+}
+
+#pragma mark -
+#pragma mark Ping thread internals
+
+/**
+ * Actually perform a keepalive ping - intended for use within a pthread.
+ */
+void _backgroundPingTask(void *ptr)
+{
+ SPMySQLConnectionPingDetails *pingDetails = (SPMySQLConnectionPingDetails *)ptr;
+
+ // Set up a cleanup routine
+ pthread_cleanup_push(_pingThreadCleanup, pingDetails);
+
+ // Set up a signal handler for SIGUSR1, to handle forced timeouts.
+ signal(SIGUSR1, _forceThreadExit);
+
+ // Perform a ping
+ *(pingDetails->keepAliveLastPingSuccessPointer) = (BOOL)(!mysql_ping(pingDetails->mySQLConnection));
+
+ // Call the cleanup routine
+ pthread_cleanup_pop(1);
+}
+
+/**
+ * Support forcing a thread to exit as a result of a signal.
+ */
+void _forceThreadExit(int signalNumber)
+{
+ pthread_exit(NULL);
+}
+
+void _pingThreadCleanup(void *pingDetails)
+{
+ SPMySQLConnectionPingDetails *pingDetailsStruct = pingDetails;
+ *(pingDetailsStruct->keepAlivePingActivePointer) = NO;
+}
+
+@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.h
new file mode 100644
index 00000000..ff55f796
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.h
@@ -0,0 +1,103 @@
+//
+// $Id$
+//
+// Querying & Preparation.h
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on January 14, 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/>
+
+
+@interface SPMySQLConnection (Querying_and_Preparation)
+
+// Data preparation
+- (NSString *)escapeAndQuoteString:(NSString *)theString;
+- (NSString *)escapeString:(NSString *)theString includingQuotes:(BOOL)includeQuotes;
+- (NSString *)escapeAndQuoteData:(NSData *)theData;
+- (NSString *)escapeData:(NSData *)theData includingQuotes:(BOOL)includeQuotes;
+
+// Queries
+- (SPMySQLResult *)queryString:(NSString *)theQueryString;
+- (SPMySQLFastStreamingResult *)streamingQueryString:(NSString *)theQueryString;
+- (id)streamingQueryString:(NSString *)theQueryString useLowMemoryBlockingStreaming:(BOOL)fullStreaming;
+- (id)queryString:(NSString *)theQueryString usingEncoding:(NSStringEncoding)theEncoding withResultType:(SPMySQLResultType)theReturnType;
+
+// Query information
+- (unsigned long long)rowsAffectedByLastQuery;
+- (unsigned long long)lastInsertID;
+
+// Connection and query error state
+- (BOOL)queryErrored;
+- (NSString *)lastErrorMessage;
+- (NSUInteger)lastErrorID;
++ (BOOL)isErrorIDConnectionError:(NSUInteger)theErrorID;
+
+// Query cancellation
+- (void)cancelCurrentQuery;
+- (BOOL)lastQueryWasCancelled;
+- (BOOL)lastQueryWasCancelledUsingReconnect;
+
+@end
+
+/**
+ * Set up static functions to allow fast calling with cached selectors
+ */
+
+static inline id SPMySQLConnectionEscapeString(SPMySQLConnection* self, NSString *theString, BOOL encloseInQuotes)
+{
+ typedef id (*SPMySQLConnectionEscapeStringMethodPtr)(SPMySQLConnection*, SEL, NSString *, BOOL);
+ static SPMySQLConnectionEscapeStringMethodPtr cachedMethodPointer;
+ static SEL cachedSelector;
+
+ if (!cachedSelector) cachedSelector = @selector(escapeString:includingQuotes:);
+ if (!cachedMethodPointer) cachedMethodPointer = (SPMySQLConnectionEscapeStringMethodPtr)[self methodForSelector:cachedSelector];
+
+ return cachedMethodPointer(self, cachedSelector, theString, encloseInQuotes);
+}
+
+static inline id SPMySQLConnectionEscapeData(SPMySQLConnection* self, NSData *theData, BOOL encloseInQuotes)
+{
+ typedef id (*SPMySQLConnectionEscapeDataMethodPtr)(SPMySQLConnection*, SEL, NSData *, BOOL);
+ static SPMySQLConnectionEscapeDataMethodPtr cachedMethodPointer;
+ static SEL cachedSelector;
+
+ if (!cachedSelector) cachedSelector = @selector(escapeData:includingQuotes:);
+ if (!cachedMethodPointer) cachedMethodPointer = (SPMySQLConnectionEscapeDataMethodPtr)[self methodForSelector:cachedSelector];
+
+ return cachedMethodPointer(self, cachedSelector, theData, encloseInQuotes);
+}
+
+static inline id SPMySQLConnectionQueryString(SPMySQLConnection* self, NSString *theQueryString, NSStringEncoding theEncoding, SPMySQLResultType theReturnType)
+{
+ typedef id (*SPMySQLConnectionQueryStringMethodPtr)(SPMySQLConnection*, SEL, NSString *, NSStringEncoding, SPMySQLResultType);
+ static SPMySQLConnectionQueryStringMethodPtr cachedMethodPointer;
+ static SEL cachedSelector;
+
+ if (!cachedSelector) cachedSelector = @selector(queryString:usingEncoding:withResultType:);
+ if (!cachedMethodPointer) cachedMethodPointer = (SPMySQLConnectionQueryStringMethodPtr)[self methodForSelector:cachedSelector];
+
+ return cachedMethodPointer(self, cachedSelector, theQueryString, theEncoding, theReturnType);
+} \ No newline at end of file
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m
new file mode 100644
index 00000000..4134880c
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m
@@ -0,0 +1,622 @@
+//
+// $Id$
+//
+// Querying & Preparation.m
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on January 14, 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 "SPMySQLConnection.h"
+#import "SPMySQL Private APIs.h"
+
+@implementation SPMySQLConnection (Querying_and_Preparation)
+
+#pragma mark -
+#pragma mark Data preparation
+
+/**
+ * See also the NSString methods mySQLTickQuotedString and mySQLBacktickQuotedString,
+ * added via an NSString category; however these methods are safer and more complete
+ * as they use the current connection encoding to quote characters.
+ */
+
+
+/**
+ * Take a string, escapes any special character, and surrounds it with single quotes
+ * for safe use within a query; correctly escapes any characters within the string
+ * using the current connection encoding.
+ */
+- (NSString *)escapeAndQuoteString:(NSString *)theString
+{
+ return SPMySQLConnectionEscapeString(self, theString, YES);
+}
+
+/**
+ * Take a string and escapes any special character for safe use within a query; correctly
+ * escapes any characters within the string using the current connection encoding.
+ * Allows control over whether to also wrap the string in single quotes.
+ */
+- (NSString *)escapeString:(NSString *)theString includingQuotes:(BOOL)includeQuotes
+{
+
+ // Return nil strings untouched
+ if (!theString) return theString;
+
+ // To correctly escape the string, an active connection is required, so verify.
+ if (state == SPMySQLDisconnected || state == SPMySQLConnecting) {
+ if ([delegate respondsToSelector:@selector(noConnectionAvailable:)]) {
+ [delegate noConnectionAvailable:self];
+ }
+ return nil;
+ }
+ if (![self _checkConnectionIfNecessary]) return nil;
+
+ // Perform a lossy conversion to bytes, using NSData to do the hard work. Preserves
+ // nul characters correctly.
+ NSData *cData = [theString dataUsingEncoding:stringEncoding allowLossyConversion:YES];
+ NSUInteger cDataLength = [cData length];
+
+ // Create a buffer for mysql_real_escape_string to place the converted string into;
+ // the max length is 2*length (if every character was quoted) + 2 (quotes/terminator).
+ // Adding quotes in this way makes the logic below *slightly* harder to follow but
+ // makes the addition of the quotes almost free, which is much nicer when building
+ // lots of strings.
+ char *escBuffer = (char *)malloc((cDataLength * 2) + 2);
+
+ // Use mysql_real_escape_string to perform the escape, starting one character in
+ NSUInteger escapedLength = mysql_real_escape_string(mySQLConnection, escBuffer+1, [cData bytes], cDataLength);
+
+ // Set up an NSData object to allow conversion back to NSString while preserving
+ // any nul characters contained in the string.
+ NSData *escapedData;
+ if (includeQuotes) {
+
+ // Add quotes if requested
+ escBuffer[0] = '\'';
+ escBuffer[escapedLength+1] = '\'';
+
+ escapedData = [NSData dataWithBytesNoCopy:escBuffer length:escapedLength+2 freeWhenDone:NO];
+ } else {
+ escapedData = [NSData dataWithBytesNoCopy:escBuffer+1 length:escapedLength freeWhenDone:NO];
+ }
+
+ // Convert to the string to return
+ NSString *escapedString = [[NSString alloc] initWithData:escapedData encoding:stringEncoding];
+
+ // Free up any memory and return
+ free(escBuffer);
+ return [escapedString autorelease];
+}
+
+/**
+ * Take NSData and hex-encodes the contents for safe transmission to a server,
+ * preserving all bytes whatever the encoding. Surrounds the hex-encoded resulting
+ * string with single quotes and precedes it with the hex-marker X for safe inclusion
+ * in a query.
+ */
+- (NSString *)escapeAndQuoteData:(NSData *)theData
+{
+ return SPMySQLConnectionEscapeData(self, theData, YES);
+}
+
+/**
+ * Takes NSData and hex-encodes the contents for safe transmission to a server,
+ * preserving all bytes whatever the encoding.
+ * Allows control over whether to also wrap the string in single quotes and a
+ * preceding X (X'...') for safe use in queries.
+ */
+- (NSString *)escapeData:(NSData *)theData includingQuotes:(BOOL)includeQuotes
+{
+
+ // Return nil datas as nil strings
+ if (!theData) return nil;
+
+ NSUInteger dataLength = [theData length];
+
+ // Create a buffer for mysql_real_escape_string to place the converted string into;
+ // the max length is 2*length (if every character was quoted) + 3 (quotes/terminator).
+ // Adding quotes in this way makes the logic below *slightly* harder to follow but
+ // makes the addition of the quotes almost free, which is much nicer when building
+ // lots of strings.
+ char *hexBuffer = (char *)malloc((dataLength * 2) + 3);
+
+ // Use mysql_hex_string to perform the escape, starting two characters in
+ NSUInteger hexLength = mysql_hex_string(hexBuffer+2, [theData bytes], dataLength);
+
+ // Set up the return NSString
+ NSString *hexString;
+ if (includeQuotes) {
+
+ // Add quotes if requested
+ hexBuffer[0] = 'X';
+ hexBuffer[1] = '\'';
+ hexBuffer[hexLength+2] = '\'';
+
+ hexString = [[NSString alloc] initWithBytes:hexBuffer length:hexLength+3 encoding:NSASCIIStringEncoding];
+ } else {
+ hexString = [[NSString alloc] initWithBytes:hexBuffer+2 length:hexLength encoding:NSASCIIStringEncoding];
+ }
+
+ // Free up any memory and return
+ free(hexBuffer);
+ return [hexString autorelease];
+}
+
+#pragma mark -
+#pragma mark Queries
+
+/**
+ * Run a query, provided as a string, on the active connection in the current connection
+ * encoding. Stores all the results before returning the complete result set.
+ */
+- (SPMySQLResult *)queryString:(NSString *)theQueryString
+{
+ return SPMySQLConnectionQueryString(self, theQueryString, stringEncoding, SPMySQLResultAsResult);
+}
+
+/**
+ * Run a query, provided as a string, on the active connection in the current connection
+ * encoding. Returns the result as a fast streaming query set, where not all the results
+ * may be available at time of return.
+ */
+- (SPMySQLFastStreamingResult *)streamingQueryString:(NSString *)theQueryString
+{
+ return SPMySQLConnectionQueryString(self, theQueryString, stringEncoding, SPMySQLResultAsFastStreamingResult);
+}
+
+/**
+ * Run a query, provided as a string, on the active connection in the current connection
+ * encoding. Returns the result as a streaming query set, where not all the results may
+ * be available at time of return.
+ * Supports a flag specifying whether streaming should be low-memory blocking (results are
+ * read from the server as the code retrives them, possibly blocking other queries on the
+ * server) or fast streaming (results are cached in the result object as fast as possible,
+ * freeing up the server even in the local rows are still being read from the result object).
+ * Will return a SPMySQLStreamingResult or SPMySQLFastStreamingResult as appropriate.
+ */
+- (id)streamingQueryString:(NSString *)theQueryString useLowMemoryBlockingStreaming:(BOOL)fullStreaming
+{
+ return SPMySQLConnectionQueryString(self, theQueryString, stringEncoding, fullStreaming?SPMySQLResultAsLowMemStreamingResult:SPMySQLResultAsFastStreamingResult);
+}
+
+/**
+ * Run a query, provided as a string, on the active connection. The query and its result
+ * set are interpreted according to the supplied encoding, which should usually match
+ * the connection encoding.
+ * The result type desired can be specified, supporting either standard or streaming
+ * result sets.
+ */
+- (id)queryString:(NSString *)theQueryString usingEncoding:(NSStringEncoding)theEncoding withResultType:(SPMySQLResultType)theReturnType
+{
+ double queryExecutionTime;
+ lastQueryWasCancelled = NO;
+ lastQueryWasCancelledUsingReconnect = NO;
+
+ // Check the connection state - if no connection is available, log an
+ // error and return.
+ if (state == SPMySQLDisconnected || state == SPMySQLConnecting) {
+ if ([delegate respondsToSelector:@selector(queryGaveError:connection:)]) {
+ [delegate queryGaveError:@"No connection available!" connection:self];
+ }
+ if ([delegate respondsToSelector:@selector(noConnectionAvailable:)]) {
+ [delegate noConnectionAvailable:self];
+ }
+ return nil;
+ }
+
+ // Check the connection if necessary, returning nil if the query couldn't be validated
+ if (![self _checkConnectionIfNecessary]) return nil;
+
+ // Determine whether a maximum query size needs to be restored from a previous query
+ if (queryActionShouldRestoreMaxQuerySize != NSNotFound) {
+ [self _restoreMaximumQuerySizeAfterQuery];
+ }
+
+ // If delegate logging is enabled, and the protocol is implemented, inform the delegate
+ if (delegateQueryLogging && delegateSupportsWillQueryString) {
+ [delegate willQueryString:theQueryString connection:self];
+ }
+
+ // Retrieve a C-style query string from the supplied NSString
+ NSUInteger cQueryStringLength;
+ const char *cQueryString = _cStringForStringWithEncoding(theQueryString, theEncoding, &cQueryStringLength);
+
+ // Check the query length against the current maximum query length. If it is
+ // larger, the query would error (and probably cause a disconnect), so if
+ // the maximum size is editable, increase it and reconnect.
+ if (cQueryStringLength > maxQuerySize) {
+ queryActionShouldRestoreMaxQuerySize = maxQuerySize;
+ if (![self _attemptMaxQuerySizeIncreaseTo:(cQueryStringLength + 1024)]) {
+ queryActionShouldRestoreMaxQuerySize = NSNotFound;
+ return nil;
+ }
+ }
+
+ // Prepare to enter a loop to run the query, allowing reattempts if appropriate
+ NSUInteger queryAttemptsAllowed = 1;
+ if (retryQueriesOnConnectionFailure) queryAttemptsAllowed++;
+ int queryStatus;
+
+ // Lock the connection while it's actively in use
+ [self _lockConnection];
+
+ while (queryAttemptsAllowed > 0) {
+
+ // While recording the overall execution time (including network lag!), run
+ // the raw query
+ uint64_t queryStartTime = mach_absolute_time();
+ queryStatus = mysql_real_query(mySQLConnection, cQueryString, cQueryStringLength);
+ queryExecutionTime = _elapsedSecondsSinceAbsoluteTime(queryStartTime);
+ lastConnectionUsedTime = mach_absolute_time();
+
+ // If the query succeeded, no need to re-attempt.
+ if (!queryStatus) {
+ break;
+
+ // If the query failed, determine whether to reattempt the query
+ } else {
+
+ // Prevent retries if the query was cancelled or not a connection error
+ if (lastQueryWasCancelled && ![SPMySQLConnection isErrorIDConnectionError:mysql_errno(mySQLConnection)]) {
+ break;
+ }
+ }
+
+ // Query has failed - check the connection
+ if (![self checkConnection]) {
+ [self _unlockConnection];
+ return nil;
+ }
+
+ queryAttemptsAllowed--;
+ }
+
+ unsigned long long theAffectedRowCount = mysql_affected_rows(mySQLConnection);
+ id theResult = nil;
+
+ // On success, if there is a query result, retrieve the result data type
+ if (!queryStatus && mysql_field_count(mySQLConnection)) {
+ MYSQL_RES *mysqlResult;
+
+ switch (theReturnType) {
+
+ // For standard result sets, retrieve all the results now, and afterwards
+ // update the affected row count.
+ case SPMySQLResultAsResult:
+ mysqlResult = mysql_store_result(mySQLConnection);
+ theResult = [[SPMySQLResult alloc] initWithMySQLResult:mysqlResult stringEncoding:theEncoding];
+ theAffectedRowCount = mysql_affected_rows(mySQLConnection);
+ break;
+
+ // For fast streaming and low memory streaming result sets, set up the result
+ case SPMySQLResultAsLowMemStreamingResult:
+ mysqlResult = mysql_use_result(mySQLConnection);
+ theResult = [[SPMySQLStreamingResult alloc] initWithMySQLResult:mysqlResult stringEncoding:theEncoding connection:self];
+ break;
+
+ case SPMySQLResultAsFastStreamingResult:
+ mysqlResult = mysql_use_result(mySQLConnection);
+ theResult = [[SPMySQLFastStreamingResult alloc] initWithMySQLResult:mysqlResult stringEncoding:theEncoding connection:self];
+ break;
+ }
+ }
+
+ // Record the error state now, as it may be affected by subsequent clean-up queries
+ NSString *theErrorMessage = [self _stringForCString:mysql_error(mySQLConnection)];
+ NSUInteger theErrorID = mysql_errno(mySQLConnection);
+
+ // If the query was cancelled, override the error state
+ if (lastQueryWasCancelled) {
+ theErrorMessage = NSLocalizedString(@"Query cancelled.", @"Query cancelled error");
+ theErrorID = 1317;
+ }
+
+ // Unlock the connection if appropriate - if not a streaming result type.
+ if (![theResult isKindOfClass:[SPMySQLStreamingResult class]]) {
+ [self _unlockConnection];
+
+ // Also perform restore if appropriate
+ if (queryActionShouldRestoreMaxQuerySize != NSNotFound) {
+ [self _restoreMaximumQuerySizeAfterQuery];
+ }
+ }
+
+ // Update error string and ID
+ [self _updateLastErrorMessage:theErrorMessage];
+ [self _updateLastErrorID:theErrorID];
+
+ // Store the result time on the response object
+ [theResult _setQueryExecutionTime:queryExecutionTime];
+
+ return [theResult autorelease];
+}
+
+#pragma mark -
+#pragma mark Query information
+
+/**
+ * Returns the number of rows changed, deleted, inserted, or selected by
+ * the last query.
+ */
+- (unsigned long long)rowsAffectedByLastQuery
+{
+ return lastQueryAffectedRowCount;
+}
+
+/**
+ * Returns the insert ID for the previous query which inserted a row. Note that
+ * this value persists through other SELECT/UPDATE etc queries.
+ */
+- (unsigned long long)lastInsertID
+{
+ return lastQueryInsertID;
+}
+
+#pragma mark -
+#pragma mark Retrieving connection and query error state
+
+/**
+ * Return whether the last query errored or not.
+ */
+- (BOOL)queryErrored
+{
+ return (queryErrorMessage)?YES:NO;
+}
+
+/**
+ * If the last query (or connection) triggered an error, returns the error
+ * message as a string; if the last query did not error, nil is returned.
+ */
+- (NSString *)lastErrorMessage
+{
+ return queryErrorMessage;
+}
+
+/**
+ * If the last query (or connection) triggered an error, returns the error
+ * ID; if the last query did not error, 0 is returned.
+ */
+- (NSUInteger)lastErrorID
+{
+ return queryErrorID;
+}
+
+/**
+ * Determines whether a supplied error ID can be classed as a connection error.
+ */
++ (BOOL)isErrorIDConnectionError:(NSUInteger)theErrorID
+{
+ switch (theErrorID) {
+ case 2001: // CR_SOCKET_CREATE_ERROR
+ case 2002: // CR_CONNECTION_ERROR
+ case 2003: // CR_CONN_HOST_ERROR
+ case 2004: // CR_IPSOCK_ERROR
+ case 2005: // CR_UNKNOWN_HOST
+ case 2006: // CR_SERVER_GONE_ERROR
+ case 2007: // CR_VERSION_ERROR
+ case 2009: // CR_WRONG_HOST_INFO
+ case 2012: // CR_SERVER_HANDSHAKE_ERR
+ case 2013: // CR_SERVER_LOST
+ case 2027: // CR_MALFORMED_PACKET
+ case 2032: // CR_DATA_TRUNCATED
+ case 2047: // CR_CONN_UNKNOW_PROTOCOL
+ case 2048: // CR_INVALID_CONN_HANDLE
+ case 2050: // CR_FETCH_CANCELED
+ case 2055: // CR_SERVER_LOST_EXTENDED
+ return YES;
+ }
+
+ return NO;
+}
+
+#pragma mark -
+#pragma mark Query cancellation
+
+/**
+ * Cancel the currently running query. This tries to kill the current query,
+ * and if that isn't possible - for example, on MySQL < 5 or if the current user
+ * does not have the relevant permissions - resets the connection.
+ */
+- (void)cancelCurrentQuery
+{
+
+ // If not connected, no action is required
+ if (state != SPMySQLConnected && state != SPMySQLDisconnecting) return;
+
+ // Check whether a query is actually being performed - if not, return
+ if ([self _tryLockConnection]) {
+ [self _unlockConnection];
+ return;
+ }
+
+ // Mark that the last query was cancelled to prevent query retries from occurring
+ lastQueryWasCancelled = YES;
+
+ // The query cancellation cannot occur on the connection actively running a query
+ // so set up a new connection to run the KILL command.
+ MYSQL *killerConnection = [self _makeRawMySQLConnectionWithEncoding:@"utf8" isMasterConnection:NO];
+
+
+ // If the new connection was successfully set up, use it to run a KILL command.
+ if (killerConnection) {
+ NSStringEncoding aStringEncoding = [SPMySQLConnection stringEncodingForMySQLCharset:mysql_character_set_name(killerConnection)];
+ BOOL killQuerySupported = [self serverVersionIsGreaterThanOrEqualTo:5 minorVersion:0 releaseVersion:0];
+
+ // Build the kill query
+ NSMutableString *killQuery = [NSMutableString stringWithString:@"KILL"];
+ if (killQuerySupported) [killQuery appendString:@" QUERY"];
+ [killQuery appendFormat:@" %lu", mySQLConnection->thread_id];
+
+ // Convert to a C string
+ NSUInteger killQueryCStringLength;
+ const char *killQueryCString = [SPMySQLConnection _cStringForString:killQuery usingEncoding:aStringEncoding returningLengthAs:&killQueryCStringLength];
+
+ // Run the query
+ int killQueryStatus = mysql_real_query(killerConnection, killQueryCString, killQueryCStringLength);
+
+ // Close the temporary connection
+ mysql_close(killerConnection);
+
+ // If the kill query succeeded, the active query was cancelled.
+ if (killQueryStatus == 0) {
+
+ // On MySQL < 5, the entire connection will have been reset. Ensure it's
+ // restored.
+ if (!killQuerySupported) {
+ [self checkConnection];
+ lastQueryWasCancelledUsingReconnect = YES;
+ } else {
+ lastQueryWasCancelledUsingReconnect = NO;
+ }
+
+ // Ensure the tracking bool is re-set to cover encompassed queries and return
+ lastQueryWasCancelled = YES;
+ return;
+ } else {
+ NSLog(@"SPMySQL Framework: query cancellation failed due to cancellation query error (status %d)", killQueryStatus);
+ }
+ } else {
+ NSLog(@"SPMySQL Framework: query cancellation failed because connection failed");
+ }
+
+ // A full reconnect is required at this point to force a cancellation. As the
+ // connection may have finished processing the query at this point (depending how
+ // long the connection attempt took), check whether we can skip the reconnect.
+ if ([self _tryLockConnection]) {
+ [self _unlockConnection];
+ return;
+ }
+
+ if (state == SPMySQLDisconnecting) return;
+
+ // Reset the connection with a reconnect. Unlock the connection beforehand,
+ // to allow the reconnect, but lock it again afterwards to restore the expected
+ // state (query execution process should unlock as appropriate).
+ [self _unlockConnection];
+ [self reconnect];
+ [self _lockConnection];
+
+ // Reset tracking bools to cover encompassed queries
+ lastQueryWasCancelled = YES;
+ lastQueryWasCancelledUsingReconnect = YES;
+}
+
+/**
+ * Returns whether the last query was cancelled using cancelCurrentQuery.
+ */
+- (BOOL)lastQueryWasCancelled
+{
+ return lastQueryWasCancelled;
+}
+
+/**
+ * If the last query was cancelled, returns whether that query cancellation
+ * required the connection to be reset or whether the query was successfully
+ * cancelled leaving the connection intact.
+ * If the last query was not cancelled, this will return NO.
+ */
+- (BOOL)lastQueryWasCancelledUsingReconnect
+{
+ return lastQueryWasCancelledUsingReconnect;
+}
+
+@end
+
+#pragma mark -
+#pragma mark Private API
+
+@implementation SPMySQLConnection (Querying_and_Preparation_Private_API)
+
+/**
+ * Retrieves all remaining results and discards them.
+ * This is necessary to correctly process multiple result sets on the connection - as
+ * we currently don't fully support multiple result, this at least allows the connection
+ * to function after running statements with multiple result sets.
+ */
+- (void)_flushMultipleResultSets
+{
+
+ // Repeat as long as there are results
+ while (!mysql_next_result(mySQLConnection)) {
+ MYSQL_RES *eachResult = mysql_use_result(mySQLConnection);
+
+ // Ensure the result is really a result
+ if (eachResult) {
+
+ // Retrieve and discard all rows
+ while (mysql_fetch_row(eachResult));
+
+ // Free the result set
+ mysql_free_result(eachResult);
+ }
+ }
+}
+
+/**
+ * Update the MySQL error message for this connection. If an error is supplied
+ * it will be stored and returned to anything asking the instance for the last
+ * error; if no error is supplied, the connection will be used to derive (or clear)
+ * the error string.
+ */
+- (void)_updateLastErrorMessage:(NSString *)theErrorMessage
+{
+
+ // If an error message wasn't supplied, select one from the connection
+ if (!theErrorMessage) {
+ theErrorMessage = [self _stringForCString:mysql_error(mySQLConnection)];
+ }
+
+ // Clear the last error message stored on the instance
+ if (queryErrorMessage) [queryErrorMessage release], queryErrorMessage = nil;
+
+ // If we have an error message *with a length*, update the instance error message
+ if (theErrorMessage && [theErrorMessage length]) {
+ queryErrorMessage = [[NSString alloc] initWithString:theErrorMessage];
+ }
+}
+
+/**
+ * Update the MySQL error ID for this connection. If an error ID is supplied,
+ * it will be stored and returned to anything asking the instance for the last
+ * error; if an NSNotFound error ID is supplied, the connection will be used to
+ * set the error ID. Note that an error ID of 0 corresponds to no error.
+ */
+- (void)_updateLastErrorID:(NSUInteger)theErrorID
+{
+
+ // If NSNotFound was supplied as the ID, ask the connection for the last error
+ if (theErrorID == NSNotFound) {
+ queryErrorID = mysql_errno(mySQLConnection);
+
+ // Otherwise, update the error ID with the supplied ID
+ } else {
+ queryErrorID = theErrorID;
+ }
+}
+
+@end \ No newline at end of file
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h
new file mode 100644
index 00000000..d8f5f183
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h
@@ -0,0 +1,50 @@
+//
+// $Id$
+//
+// Server Info.h
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on January 14, 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/>
+
+@class SPMySQLResult;
+
+@interface SPMySQLConnection (Server_Info)
+
+// Server version information
+- (NSString *)serverVersionString;
+- (NSUInteger)serverMajorVersion;
+- (NSUInteger)serverMinorVersion;
+- (NSUInteger)serverReleaseVersion;
+
+// Server version comparisons
+- (BOOL)serverVersionIsGreaterThanOrEqualTo:(NSUInteger)aMajorVersion minorVersion:(NSUInteger)aMinorVersion releaseVersion:(NSUInteger)aReleaseVersion;
+
+// Server tasks & processes
+- (SPMySQLResult *)listProcesses;
+- (BOOL)killQueryOnThreadID:(unsigned long)theThreadID;
+
+@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m
new file mode 100644
index 00000000..f695d977
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m
@@ -0,0 +1,175 @@
+//
+// $Id$
+//
+// Server Info.m
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on January 14, 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 "Server Info.h"
+#import "SPMySQL Private APIs.h"
+
+@implementation SPMySQLConnection (Server_Info)
+
+#pragma mark -
+#pragma mark Server version information
+
+/**
+ * Return the server version string, or nil on failure.
+ */
+- (NSString *)serverVersionString
+{
+ if (serverVersionString) {
+ return [NSString stringWithString:serverVersionString];
+ }
+
+ return nil;
+}
+
+/**
+ * Return the server major version or NSNotFound on failure
+ */
+- (NSUInteger)serverMajorVersion
+{
+
+ if (serverVersionString != nil) {
+ NSString *s = [[serverVersionString componentsSeparatedByString:@"."] objectAtIndex:0];
+ return (NSUInteger)[s integerValue];
+ }
+
+ return NSNotFound;
+}
+
+/**
+ * Return the server minor version or NSNotFound on failure
+ */
+- (NSUInteger)serverMinorVersion
+{
+ if (serverVersionString != nil) {
+ NSString *s = [[serverVersionString componentsSeparatedByString:@"."] objectAtIndex:1];
+ return (NSUInteger)[s integerValue];
+ }
+
+ return NSNotFound;
+}
+
+/**
+ * Return the server release version or NSNotFound on failure
+ */
+- (NSUInteger)serverReleaseVersion
+{
+ if (serverVersionString != nil) {
+ NSString *s = [[serverVersionString componentsSeparatedByString:@"."] objectAtIndex:2];
+ return (NSUInteger)[[[s componentsSeparatedByString:@"-"] objectAtIndex:0] integerValue];
+ }
+
+ return NSNotFound;
+}
+
+#pragma mark -
+#pragma mark Server version comparisons
+
+/**
+ * Returns whether the connected server version is greater than or equal to the
+ * supplied version number. Returns NO if no connection is active.
+ */
+- (BOOL)serverVersionIsGreaterThanOrEqualTo:(NSUInteger)aMajorVersion minorVersion:(NSUInteger)aMinorVersion releaseVersion:(NSUInteger)aReleaseVersion
+{
+ if (!serverVersionString) return NO;
+
+ NSArray *serverVersionParts = [serverVersionString componentsSeparatedByString:@"."];
+
+ NSUInteger serverMajorVersion = (NSUInteger)[[serverVersionParts objectAtIndex:0] integerValue];
+ if (serverMajorVersion < aMajorVersion) return NO;
+ if (serverMajorVersion > aMajorVersion) return YES;
+
+ NSUInteger serverMinorVersion = (NSUInteger)[[serverVersionParts objectAtIndex:1] integerValue];
+ if (serverMinorVersion < aMinorVersion) return NO;
+ if (serverMinorVersion > aMinorVersion) return YES;
+
+ NSString *serverReleasePart = [serverVersionParts objectAtIndex:2];
+ NSUInteger serverReleaseVersion = (NSUInteger)[[[serverReleasePart componentsSeparatedByString:@"-"] objectAtIndex:0] integerValue];
+ if (serverReleaseVersion < aReleaseVersion) return NO;
+ return YES;
+}
+
+#pragma mark -
+#pragma mark Server tasks & processes
+
+/**
+ * Returns a result set describing the current server threads and their tasks. Note that
+ * the resulting process list defaults to the short form; run a manual SHOW FULL PROCESSLIST
+ * to retrieve tasks in non-truncated form.
+ * Returns nil on error.
+ */
+- (SPMySQLResult *)listProcesses
+{
+ if (state != SPMySQLConnected) return nil;
+
+ // Check the connection if appropriate
+ if (![self _checkConnectionIfNecessary]) return nil;
+
+ // Lock the connection before using it
+ [self _lockConnection];
+
+ // Get the process list
+ MYSQL_RES *mysqlResult = mysql_list_processes(mySQLConnection);
+
+ // Convert to SPMySQLResult
+ SPMySQLResult *theResult = [[SPMySQLResult alloc] initWithMySQLResult:mysqlResult stringEncoding:stringEncoding];
+
+ // Unlock and return
+ [self _unlockConnection];
+ return [theResult autorelease];
+}
+
+/**
+ * Kill the process with the supplied thread ID. On MySQL version 5 or later, this kills
+ * the query; on older servers this kills the entire connection. Note that the SUPER
+ * privilege is required to kill queries and processes not belonging to the currently
+ * connected user, while only PROCESS is required to see other user's processes.
+ * Returns a boolean indicating success or failure.
+ */
+- (BOOL)killQueryOnThreadID:(unsigned long)theThreadID
+{
+
+ // Note that mysql_kill has been deprecated, so use a query to perform this task.
+ NSMutableString *killQuery = [NSMutableString stringWithString:@"KILL"];
+ if ([self serverVersionIsGreaterThanOrEqualTo:5 minorVersion:0 releaseVersion:0]) {
+ [killQuery appendString:@" QUERY"];
+ }
+ [killQuery appendFormat:@" %lu", theThreadID];
+
+ // Run the query
+ [self queryString:killQuery];
+
+ // Return a value based on whether the query errored or not
+ return ![self queryErrored];
+}
+
+@end \ No newline at end of file