aboutsummaryrefslogtreecommitdiffstats
path: root/Frameworks/SPMySQLFramework/Source
diff options
context:
space:
mode:
Diffstat (limited to 'Frameworks/SPMySQLFramework/Source')
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h8
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m6
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h6
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m60
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m29
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h10
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m76
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h9
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m90
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m4
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.h7
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m49
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m14
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLResult.m2
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.m4
15 files changed, 221 insertions, 153 deletions
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h
index 2419d7a9..1e2a8c14 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h
@@ -82,6 +82,7 @@
@interface SPMySQLConnection (Querying_and_Preparation_Private_API)
- (void)_flushMultipleResultSets;
+- (void)_updateLastErrorInfos;
- (void)_updateLastErrorMessage:(NSString *)theErrorMessage;
- (void)_updateLastErrorID:(NSUInteger)theErrorID;
- (void)_updateLastSqlstate:(NSString *)theSqlstate;
@@ -99,12 +100,7 @@
@end
// SPMySQLResult Data Conversion Private API
-@interface SPMySQLResult (Data_Conversion_Private_API)
-
-+ (void)_initializeDataConversion;
-- (id)_getObjectFromBytes:(char *)bytes ofLength:(NSUInteger)length fieldDefinitionIndex:(NSUInteger)fieldIndex previewLength:(NSUInteger)previewLength;
-
-@end
+#import "Data Conversion.h"
/**
* Set up a static function to allow fast calling of SPMySQLResult data conversion with cached selectors
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m
index fb949679..76f323bc 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m
@@ -213,7 +213,7 @@
if (!strcmp(mysqlCharset, "utf8")) {
return NSUTF8StringEncoding;
} else if (!strcmp(mysqlCharset, "latin1")) {
- return NSISOLatin1StringEncoding;
+ return NSWindowsCP1252StringEncoding; // Warning: This is NOT the same as ISO-8859-1 (aka "ISO Latin 1")
} else if (!strcmp(mysqlCharset, "ascii")) {
return NSASCIIStringEncoding;
@@ -289,7 +289,7 @@
} else if (!strcmp(mysqlCharset, "dos")) {
return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingDOSLatin1);
} else if (!strcmp(mysqlCharset, "german1")) {
- return NSISOLatin1StringEncoding;
+ return NSWindowsCP1252StringEncoding;
} else if (!strcmp(mysqlCharset, "usa7")) {
return NSASCIIStringEncoding;
} else if (!strcmp(mysqlCharset, "danish")) {
@@ -313,7 +313,7 @@
} else if (!strcmp(mysqlCharset, "croat")) {
return NSISOLatin2StringEncoding;
} else if (!strcmp(mysqlCharset, "latin1_de")) {
- return NSISOLatin1StringEncoding;
+ return NSWindowsCP1252StringEncoding;
}
/**
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h
index cff8d43b..a3f34817 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h
@@ -32,8 +32,8 @@
typedef struct {
MYSQL *mySQLConnection;
- BOOL *keepAlivePingActivePointer;
- BOOL *keepAliveLastPingSuccessPointer;
+ volatile BOOL *keepAlivePingThreadActivePointer;
+ volatile BOOL *keepAliveLastPingSuccessPointer;
} SPMySQLConnectionPingDetails;
@interface SPMySQLConnection (Ping_and_KeepAlive)
@@ -51,6 +51,6 @@ void _forceThreadExit(int signalNumber);
void _pingThreadCleanup(void *pingDetails);
// Cancellation
-- (void)_cancelKeepAlives;
+- (BOOL)_cancelKeepAlives;
@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m
index e8338bb4..7940b483 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m
@@ -80,6 +80,10 @@
*/
- (void)_threadedKeepAlive
{
+ if(keepAliveThread) {
+ NSLog(@"warning: overwriting existing keepAliveThread: %@, results may be unpredictable!",keepAliveThread);
+ }
+
keepAliveThread = [NSThread currentThread];
[keepAliveThread setName:@"SPMySQL connection keepalive monitor thread"];
@@ -90,16 +94,15 @@
// attempt a single reconnection in the background
if (_elapsedSecondsSinceAbsoluteTime(lastConnectionUsedTime) < 60 * 15) {
[self _reconnectAfterBackgroundConnectionLoss];
-
+ }
// Otherwise set the state to connection lost for automatic reconnect on
// next use.
- } else {
+ else {
state = SPMySQLConnectionLostInBackground;
}
// Return as no further ping action required this cycle.
- keepAliveThread = nil;
- return;
+ goto end_cleanup;
}
// Otherwise, perform a background ping.
@@ -109,6 +112,7 @@
} else {
keepAlivePingFailures++;
}
+end_cleanup:
keepAliveThread = nil;
}
@@ -135,8 +139,13 @@
// Set up a query lock
[self _lockConnection];
+ //we might find ourselves at the losing end of a contest with -[self _disconnect]
+ if(!mySQLConnection) {
+ [self _unlockConnection];
+ return NO;
+ }
- keepAliveLastPingSuccess = NO;
+ volatile BOOL keepAliveLastPingSuccess = NO;
keepAliveLastPingBlocked = NO;
keepAlivePingThreadActive = YES;
@@ -148,12 +157,14 @@
SPMySQLConnectionPingDetails *pingDetails = malloc(sizeof(SPMySQLConnectionPingDetails));
pingDetails->mySQLConnection = mySQLConnection;
pingDetails->keepAliveLastPingSuccessPointer = &keepAliveLastPingSuccess;
- pingDetails->keepAlivePingActivePointer = &keepAlivePingThreadActive;
+ pingDetails->keepAlivePingThreadActivePointer = &keepAlivePingThreadActive;
// Create a pthread for the ping
+ pthread_t keepAlivePingThread_t;
+
pthread_attr_t attr;
pthread_attr_init(&attr);
- pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&keepAlivePingThread_t, &attr, (void *)&_backgroundPingTask, pingDetails);
// Record the ping start time
@@ -166,7 +177,7 @@
// If the ping timeout has been exceeded, or the ping thread has been
// cancelled, force a timeout; double-check that the thread is still active.
- if (([keepAliveThread isCancelled] || pingElapsedTime > pingTimeout)
+ if (([[NSThread currentThread] isCancelled] || pingElapsedTime > pingTimeout)
&& keepAlivePingThreadActive
&& !threadCancelled)
{
@@ -182,6 +193,9 @@
keepAliveLastPingBlocked = YES;
}
} while (keepAlivePingThreadActive);
+
+ //wait for thread to go away, otherwise our free() below might run before _pingThreadCleanup()
+ pthread_join(keepAlivePingThread_t, NULL);
// Clean up
keepAlivePingThread_t = NULL;
@@ -238,7 +252,7 @@ void _forceThreadExit(int signalNumber)
void _pingThreadCleanup(void *pingDetails)
{
SPMySQLConnectionPingDetails *pingDetailsStruct = pingDetails;
- *(pingDetailsStruct->keepAlivePingActivePointer) = NO;
+ *(pingDetailsStruct->keepAlivePingThreadActivePointer) = NO;
// Clean up MySQL variables and handlers
mysql_thread_end();
@@ -250,24 +264,28 @@ void _pingThreadCleanup(void *pingDetails)
/**
* If a keepalive thread is active, cancel it, and wait a short time for it
* to exit.
+ *
+ * @return YES, if the thread exited within 10 seconds after canceling it
*/
-- (void)_cancelKeepAlives
+- (BOOL)_cancelKeepAlives
{
// If no keepalive thread is active, return
- if (!keepAliveThread) {
- return;
- }
+ if (keepAliveThread) {
- // Mark the thread as cancelled
- [keepAliveThread cancel];
+ // Mark the thread as cancelled
+ [keepAliveThread cancel];
- // Wait inside a time limit of ten seconds for it to exit
- uint64_t threadCancelStartTime_t = mach_absolute_time();
- do {
- usleep(100000);
- if (_elapsedSecondsSinceAbsoluteTime(threadCancelStartTime_t) > 10) break;
- } while (keepAliveThread);
+ // Wait inside a time limit of ten seconds for it to exit
+ uint64_t threadCancelStartTime_t = mach_absolute_time();
+ do {
+ usleep(100000);
+ if (_elapsedSecondsSinceAbsoluteTime(threadCancelStartTime_t) > 10) return NO;
+ } while (keepAliveThread);
+
+ }
+
+ return YES;
}
@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m
index 48f4fc1e..e5a48d48 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m
@@ -58,6 +58,9 @@
* 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.
+ *
+ * WARNING: This method may return nil if the current thread is cancelled!
+ * You MUST check the isCancelled flag before using the result!
*/
- (NSString *)escapeString:(NSString *)theString includingQuotes:(BOOL)includeQuotes
{
@@ -72,11 +75,12 @@
}
return nil;
}
- if (![self _checkConnectionIfNecessary]) return nil;
// Ensure per-thread variables are set up
[self _validateThreadSetup];
+ 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];
@@ -221,6 +225,9 @@
* the connection encoding.
* The result type desired can be specified, supporting either standard or streaming
* result sets.
+ *
+ * WARNING: This method may return nil if the current thread is cancelled!
+ * You MUST check the isCancelled flag before using the result!
*/
- (id)queryString:(NSString *)theQueryString usingEncoding:(NSStringEncoding)theEncoding withResultType:(SPMySQLResultType)theReturnType
{
@@ -288,6 +295,7 @@
// Lock the connection while it's actively in use
[self _lockConnection];
+ unsigned long long theAffectedRowCount;
while (queryAttemptsAllowed > 0) {
// While recording the overall execution time (including network lag!), run
@@ -296,6 +304,11 @@
queryStatus = mysql_real_query(mySQLConnection, queryBytes, queryBytesLength);
queryExecutionTime = _elapsedSecondsSinceAbsoluteTime(queryStartTime);
lastConnectionUsedTime = mach_absolute_time();
+
+ // "An integer greater than zero indicates the number of rows affected or retrieved.
+ // Zero indicates that no records were updated for an UPDATE statement, no rows matched the WHERE clause in the query or that no query has yet been executed.
+ // -1 indicates that the query returned an error or that, for a SELECT query, mysql_affected_rows() was called prior to calling mysql_store_result()."
+ theAffectedRowCount = mysql_affected_rows(mySQLConnection);
// If the query succeeded, no need to re-attempt.
if (!queryStatus) {
@@ -313,7 +326,7 @@
theSqlstate = [self _stringForCString:mysql_sqlstate(mySQLConnection)];
// Prevent retries if the query was cancelled or not a connection error
- if (lastQueryWasCancelled || ![SPMySQLConnection isErrorIDConnectionError:mysql_errno(mySQLConnection)]) {
+ if (lastQueryWasCancelled || ![SPMySQLConnection isErrorIDConnectionError:theErrorID]) {
break;
}
}
@@ -327,11 +340,11 @@
return nil;
}
[self _lockConnection];
+ NSAssert(mySQLConnection != NULL, @"mySQLConnection has disappeared while checking it!");
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
@@ -660,6 +673,16 @@
}
/**
+ * Update lastErrorID, lastErrorMessage and lastSqlstate from connection
+ */
+- (void)_updateLastErrorInfos
+{
+ [self _updateLastErrorID:NSNotFound];
+ [self _updateLastErrorMessage:nil];
+ [self _updateLastSqlstate:nil];
+}
+
+/**
* 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)
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h
index 82607cdb..8ec6c9e0 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h
@@ -45,4 +45,14 @@
- (SPMySQLResult *)listProcesses;
- (BOOL)killQueryOnThreadID:(unsigned long)theThreadID;
+/**
+ * mysql_shutdown() - If the user has the permission, will shutdown the (remote) server
+ * @return Whether the command was executed successfully
+ * Note: this can also be NO if the user denied a reconnect attempt.
+ *
+ * WARNING: This method may return NO if the current thread is cancelled!
+ * You MUST check the isCancelled flag before using the result!
+ */
+- (BOOL)serverShutdown;
+
@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m
index dd684c78..db846929 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m
@@ -46,54 +46,34 @@
return [NSString stringWithString:serverVariableVersion];
}
-#warning FIXME: There is probably a race condition here with -[self _disconnect]
- if(mySQLConnection) {
- return [self _stringForCString:mysql_get_server_info(mySQLConnection)];
- }
-
return nil;
}
/**
- * Return the server major version or NSNotFound on failure
+ * Return the server major version or 0 on failure
*/
- (NSUInteger)serverMajorVersion
{
- NSString *ver;
- if ((ver = [self serverVersionString]) != nil) {
- NSString *s = [[ver componentsSeparatedByString:@"."] objectAtIndex:0];
- return (NSUInteger)[s integerValue];
- }
-
- return NSNotFound;
+ // 5.5.33 => 50533 / 10'000 => 5.0533 => 5
+ return (serverVersionNumber / 10000);
}
/**
- * Return the server minor version or NSNotFound on failure
+ * Return the server minor version or 0 on failure
*/
- (NSUInteger)serverMinorVersion
{
- NSString *ver;
- if ((ver = [self serverVersionString]) != nil) {
- NSString *s = [[ver componentsSeparatedByString:@"."] objectAtIndex:1];
- return (NSUInteger)[s integerValue];
- }
-
- return NSNotFound;
+ // 5.5.33 => 50533 - (5*10'000) => 533 / 100 => 5.33 => 5
+ return ((serverVersionNumber - [self serverMajorVersion]*10000) / 100);
}
/**
- * Return the server release version or NSNotFound on failure
+ * Return the server release version or 0 on failure
*/
- (NSUInteger)serverReleaseVersion
{
- NSString *ver;
- if ((ver = [self serverVersionString]) != nil) {
- NSString *s = [[ver componentsSeparatedByString:@"."] objectAtIndex:2];
- return (NSUInteger)[[[s componentsSeparatedByString:@"-"] objectAtIndex:0] integerValue];
- }
-
- return NSNotFound;
+ // 5.5.33 => 50533 - (5*10'000 + 5*100) => 33
+ return (serverVersionNumber - ([self serverMajorVersion]*10000 + [self serverMinorVersion]*100));
}
#pragma mark -
@@ -105,23 +85,9 @@
*/
- (BOOL)serverVersionIsGreaterThanOrEqualTo:(NSUInteger)aMajorVersion minorVersion:(NSUInteger)aMinorVersion releaseVersion:(NSUInteger)aReleaseVersion
{
- NSString *ver;
- if (!(ver = [self serverVersionString])) return NO;
-
- NSArray *serverVersionParts = [ver componentsSeparatedByString:@"."];
-
- NSUInteger serverMajorVersion = (NSUInteger)[[serverVersionParts objectAtIndex:0] integerValue];
- if (serverMajorVersion < aMajorVersion) return NO;
- if (serverMajorVersion > aMajorVersion) return YES;
+ unsigned long myver = aMajorVersion * 10000 + aMinorVersion * 100 + aReleaseVersion;
- 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;
+ return (serverVersionNumber >= myver);
}
#pragma mark -
@@ -132,6 +98,9 @@
* 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.
+ *
+ * WARNING: This method may return nil if the current thread is cancelled!
+ * You MUST check the isCancelled flag before using the result!
*/
- (SPMySQLResult *)listProcesses
{
@@ -182,4 +151,21 @@
return ![self queryErrored];
}
+- (BOOL)serverShutdown
+{
+ if([self _checkConnectionIfNecessary]) {
+ [self _lockConnection];
+ // Ensure per-thread variables are set up
+ [self _validateThreadSetup];
+ //only SHUTDOWN_DEFAULT is supported right now
+ int res = mysql_shutdown(mySQLConnection, SHUTDOWN_DEFAULT);
+ //update or clear error
+ [self _updateLastErrorInfos];
+ [self _unlockConnection];
+
+ return (res == 0);
+ }
+ return NO;
+}
+
@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h
index c65ec2fb..15b809f1 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h
@@ -85,10 +85,8 @@
CGFloat keepAliveInterval;
uint64_t lastKeepAliveTime;
NSUInteger keepAlivePingFailures;
- NSThread *keepAliveThread;
- pthread_t keepAlivePingThread_t;
- BOOL keepAlivePingThreadActive;
- BOOL keepAliveLastPingSuccess;
+ volatile NSThread *keepAliveThread;
+ volatile BOOL keepAlivePingThreadActive;
BOOL keepAliveLastPingBlocked;
// Encoding details - and also a record of any previous encoding to allow
@@ -101,6 +99,7 @@
// Server details
NSString *serverVariableVersion;
+ unsigned long serverVersionNumber;
// Error state for the last query or connection state
NSUInteger queryErrorID;
@@ -129,6 +128,8 @@
BOOL retryQueriesOnConnectionFailure;
SPMySQLClientFlags clientFlags;
+
+ NSString *_debugLastConnectedEvent;
}
#pragma mark -
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m
index 40c95321..b09a27ee 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m
@@ -145,9 +145,7 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS
keepAlivePingFailures = 0;
lastKeepAliveTime = 0;
keepAliveThread = nil;
- keepAlivePingThread_t = NULL;
keepAlivePingThreadActive = NO;
- keepAliveLastPingSuccess = NO;
keepAliveLastPingBlocked = NO;
// Set up default encoding variables
@@ -176,6 +174,7 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS
// Ensure the server detail records are initialised
serverVariableVersion = nil;
+ serverVersionNumber = 0;
// Start with a blank error state
queryErrorID = 0;
@@ -200,6 +199,8 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS
// while running them
retryQueriesOnConnectionFailure = YES;
+ _debugLastConnectedEvent = nil;
+
// Start the ping keepalive timer
keepAliveTimer = [[SPMySQLKeepAliveTimer alloc] initWithInterval:10 target:self selector:@selector(_keepAlive)];
@@ -254,6 +255,8 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS
if (querySqlstate) [querySqlstate release], querySqlstate = nil;
[delegateDecisionLock release];
+ [_debugLastConnectedEvent release];
+
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[super dealloc];
@@ -279,6 +282,9 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS
* Error checks extensively - if this method fails, it will ask how to proceed and loop depending
* on the status, not returning control until either a connection has been established or
* the connection and document have been closed.
+ *
+ * WARNING: This method may exit early returning NO if the current thread is cancelled!
+ * You MUST check the isCancelled flag before using the result!
*/
- (BOOL)reconnect
{
@@ -327,10 +333,12 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS
* Checks whether the connection to the server is still active. This verifies
* the connection using a ping, and if the connection is found to be down attempts
* to quickly restore it, including the previous state.
+ *
+ * WARNING: This method may return NO if the current thread is cancelled!
+ * You MUST check the isCancelled flag before using the result!
*/
- (BOOL)checkConnection
{
-
// If the connection is not seen as active, don't proceed
if (state != SPMySQLConnected) return NO;
@@ -429,6 +437,14 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS
const char *__crashreporter_info__ = NULL;
asm(".desc ___crashreporter_info__, 0x10");
+static uint64_t _elapsedMicroSecondsSinceAbsoluteTime(uint64_t comparisonTime)
+{
+ uint64_t elapsedTime_t = mach_absolute_time() - comparisonTime;
+ Nanoseconds elapsedTime = AbsoluteToNanoseconds(*(AbsoluteTime *)&(elapsedTime_t));
+
+ return (UnsignedWideToUInt64(elapsedTime) / 1000ULL);
+}
+
@implementation SPMySQLConnection (PrivateAPI)
/**
@@ -439,8 +455,11 @@ asm(".desc ___crashreporter_info__, 0x10");
// If a connection is already active in some form, throw an exception
if (state != SPMySQLDisconnected && state != SPMySQLConnectionLostInBackground) {
- asprintf(&__crashreporter_info__, "Attempted to connect a connection that is not disconnected (SPMySQLConnectionState=%d).", state);
- __builtin_trap();
+ @synchronized (self) {
+ uint64_t diff = _elapsedMicroSecondsSinceAbsoluteTime(initialConnectTime);
+ asprintf(&__crashreporter_info__, "Attempted to connect a connection that is not disconnected (SPMySQLConnectionState=%d).\nIf state==2: Previous connection made %lluµs ago from: %s", state, diff, [_debugLastConnectedEvent cStringUsingEncoding:NSUTF8StringEncoding]);
+ __builtin_trap();
+ }
[NSException raise:NSInternalInconsistencyException format:@"Attempted to connect a connection that is not disconnected (SPMySQLConnectionState=%d).", state];
return NO;
}
@@ -466,17 +485,36 @@ asm(".desc ___crashreporter_info__, 0x10");
// If the connection was cancelled, clean up and don't continue
if (userTriggeredDisconnect) {
mysql_close(mySQLConnection);
- [self _unlockConnection];
mySQLConnection = NULL;
+ [self _unlockConnection];
return NO;
}
// Successfully connected - record connected state and reset tracking variables
state = SPMySQLConnected;
- initialConnectTime = mach_absolute_time();
+
+ @synchronized (self) {
+ initialConnectTime = mach_absolute_time();
+ [_debugLastConnectedEvent release];
+ _debugLastConnectedEvent = [[NSString alloc] initWithFormat:@"thread=%@ stack=%@",[NSThread currentThread],[NSThread callStackSymbols]];
+ }
+
mysqlConnectionThreadId = mySQLConnection->thread_id;
lastConnectionUsedTime = initialConnectTime;
+ // Copy the server version string to the instance variable
+ if (serverVariableVersion) [serverVariableVersion release], serverVariableVersion = nil;
+ // the mysql_get_server_info() function
+ // * returns the version name that is part of the initial connection handshake.
+ // * Unless the connection failed, it will always return a non-null buffer containing at least a '\0'.
+ // * It will never affect the error variables (since it only returns a struct member)
+ //
+ // At that point (handshake) there is no charset and it's highly unlikely this will ever contain something other than ASCII,
+ // but to be safe, we'll use the Latin1 encoding which won't bail on invalid chars.
+ serverVariableVersion = [[NSString alloc] initWithCString:mysql_get_server_info(mySQLConnection) encoding:NSISOLatin1StringEncoding];
+ // this one can actually change the error state, but only if the server version string is not set (ie. no connection)
+ serverVersionNumber = mysql_get_server_version(mySQLConnection);
+
// Update SSL state
connectedWithSSL = NO;
if (useSSL) connectedWithSSL = (mysql_get_ssl_cipher(mySQLConnection))?YES:NO;
@@ -491,9 +529,7 @@ asm(".desc ___crashreporter_info__, 0x10");
keepAlivePingFailures = 0;
// Clear the connection error record
- [self _updateLastErrorID:NSNotFound];
- [self _updateLastErrorMessage:nil];
- [self _updateLastSqlstate:nil];
+ [self _updateLastErrorInfos];
// Unlock the connection
[self _unlockConnection];
@@ -526,10 +562,7 @@ asm(".desc ___crashreporter_info__, 0x10");
// Calling mysql_init will have automatically installed per-thread variables if necessary,
// so track their installation for removal and to avoid recreating again.
- if (!pthread_getspecific(mySQLThreadInitFlagKey)) {
- pthread_setspecific(mySQLThreadInitFlagKey, &mySQLThreadFlag);
- [(NSNotificationCenter *)[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(_removeThreadVariables:) name:NSThreadWillExitNotification object:[NSThread currentThread]];
- }
+ [self _validateThreadSetup];
// Disable automatic reconnection, as it's handled in-framework to preserve
// options, encodings and connection state.
@@ -619,6 +652,9 @@ asm(".desc ___crashreporter_info__, 0x10");
* the connection and document have been closed.
* Runs its own autorelease pool as sometimes called in a thread following proxy changes
* (where the return code doesn't matter).
+ *
+ * WARNING: This method may exit early returning NO if the current thread is cancelled!
+ * You MUST check the isCancelled flag before using the result!
*/
- (BOOL)_reconnectAllowingRetries:(BOOL)canRetry
{
@@ -897,6 +933,7 @@ asm(".desc ___crashreporter_info__, 0x10");
[self _unlockConnection];
[self _cancelKeepAlives];
+ [self _lockConnection];
// Close the underlying MySQL connection if it still appears to be active, and not reading
// or writing. While this may result in a leak of the MySQL object, it prevents crashes
// due to attempts to close a blocked/stuck connection.
@@ -904,17 +941,16 @@ asm(".desc ___crashreporter_info__, 0x10");
mysql_close(mySQLConnection);
}
mySQLConnection = NULL;
+ if (serverVariableVersion) [serverVariableVersion release], serverVariableVersion = nil;
+ serverVersionNumber = 0;
+ if (database) [database release], database = nil;
+ state = SPMySQLDisconnected;
+ [self _unlockConnection];
// If using a connection proxy, disconnect that too
if (proxy) {
[proxy performSelectorOnMainThread:@selector(disconnect) withObject:nil waitUntilDone:YES];
}
-
- // Clear host-specific information
- if (serverVariableVersion) [serverVariableVersion release], serverVariableVersion = nil;
- if (database) [database release], database = nil;
-
- state = SPMySQLDisconnected;
}
/**
@@ -939,10 +975,6 @@ asm(".desc ___crashreporter_info__, 0x10");
[variables setObject:[variableRow objectAtIndex:1] forKey:[variableRow objectAtIndex:0]];
}
- // Copy the server version string to the instance variable
- if (serverVariableVersion) [serverVariableVersion release], serverVariableVersion = nil;
- serverVariableVersion = [[variables objectForKey:@"version"] retain];
-
// Get the connection encoding. Although a specific encoding may have been requested on
// connection, it may be overridden by init_connect commands or connection state changes.
// Default to latin1 for older server versions.
@@ -995,6 +1027,9 @@ asm(".desc ___crashreporter_info__, 0x10");
* each of which requires a round trip to the server - but handles most
* network issues.
* Returns whether the connection is considered still valid.
+ *
+ * WARNING: This method may return NO if the current thread is cancelled!
+ * You MUST check the isCancelled flag before using the result!
*/
- (BOOL)_checkConnectionIfNecessary
{
@@ -1018,21 +1053,22 @@ asm(".desc ___crashreporter_info__, 0x10");
* Ensure that the thread this method is called on has been registered for
* use with MySQL. MySQL requires thread-specific variables for safe
* execution.
+ *
+ * Calling this multiple times per thread is OK.
*/
- (void)_validateThreadSetup
{
-
// Check to see whether the handler has already been installed
if (pthread_getspecific(mySQLThreadInitFlagKey)) return;
// If not, install it
- mysql_thread_init();
+ mysql_thread_init(); // multiple calls per thread OK.
// Mark the thread to avoid multiple installs
pthread_setspecific(mySQLThreadInitFlagKey, &mySQLThreadFlag);
// Set up the notification handler to deregister it
- [(NSNotificationCenter *)[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(_removeThreadVariables:) name:NSThreadWillExitNotification object:[NSThread currentThread]];
+ [[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(_removeThreadVariables:) name:NSThreadWillExitNotification object:[NSThread currentThread]];
}
/**
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m b/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m
index 930180da..53ab116f 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m
@@ -392,9 +392,7 @@ typedef struct st_spmysqlstreamingrowdata {
}
// Update the connection's error statuses to reflect any errors during the content download
- [parentConnection _updateLastErrorID:NSNotFound];
- [parentConnection _updateLastErrorMessage:nil];
- [parentConnection _updateLastSqlstate:nil];
+ [parentConnection _updateLastErrorInfos];
// Unlock the parent connection now all data has been retrieved
[parentConnection _unlockConnection];
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.h b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.h
index 817d2cb7..7d865226 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.h
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.h
@@ -30,12 +30,7 @@
@interface SPMySQLResult (Data_Conversion_Private_API)
++ (void)_initializeDataConversion;
- (id)_getObjectFromBytes:(char *)bytes ofLength:(NSUInteger)length fieldDefinitionIndex:(NSUInteger)fieldIndex previewLength:(NSUInteger)previewLength;
-static inline SPMySQLResultFieldProcessor _processorForField(MYSQL_FIELD aField);
-
-static inline NSString * _stringWithBytes(const void *dataBytes, NSUInteger dataLength, NSStringEncoding aStringEncoding, NSUInteger previewLength);
-static inline NSString * _bitStringWithBytes(const char *bytes, NSUInteger length, NSUInteger padLength);
-static inline NSString * _convertStringData(const void *dataBytes, NSUInteger dataLength, NSStringEncoding aStringEncoding, NSUInteger previewLength);
-
@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m
index 639ff0b9..3b29fb5e 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m
@@ -31,6 +31,16 @@
#import "Data Conversion.h"
+#ifdef SPMYSQL_FOR_UNIT_TESTING
+#define PRIVATE /* public */
+#else
+#define PRIVATE static inline
+#endif
+
+PRIVATE SPMySQLResultFieldProcessor _processorForField(MYSQL_FIELD aField);
+PRIVATE NSString * _bitStringWithBytes(const char *bytes, NSUInteger length, NSUInteger padLength);
+PRIVATE NSString * _convertStringData(const void *dataBytes, NSUInteger dataLength, NSStringEncoding aStringEncoding, NSUInteger previewLength);
+
static SPMySQLResultFieldProcessor fieldProcessingMap[256];
static id NSNullPointer;
static NSStringEncoding NSFromCFStringEncodingBig5;
@@ -68,6 +78,7 @@ static NSStringEncoding NSFromCFStringEncodingGBK_95;
fieldProcessingMap[MYSQL_TYPE_NEWDATE] = SPMySQLResultFieldAsString;
fieldProcessingMap[MYSQL_TYPE_VARCHAR] = SPMySQLResultFieldAsString;
fieldProcessingMap[MYSQL_TYPE_BIT] = SPMySQLResultFieldAsBit;
+ fieldProcessingMap[MYSQL_TYPE_JSON] = SPMySQLResultFieldAsString;
fieldProcessingMap[MYSQL_TYPE_NEWDECIMAL] = SPMySQLResultFieldAsString;
fieldProcessingMap[MYSQL_TYPE_ENUM] = SPMySQLResultFieldAsString;
fieldProcessingMap[MYSQL_TYPE_SET] = SPMySQLResultFieldAsString;
@@ -161,10 +172,12 @@ static NSStringEncoding NSFromCFStringEncodingGBK_95;
return nil;
}
+@end
+
/**
* Returns the field processor to use for a specified field.
*/
-static inline SPMySQLResultFieldProcessor _processorForField(MYSQL_FIELD aField)
+PRIVATE SPMySQLResultFieldProcessor _processorForField(MYSQL_FIELD aField)
{
// Determine the default field processor to use
SPMySQLResultFieldProcessor dataProcessor = fieldProcessingMap[aField.type];
@@ -200,7 +213,7 @@ static inline SPMySQLResultFieldProcessor _processorForField(MYSQL_FIELD aField)
* field length.
* MySQL stores bit data as string data stored in an 8-bit wide character set.
*/
-static inline NSString * _bitStringWithBytes(const char *bytes, NSUInteger length, NSUInteger padLength)
+PRIVATE NSString * _bitStringWithBytes(const char *bytes, NSUInteger length, NSUInteger padLength)
{
NSUInteger i = 0;
NSUInteger bitLength = length << 3;
@@ -209,27 +222,26 @@ static inline NSString * _bitStringWithBytes(const char *bytes, NSUInteger lengt
return nil;
}
- // Ensure padLength is never lower than the length
- if (padLength < bitLength) {
- padLength = bitLength;
- }
-
+ // use whatever is smaller. padLength comes from BIT(x), bitLength from the actual bytes transmitted.
+ // if bitLength < padLength it means the value is smaller than what the field can accomodate.
+ // if bitLength > padLength it means BIT(x) is not a full n bytes long and was extended by mysqls storage.
+ // In that case the additional bits should still be 0 as mysql does not allow to set bits over the size of x.
+ bitLength = MIN(bitLength,padLength);
// Generate a nul-terminated C string representation of the binary data
char *cStringBuffer = malloc(padLength + 1);
- cStringBuffer[padLength] = '\0';
+ memset(cStringBuffer, '0', padLength);
while (i < bitLength)
{
+ // start with the least significant bit (the rightmost bit in the last byte) and move left
+ unsigned char bitInByteMask = i % 8; // 0-7, the cycle is 0,1,...,7,0,...
+ unsigned long bytesOffset = (length - 1) - (i >> 3); // i>>3 == floor(i/8)
++i;
-
- cStringBuffer[padLength - i] = ((bytes[length - 1 - (i >> 3)] >> (i & 0x7)) & 1 ) ? '1' : '0';
+ cStringBuffer[padLength - i] = ((bytes[bytesOffset] & (1 << bitInByteMask)) != 0) ? '1' : '0';
}
-
- while (i++ < padLength)
- {
- cStringBuffer[padLength - i] = '0';
- }
-
+
+ cStringBuffer[padLength] = '\0';
+
// Convert to a string
NSString *returnString = [NSString stringWithUTF8String:cStringBuffer];
@@ -243,7 +255,7 @@ static inline NSString * _bitStringWithBytes(const char *bytes, NSUInteger lengt
* Converts stored string data - which may contain nul bytes - to a native
* Objective-C string, using the current class encoding.
*/
-static inline NSString * _convertStringData(const void *dataBytes, NSUInteger dataLength, NSStringEncoding aStringEncoding, NSUInteger previewLength)
+PRIVATE NSString * _convertStringData(const void *dataBytes, NSUInteger dataLength, NSStringEncoding aStringEncoding, NSUInteger previewLength)
{
// Fast case - if not using a preview length, or if the data length is shorter, return the requested data.
@@ -414,5 +426,4 @@ static inline NSString * _convertStringData(const void *dataBytes, NSUInteger da
return previewString;
}
-
-@end
+#undef PRIVATE
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m
index c61b9140..ec52e0e3 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m
@@ -29,6 +29,7 @@
// More info at <https://github.com/sequelpro/sequelpro>
#import "Field Definitions.h"
+#import "SPMySQL Private APIs.h"
@interface SPMySQLResult (Field_Definitions_Private_API)
@@ -40,14 +41,6 @@
@end
-// Import a private declaration from the SPMySQLResult file for use
-@interface SPMySQLResult (Private_API)
-
-- (NSString *)_stringWithBytes:(const void *)bytes length:(NSUInteger)length;
-- (NSString *)_lossyStringWithBytes:(const void *)bytes length:(NSUInteger)length wasLossy:(BOOL *)outLossy;
-
-@end
-
#define MAGIC_BINARY_CHARSET_NR 63
const SPMySQLResultCharset SPMySQLCharsetMap[] =
@@ -379,7 +372,7 @@ const SPMySQLResultCharset SPMySQLCharsetMap[] =
switch (type) {
- case FIELD_TYPE_BIT:
+ case MYSQL_TYPE_BIT:
return @"BIT";
case MYSQL_TYPE_DECIMAL:
@@ -482,6 +475,9 @@ const SPMySQLResultCharset SPMySQLCharsetMap[] =
case MYSQL_TYPE_GEOMETRY:
return @"GEOMETRY";
+
+ case MYSQL_TYPE_JSON:
+ return @"JSON";
default:
return @"UNKNOWN";
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m
index 5f54960c..2e1cb2ba 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m
@@ -318,7 +318,7 @@ static id NSNullPointer;
{
return [[[NSString alloc] initWithBytes:bytes length:length encoding:stringEncoding] autorelease];
}
-
+#warning duplicate code with Data Conversion.m stringForDataBytes:length:encoding: (↑, ↓)
- (NSString *)_lossyStringWithBytes:(const void *)bytes length:(NSUInteger)length wasLossy:(BOOL *)outLossy
{
if(!bytes || !length) return @""; //to match -[NSString initWithBytes:length:encoding:]
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.m b/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.m
index 86a6b2b5..29f83e0e 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.m
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.m
@@ -809,9 +809,7 @@ static inline void SPMySQLStreamingResultStoreFreeRowData(SPMySQLStreamingResult
}
// Update the connection's error statuses to reflect any errors during the content download
- [parentConnection _updateLastErrorID:NSNotFound];
- [parentConnection _updateLastErrorMessage:nil];
- [parentConnection _updateLastSqlstate:nil];
+ [parentConnection _updateLastErrorInfos];
// Unlock the parent connection now all data has been retrieved
[parentConnection _unlockConnection];