diff options
author | stuconnolly <stuart02@gmail.com> | 2009-07-21 16:47:11 +0000 |
---|---|---|
committer | stuconnolly <stuart02@gmail.com> | 2009-07-21 16:47:11 +0000 |
commit | 8b8f3e6cea540b17262aadf6d97a8ad28fe41c03 (patch) | |
tree | ad6bb7f53b03924aa24d0cf5822a27b3bd453592 /Frameworks/MCPKit/MCPFoundationKit | |
parent | 383863f98dfc488db0181e01d39da1bb025d421b (diff) | |
download | sequelpro-8b8f3e6cea540b17262aadf6d97a8ad28fe41c03.tar.gz sequelpro-8b8f3e6cea540b17262aadf6d97a8ad28fe41c03.tar.bz2 sequelpro-8b8f3e6cea540b17262aadf6d97a8ad28fe41c03.zip |
Merge framework integration branch back to trunk. Summary of changes:
- Includes all custom code from subclasses CMMCPConnection and CMMCPResult, meaning they have subsequently been removed from the project.
- All previous Sequel Pro specific code in the above subclasses has been removed in favour of the delegate (currently set to TableDocumet) informing the framework of such information.
- All references to CMMCPConnection and CMMCPResult have subsequently been changed to MCPConnection and MCPResult.
- Framework includes MySQL 5.1.36 client libraries and source headers.
- Framework is now built as a 4-way (32/64 bit, i386/PPC arch) binary.
- All import references to <MCPKit_bundled/MCPKit_bundled.h> have been changed to <MCPKit/MCPKit.h>.
- New script 'build-mysql-client.sh' can be used to build the MySQL client libraries from the MySQL source. See the script's header for a list of available options or run it with no arguments to display it's usage.
Note that there are still a few changes to be made to the framework with regard to removing Sequel Pro specific calls to the delegate. These however can be made later on as they have no effect on functionality and are merely design changes.
Also, note that any future development done on the framework should be made to be as 'generic' as possible, with no Sequel Pro specific references. This should allow the framework to be integrated into another project without the need for SP specific code.
Diffstat (limited to 'Frameworks/MCPKit/MCPFoundationKit')
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPConnection.h | 226 | ||||
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m | 1995 | ||||
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPConnectionProxy.h | 70 | ||||
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPConstants.h | 64 | ||||
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPFastQueries.h | 46 | ||||
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPFastQueries.m | 113 | ||||
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPKit.h | 40 | ||||
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPNull.h | 35 | ||||
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPNull.m | 41 | ||||
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPNumber.h | 83 | ||||
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPNumber.m | 301 | ||||
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPResult.h | 85 | ||||
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPResult.m | 1360 | ||||
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPResultPlus.h | 42 | ||||
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPResultPlus.m | 188 |
15 files changed, 4689 insertions, 0 deletions
diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.h b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.h new file mode 100644 index 00000000..a6457494 --- /dev/null +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.h @@ -0,0 +1,226 @@ +// +// $Id: MCPConnection.h 1069 2009-07-19 23:12:50Z rowanb $ +// +// MCPConnection.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 08/12/2001. +// Copyright (c) 2001 Serge Cohen. All rights reserved. +// +// Forked by the Sequel Pro team (sequelpro.com), April 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> + +#import "MCPConstants.h" + +#import "mysql.h" + +@class MCPResult; +@protocol MCPConnectionProxy; + +/** + * NSStringDataUsingLossyEncoding(aStr, enc, lossy) := [aStr dataUsingEncoding:enc allowLossyConversion:lossy] + */ +static inline NSData* NSStringDataUsingLossyEncoding(NSString* self, int encoding, int lossy) +{ + typedef NSData* (*SPStringDataUsingLossyEncodingMethodPtr)(NSString*, SEL, int, int); + static SPStringDataUsingLossyEncodingMethodPtr SPNSStringDataUsingLossyEncoding; + if (!SPNSStringDataUsingLossyEncoding) SPNSStringDataUsingLossyEncoding = (SPStringDataUsingLossyEncodingMethodPtr)[self methodForSelector:@selector(dataUsingEncoding:allowLossyConversion:)]; + NSData* to_return = SPNSStringDataUsingLossyEncoding(self, @selector(dataUsingEncoding:allowLossyConversion:), encoding, lossy); + return to_return; +} + +// Connection delegate interface +@interface NSObject (MCPConnectionDelegate) + +- (void)willQueryString:(NSString *)query connection:(id)connection; +- (void)queryGaveError:(NSString *)error connection:(id)connection; +- (BOOL)connectionEncodingViaLatin1:(id)connection; +- (NSString *)keychainPasswordForConnection:(id)connection; +- (MCPConnectionCheck)connectionLost:(id)connection; + +@end + +@interface MCPConnection : NSObject +{ + MYSQL *mConnection; /* The inited MySQL connection. */ + BOOL mConnected; /* Reflect the fact that the connection is already in place or not. */ + NSStringEncoding mEncoding; /* The encoding used by MySQL server, to ISO-1 default. */ + NSTimeZone *mTimeZone; /* The time zone of the session. */ + unsigned int mConnectionFlags; /* The flags to be used for the connection to the database. */ + id delegate; /* Connection delegate */ + + NSLock *queryLock; /* Anything that performs a mysql_net_read is not thread-safe: mysql queries, pings */ + + BOOL useKeepAlive; + int connectionTimeout; + float keepAliveInterval; + + id <MCPConnectionProxy> connectionProxy; + NSString *connectionLogin; + NSString *connectionPassword; + NSString *connectionHost; + int connectionPort; + NSString *connectionSocket; + int maxAllowedPacketSize; + unsigned long connectionThreadId; + + int currentProxyState; + + double lastQueryExecutionTime; + double lastQueryExecutedAtTime; + NSString *lastQueryErrorMessage; + unsigned int lastQueryErrorId; + my_ulonglong lastQueryAffectedRows; + + BOOL isMaxAllowedPacketEditable; + + NSString *serverVersionString; + + NSTimer *keepAliveTimer; + NSDate *lastKeepAliveSuccess; + uint64_t connectionStartTime; + + BOOL retryAllowed; + BOOL delegateQueryLogging; + BOOL delegateResponseToWillQueryString; + + // Pointers + IMP cStringPtr; + IMP willQueryStringPtr; + IMP stopKeepAliveTimerPtr; + IMP startKeepAliveTimerResettingStatePtr; + IMP timeConnectedPtr; + + // Selectors + SEL cStringSEL; + SEL willQueryStringSEL; + SEL stopKeepAliveTimerSEL; + SEL startKeepAliveTimerResettingStateSEL; + SEL timeConnectedSEL; +} + +@property (readwrite, assign) id delegate; +@property (readwrite, assign) BOOL useKeepAlive; +@property (readwrite, assign) BOOL delegateQueryLogging; +@property (readwrite, assign) int connectionTimeout; +@property (readwrite, assign) float keepAliveInterval; + +// Initialisation +- (id)initToHost:(NSString *)host withLogin:(NSString *)login usingPort:(int)port; +- (id)initToSocket:(NSString *)socket withLogin:(NSString *)login; + +// Connection details +- (BOOL)setPort:(int)thePort; +- (BOOL)setPassword:(NSString *)thePassword; + +// Proxy +- (BOOL)setConnectionProxy:(id <MCPConnectionProxy>)proxy; +- (void)connectionProxyStateChange:(id <MCPConnectionProxy>)proxy; + +// Connection +- (BOOL)connect; +- (void)disconnect; +- (BOOL)reconnect; +- (BOOL)isConnected; +- (BOOL)checkConnection; +- (BOOL)pingConnection; +- (void)startKeepAliveTimerResettingState:(BOOL)resetState; +- (void)stopKeepAliveTimer; +- (void)keepAlive:(NSTimer *)theTimer; +- (void)threadedKeepAlive; +- (void)restoreConnectionDetails; +- (void)setAllowQueryRetries:(BOOL)allow; +- (double)timeConnected; + +// Server versions +- (int)serverMajorVersion; +- (int)serverMinorVersion; +- (int)serverReleaseVersion; + +// MySQL defaults ++ (NSDictionary *)getMySQLLocales; ++ (NSStringEncoding)encodingForMySQLEncoding:(const char *)mysqlEncoding; ++ (NSStringEncoding)defaultMySQLEncoding; ++ (BOOL)isErrorNumberConnectionError:(int)theErrorNumber; + +// Class maintenance ++ (void)setTruncateLongFieldInLogs:(BOOL)iTruncFlag; ++ (BOOL)truncateLongField; +- (BOOL)setConnectionOption:(int)option toValue:(BOOL)value; +- (BOOL)connectWithLogin:(NSString *)login password:(NSString *)pass host:(NSString *)host port:(int)port socket:(NSString *)socket; + +- (BOOL)selectDB:(NSString *)dbName; + +// Error information +- (NSString *)getLastErrorMessage; +- (void)setLastErrorMessage:(NSString *)theErrorMessage; +- (unsigned int)getLastErrorID; ++ (BOOL)isErrorNumberConnectionError:(int)theErrorNumber; + +// Queries +- (NSString *)prepareBinaryData:(NSData *)theData; +- (NSString *)prepareString:(NSString *)theString; +- (NSString *)quoteObject:(id)theObject; +- (MCPResult *)queryString:(NSString *)query; +- (MCPResult *)queryString:(NSString *)query usingEncoding:(NSStringEncoding)encoding; +- (double)lastQueryExecutionTime; +- (my_ulonglong)affectedRows; +- (my_ulonglong)insertId; + +// Database structure +- (MCPResult *)listDBs; +- (MCPResult *)listDBsLike:(NSString *)dbsName; +- (MCPResult *)listTables; +- (MCPResult *)listTablesLike:(NSString *)tablesName; +- (MCPResult *)listTablesFromDB:(NSString *)dbName like:(NSString *)tablesName; +- (MCPResult *)listFieldsFromTable:(NSString *)tableName; +- (MCPResult *)listFieldsFromTable:(NSString *)tableName like:(NSString *)fieldsName; + +// Server information +- (NSString *)clientInfo; +- (NSString *)hostInfo; +- (NSString *)serverInfo; +- (NSNumber *)protoInfo; +- (MCPResult *)listProcesses; +- (BOOL)killProcess:(unsigned long)pid; +- (NSString *)findSocketPath; + +// Encoding +- (void)setEncoding:(NSStringEncoding)theEncoding; +- (NSStringEncoding)encoding; + +// Time zone +- (void)setTimeZone:(NSTimeZone *)iTimeZone; +- (NSTimeZone *)timeZone; + +// Packet size +- (BOOL)fetchMaxAllowedPacket; +- (int)getMaxAllowedPacket; +- (BOOL)isMaxAllowedPacketEditable; +- (int)setMaxAllowedPacketTo:(int)newSize resetSize:(BOOL)reset; + +// Data conversion +- (const char *)cStringFromString:(NSString *)theString; +- (const char *)cStringFromString:(NSString *)theString usingEncoding:(NSStringEncoding)encoding; +- (NSString *)stringWithCString:(const char *)theCString; +- (NSString *)stringWithText:(NSData *)theTextData; + +@end diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m new file mode 100644 index 00000000..fb0dcc10 --- /dev/null +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m @@ -0,0 +1,1995 @@ +// +// $Id: MCPConnection.m 1070 2009-07-19 23:21:19Z rowanb $ +// +// MCPConnection.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 08/12/2001. +// Copyright (c) 2001 Serge Cohen. All rights reserved. +// +// Forked by the Sequel Pro team (sequelpro.com), April 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import "MCPConnection.h" +#import "MCPResult.h" +#import "MCPNumber.h" +#import "MCPNull.h" +#import "MCPConnectionProxy.h" + +#include <unistd.h> +#include <setjmp.h> +#include <mach/mach_time.h> + +static jmp_buf pingTimeoutJumpLocation; +static void forcePingTimeout(int signalNumber); + +const unsigned int kMCPConnectionDefaultOption = CLIENT_COMPRESS; +const char *kMCPConnectionDefaultSocket = MYSQL_UNIX_ADDR; +const unsigned int kMCPConnection_Not_Inited = 1000; +const unsigned int kLengthOfTruncationForLog = 100; + +static BOOL sTruncateLongFieldInLogs = YES; + +/** + * Privte API + */ +@interface MCPConnection (PrivateAPI) + +- (void)_getServerVersionString; + +@end + +@implementation MCPConnection + +// Synthesize ivars +@synthesize delegate; +@synthesize useKeepAlive; +@synthesize delegateQueryLogging; +@synthesize connectionTimeout; +@synthesize keepAliveInterval; + +#pragma mark - +#pragma mark Initialisation + +/** + * Initialise a MySQLConnection without making a connection, most likely useless, except with !{setConnectionOption:withArgument:}. + * + * Because this method is not making a connection to any MySQL server, it can not know already what the DB server encoding will be, + * hence the encoding is set to some default (at present this is NSISOLatin1StringEncoding). Obviously this is reset to a proper + * value as soon as a DB connection is performed. + */ +- (id)init +{ + if ((self = [super init])) { + mConnection = mysql_init(NULL); + mConnected = NO; + + if (mConnection == NULL) { + [self autorelease]; + + return nil; + } + + mEncoding = NSISOLatin1StringEncoding; + mConnectionFlags = kMCPConnectionDefaultOption; + + queryLock = [[NSLock alloc] init]; + + connectionHost = nil; + connectionLogin = nil; + connectionSocket = nil; + connectionPassword = nil; + keepAliveTimer = nil; + connectionProxy = nil; + lastKeepAliveSuccess = nil; + connectionStartTime = -1; + lastQueryExecutedAtTime = INT_MAX; + + // Initialize ivar defaults + connectionTimeout = 10; + useKeepAlive = YES; + keepAliveInterval = 60; + + connectionThreadId = 0; + maxAllowedPacketSize = -1; + lastQueryExecutionTime = 0; + lastQueryErrorId = 0; + lastQueryErrorMessage = nil; + lastQueryAffectedRows = 0; + + // Default to allowing queries to be reattempted if they fail due to connection issues + retryAllowed = YES; + + // Obtain SEL references + willQueryStringSEL = @selector(willQueryString:connection:); + stopKeepAliveTimerSEL = @selector(stopKeepAliveTimer); + startKeepAliveTimerResettingStateSEL = @selector(startKeepAliveTimerResettingState:); + cStringSEL = @selector(cStringFromString:); + + // Obtain pointers + cStringPtr = [self methodForSelector:cStringSEL]; + stopKeepAliveTimerPtr = [self methodForSelector:stopKeepAliveTimerSEL]; + startKeepAliveTimerResettingStatePtr = [self methodForSelector:startKeepAliveTimerResettingStateSEL]; + } + + return self; +} + +/** + * Inialize connection using the supplied host details. + */ +- (id)initToHost:(NSString *)host withLogin:(NSString *)login usingPort:(int)port +{ + if ((self = [self init])) { + if (!host) host = @""; + if (!login) login = @""; + + connectionHost = [[NSString alloc] initWithString:host]; + connectionLogin = [[NSString alloc] initWithString:login]; + connectionPort = port; + connectionSocket = nil; + } + + return self; +} + +/** + * Inialize connection using the supplied socket details. + */ +- (id)initToSocket:(NSString *)socket withLogin:(NSString *)login +{ + if ((self = [self init])) { + if (!socket || ![socket length]) { + socket = [self findSocketPath]; + if (!socket) socket = @""; + } + + if (!login) login = @""; + + connectionHost = nil; + connectionLogin = [[NSString alloc] initWithString:login]; + connectionSocket = [[NSString alloc] initWithString:socket]; + connectionPort = 0; + } + + return self; +} + +#pragma mark - +#pragma mark Connection details + +/** + * Sets or updates the connection port - for use with tunnels. + */ +- (BOOL)setPort:(int)thePort +{ + connectionPort = thePort; + + return YES; +} + +/** + * Sets the password to be stored locally. + * Providing a keychain name is much more secure. + */ +- (BOOL)setPassword:(NSString *)thePassword +{ + if (connectionPassword) [connectionPassword release], connectionPassword = nil; + + if (!thePassword) thePassword = @""; + + connectionPassword = [[NSString alloc] initWithString:thePassword]; + + return YES; +} + +#pragma mark - +#pragma mark Connection proxy + +/* + * Set a connection proxy object to connect through. This object will be retained locally, + * and will be automatically connected/connection checked/reconnected/disconnected + * together with the main connection. + */ +- (BOOL)setConnectionProxy:(id <MCPConnectionProxy>)proxy +{ + connectionProxy = proxy; + [connectionProxy retain]; + + currentProxyState = [connectionProxy state]; + [connectionProxy setConnectionStateChangeSelector:@selector(connectionProxyStateChange:) delegate:self]; + + return YES; +} + +/** + * Handle any state changes in the associated connection proxy. + */ +- (void)connectionProxyStateChange:(id <MCPConnectionProxy>)proxy +{ + int newState = [proxy state]; + + // Restart the tunnel if it dies + if (mConnected && newState == PROXY_STATE_IDLE && currentProxyState == PROXY_STATE_CONNECTED) { + currentProxyState = newState; + [connectionProxy setConnectionStateChangeSelector:nil delegate:nil]; + [self reconnect]; + + return; + } + + currentProxyState = newState; +} + +#pragma mark - +#pragma mark Connection + +/** + * Add a new connection method, intended for use with the init methods above. + * Uses the stored details to instantiate a connection to the specified server, + * including custom timeouts - used for pings, not for long-running commands. + */ +- (BOOL)connect +{ + const char *theLogin = [self cStringFromString:connectionLogin]; + const char *theHost; + const char *thePass; + const char *theSocket; + void *theRet; + + // Disconnect if a connection is already active + if (mConnected) { + [self disconnect]; + mConnection = mysql_init(NULL); + if (mConnection == NULL) return NO; + } + + if (mConnection != NULL) { + + // Ensure the custom timeout option is set + mysql_options(mConnection, MYSQL_OPT_CONNECT_TIMEOUT, (const void *)&connectionTimeout); + + // Set automatic reconnection for use with mysql_ping + // TODO: Automatic reconnection is currently used by MCPConnection, using thread IDs to + // detect when this has occurred. Custom reconnection may be preferable. + my_bool trueBool = TRUE; + mysql_options(mConnection, MYSQL_OPT_RECONNECT, &trueBool); + + // Ensure compression is enabled where possible + mysql_options(mConnection, MYSQL_OPT_COMPRESS, 0); + } + + // Set the host as appropriate + if (!connectionHost || ![connectionHost length]) { + theHost = NULL; + + + } else { + theHost = [self cStringFromString:connectionHost]; + } + + // Use the default socket if none is set, or set appropriately + if (connectionSocket == nil || ![connectionSocket length]) { + theSocket = kMCPConnectionDefaultSocket; + } else { + theSocket = [self cStringFromString:connectionSocket]; + } + + // Select the password from the provided method + if (!connectionPassword) { + if (delegate && [delegate respondsToSelector:@selector(keychainPasswordForConnection:)]) { + thePass = [self cStringFromString:[delegate keychainPasswordForConnection:self]]; + } + } else { + thePass = [self cStringFromString:connectionPassword]; + } + + // Connect + theRet = mysql_real_connect(mConnection, theHost, theLogin, thePass, NULL, connectionPort, theSocket, mConnectionFlags); + thePass = NULL; + + if (theRet != mConnection) { + [self setLastErrorMessage:nil]; + + lastQueryErrorId = mysql_errno(mConnection); + + return mConnected = NO; + } + + mConnected = YES; + connectionStartTime = mach_absolute_time(); + mEncoding = [MCPConnection encodingForMySQLEncoding:mysql_character_set_name(mConnection)]; + connectionThreadId = mConnection->thread_id; + [self timeZone]; // Getting the timezone used by the server. + + isMaxAllowedPacketEditable = [self isMaxAllowedPacketEditable]; + + if (![self fetchMaxAllowedPacket]) { + [self setLastErrorMessage:nil]; + + lastQueryErrorId = mysql_errno(mConnection); + + return mConnected = NO; + } + + // Start the keepalive timer + [self startKeepAliveTimerResettingState:YES]; + + return mConnected; +} + +/** + * Disconnect the current connection. + */ +- (void)disconnect +{ + if (mConnected) { + mysql_close(mConnection); + mConnection = NULL; + } + + mConnected = NO; + + if (connectionProxy) { + [connectionProxy disconnect]; + } + + if (serverVersionString != nil) { + [serverVersionString release]; + serverVersionString = nil; + } + + [self stopKeepAliveTimer]; +} + +/** + * Reconnect to the currently "active" - but possibly disconnected - connection, using the + * stored details. + * 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. + */ +- (BOOL)reconnect +{ + NSString *currentEncoding = nil; + BOOL currentEncodingUsesLatin1Transport = NO; + NSString *currentDatabase = nil; + + // Store the current database and encoding so they can be re-set if reconnection was successful + if (delegate && [delegate valueForKey:@"selectedDatabase"]) { + currentDatabase = [NSString stringWithString:[delegate valueForKey:@"selectedDatabase"]]; + } + + if (delegate && [delegate valueForKey:@"_encoding"]) { + currentEncoding = [NSString stringWithString:[delegate valueForKey:@"_encoding"]]; + } + + if (delegate && [delegate respondsToSelector:@selector(connectionEncodingViaLatin1:)]) { + currentEncodingUsesLatin1Transport = [delegate connectionEncodingViaLatin1:self]; + } + + // Close the connection if it exists. + if (mConnected) { + mysql_close(mConnection); + mConnection = NULL; + } + + mConnected = NO; + + // If there is a tunnel, ensure it's disconnected and attempt to reconnect it in blocking fashion + if (connectionProxy) { + [connectionProxy setConnectionStateChangeSelector:nil delegate:nil]; + if ([connectionProxy state] != PROXY_STATE_IDLE) [connectionProxy disconnect]; + [connectionProxy connect]; + NSDate *tunnelStartDate = [NSDate date], *interfaceInteractionTimer; + + // Allow the tunnel to attempt to connect in a loop + while (1) { + if ([connectionProxy state] == PROXY_STATE_CONNECTED) { + connectionPort = [connectionProxy localPort]; + break; + } + if ([[NSDate date] timeIntervalSinceDate:tunnelStartDate] > (connectionTimeout + 1)) { + [connectionProxy disconnect]; + break; + } + + // Process events for a short time, allowing dialogs to be shown but waiting for the tunnel + interfaceInteractionTimer = [NSDate date]; + [[NSRunLoop currentRunLoop] runMode:NSModalPanelRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.25]]; + tunnelStartDate = [tunnelStartDate addTimeInterval:([[NSDate date] timeIntervalSinceDate:interfaceInteractionTimer] - 0.25)]; + } + + currentProxyState = [connectionProxy state]; + [connectionProxy setConnectionStateChangeSelector:@selector(sshTunnelStateChange:) delegate:self]; + } + + if (!connectionProxy || [connectionProxy state] == PROXY_STATE_CONNECTED) { + + // Attempt to reinitialise the connection - if this fails, it will still be set to NULL. + if (mConnection == NULL) { + mConnection = mysql_init(NULL); + } + + if (mConnection != NULL) { + + // Attempt to reestablish the connection + [self connect]; + } + } + + // If the connection was successfully established, reselect the old database and encoding if appropriate. + if (mConnected) { + if (currentDatabase) { + [self selectDB:currentDatabase]; + } + + if (currentEncoding) { + [self queryString:[NSString stringWithFormat:@"/*!40101 SET NAMES '%@' */", currentEncoding]]; + [self setEncoding:[MCPConnection encodingForMySQLEncoding:[currentEncoding UTF8String]]]; + if (currentEncodingUsesLatin1Transport) { + [self queryString:@"/*!40101 SET CHARACTER_SET_RESULTS=latin1 */"]; + } + } + } + else { + [self setLastErrorMessage:nil]; + + // Default to retry + MCPConnectionCheck failureDecision = MCPConnectionCheckReconnect; + + // Ask delegate what to do + if ([delegate respondsToSelector:@selector(connectionLost:)]) { + failureDecision = [delegate connectionLost:self]; + } + + switch (failureDecision) { + case MCPConnectionCheckDisconnect: + return NO; + default: + return [self reconnect]; + } + } + + return mConnected; +} + +/** + * Returns YES if the MCPConnection is connected to a DB, NO otherwise. + */ +- (BOOL)isConnected +{ + return mConnected; +} + +/** + * Checks if the connection to the server is still on. + * If not, tries to reconnect (changing no parameters from the MYSQL pointer). + * This method just uses mysql_ping(). + */ +- (BOOL)checkConnection +{ + if (!mConnected) return NO; + + BOOL connectionVerified = FALSE; + + // Check whether the connection is still operational via a wrapped version of MySQL ping. + connectionVerified = [self pingConnection]; + + // If the connection doesn't appear to be responding, show a dialog asking how to proceed + if (!connectionVerified) { + + // Default to retry + MCPConnectionCheck failureDecision = MCPConnectionCheckRetry; + + // Ask delegate what to do + if (delegate && [delegate respondsToSelector:@selector(connectionLost:)]) { + failureDecision = [delegate connectionLost:self]; + } else { + failureDecision = MCPConnectionCheckDisconnect; + } + + switch (failureDecision) { + // 'Reconnect' has been selected. Request a reconnect, and retry. + case MCPConnectionCheckReconnect: + [self reconnect]; + + return [self checkConnection]; + + // 'Disconnect' has been selected. Close the parent window, which will handle disconnections, and return false. + case MCPConnectionCheckDisconnect: + return NO; + + // 'Retry' has been selected - return a recursive call. + case MCPConnectionCheckRetry: + return [self checkConnection]; + } + + // If a connection exists, check whether the thread id differs; if so, the connection has + // probably been reestablished and we need to reset the connection encoding + } else if (connectionThreadId != mConnection->thread_id) [self restoreConnectionDetails]; + + return connectionVerified; +} + +/** + * This function provides a method of pinging the remote server while running a SIGALRM + * to enforce the specified connection time. This is low-level but effective, and required + * because low-level net reads can block indefintely 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. + * Unlike mysql_ping, this function returns FALSE on failure and TRUE on success. + */ +- (BOOL) pingConnection +{ + struct sigaction timeoutAction; + NSDate *startDate = [[NSDate alloc] initWithTimeIntervalSinceNow:0]; + BOOL pingSuccess = FALSE; + + // Construct the SIGALRM to fire after the connection timeout if it isn't cleared, calling the forcePingTimeout function. + timeoutAction.sa_handler = forcePingTimeout; + sigemptyset(&timeoutAction.sa_mask); + timeoutAction.sa_flags = 0; + sigaction(SIGALRM, &timeoutAction, NULL); + alarm(connectionTimeout+1); + + [queryLock lock]; + + // Set up a "restore point", returning 0; if longjmp is used later with this reference, execution + // jumps back to this point and returns a nonzero value, so this function evaluates to false when initially + // set and true if it's called again. + if (setjmp(pingTimeoutJumpLocation)) { + + // The connection timed out - we want to return false. + pingSuccess = FALSE; + + // On direct execution: + } else { + + // Run mysql_ping, which returns 0 on success, and otherwise an error. + pingSuccess = (BOOL)(! mysql_ping(mConnection)); + + // If the ping failed within a second, try another one; this is because a terminated-but-then + // restored connection is at times restored or functional after a ping, but the ping still returns + // an error. This additional check ensures the returned status is correct with minimal other effect. + if (!pingSuccess && ([startDate timeIntervalSinceNow] > -1)) { + pingSuccess = (BOOL)(! mysql_ping(mConnection)); + } + } + + [queryLock unlock]; + + // Reset and clear the SIGALRM used to check connection timeouts. + alarm(0); + timeoutAction.sa_handler = SIG_IGN; + sigemptyset(&timeoutAction.sa_mask); + timeoutAction.sa_flags = 0; + sigaction(SIGALRM, &timeoutAction, NULL); + + [startDate release]; + + return pingSuccess; +} + +/** + * This function is paired with pingConnection, and provides a method of enforcing the connection + * timeout when mysql_ping does not respect the specified limits. + */ +static void forcePingTimeout(int signalNumber) +{ + longjmp(pingTimeoutJumpLocation, 1); +} + +/** + * Restarts a keepalive to fire in the future. + */ +- (void)startKeepAliveTimerResettingState:(BOOL)resetState +{ + if (keepAliveTimer) [self stopKeepAliveTimer]; + if (!mConnected) return; + + if (resetState && lastKeepAliveSuccess) { + [lastKeepAliveSuccess release]; + lastKeepAliveSuccess = nil; + } + + if (useKeepAlive && keepAliveInterval) { + keepAliveTimer = [NSTimer + scheduledTimerWithTimeInterval:keepAliveInterval + target:self + selector:@selector(keepAlive:) + userInfo:nil + repeats:NO]; + [keepAliveTimer retain]; + } +} + +/** + * Stops a keepalive if one is set for the future. + */ +- (void)stopKeepAliveTimer +{ + if (!keepAliveTimer) return; + [keepAliveTimer invalidate]; + [keepAliveTimer release]; + keepAliveTimer = nil; +} + +/** + * Keeps a connection alive by running a ping. + */ +- (void)keepAlive:(NSTimer *)theTimer +{ + if (!mConnected) return; + + // If there a successful keepalive record exists, and it was more than 5*keepaliveinterval ago, + // abort. This prevents endless spawning of threads in a state where the connection has been + // cut but mysql doesn't pick up on the fact - see comment for pingConnection above. The same + // forced-timeout approach cannot be used here on a background thread. + // When the connection is disconnected in code, these 5 "hanging" threads are automatically cleaned. + if (lastKeepAliveSuccess && [lastKeepAliveSuccess timeIntervalSinceNow] < -5 * keepAliveInterval) return; + + [NSThread detachNewThreadSelector:@selector(threadedKeepAlive) toTarget:self withObject:nil]; + [self startKeepAliveTimerResettingState:NO]; +} + +/** + * A threaded keepalive to avoid blocking the interface + */ +- (void)threadedKeepAlive +{ + if (!mConnected) return; + + if (![queryLock tryLock]) return; + [queryLock unlock]; + + // Don't wrap this ping in a lock - will block main thread on read issues, and can't use + // the setjmp/lngjmp safety net in a thread. + mysql_ping(mConnection); + + if (lastKeepAliveSuccess) { + [lastKeepAliveSuccess release]; + lastKeepAliveSuccess = nil; + } + + lastKeepAliveSuccess = [[NSDate alloc] initWithTimeIntervalSinceNow:0]; +} + +/** + * Restore the connection encoding details as necessary based on the delegate-provided + * details. + */ +- (void)restoreConnectionDetails +{ + connectionThreadId = mConnection->thread_id; + connectionStartTime = mach_absolute_time(); + [self fetchMaxAllowedPacket]; + + if (delegate && [delegate valueForKey:@"_encoding"]) { + [self queryString:[NSString stringWithFormat:@"/*!40101 SET NAMES '%@' */", [NSString stringWithString:[delegate valueForKey:@"_encoding"]]]]; + if (delegate && [delegate respondsToSelector:@selector(connectionEncodingViaLatin1:)]) { + if ([delegate connectionEncodingViaLatin1:self]) [self queryString:@"/*!40101 SET CHARACTER_SET_RESULTS=latin1 */"]; + } + } +} + +/** + * Allow controlling over whether queries are allowed to retry after a connection failure. + * This defaults to YES on init, and is intended to allow temporary disabling in situations + * where the query result is checked and displayed to the user without any repurcussions on + * failure. + */ +- (void)setAllowQueryRetries:(BOOL)allow +{ + retryAllowed = allow; +} + +/** + * Retrieve the time elapsed since the connection was established, in seconds. + * This time is retrieved in a monotonically increasing fashion and is high + * precision; it is used internally for query timing, and is reset on reconnections. + */ +- (double)timeConnected +{ + if (connectionStartTime == -1) return -1; + + uint64_t currentTime_t = mach_absolute_time() - connectionStartTime; + Nanoseconds elapsedTime = AbsoluteToNanoseconds(*(AbsoluteTime *)&(currentTime_t)); + + return (((double)UnsignedWideToUInt64(elapsedTime)) * 1e-9); +} + +#pragma mark - +#pragma mark Server versions + +/** + * rReturn the server major version or -1 on fail + */ +- (int)serverMajorVersion +{ + + if (mConnected) { + if (serverVersionString == nil) { + [self _getServerVersionString]; + } + + if (serverVersionString != nil) { + return [[[serverVersionString componentsSeparatedByString:@"."] objectAtIndex:0] intValue]; + } + } + + return -1; +} + +/** + * Return the server minor version or -1 on fail + */ +- (int)serverMinorVersion +{ + + if (mConnected) { + if (serverVersionString == nil) { + [self _getServerVersionString]; + } + + if(serverVersionString != nil) { + return [[[serverVersionString componentsSeparatedByString:@"."] objectAtIndex:1] intValue]; + } + } + + return -1; +} + +/** + * Return the server release version or -1 on fail + */ +- (int)serverReleaseVersion +{ + if (mConnected) { + if (serverVersionString == nil) { + [self _getServerVersionString]; + } + + if (serverVersionString != nil) { + NSString *s = [[serverVersionString componentsSeparatedByString:@"."] objectAtIndex:2]; + return [[[s componentsSeparatedByString:@"-"] objectAtIndex:0] intValue]; + } + } + + return -1; +} + +#pragma mark - +#pragma mark MySQL defaults + +/** + * This class is used to keep a connection with a MySQL server, it correspond to the MYSQL structure of the C API, or the database handle of the PERL DBI/DBD interface. + * + * You have to start any work on a MySQL server by getting a working MCPConnection object. + * + * Most likely you will use this kind of code: + * + * + * MCPConnection *theConnec = [MCPConnection alloc]; + * MCPResult *theRes; + * + * theConnec = [theConnec initToHost:@"albert.com" withLogin:@"toto" password:@"albert" usingPort:0]; + * [theConnec selectDB:@"db1"]; + * theRes = [theConnec queryString:@"select * from table1"]; + * ... + * + * Failing to properly release your MCPConnection(s) object might cause a MySQL crash!!! (recovered if the server was started using mysqld_safe). + * + * Gets a proper Locale dictionary to use formater to parse strings from MySQL. + * For example strings representing dates should give a proper Locales for use with methods such as NSDate::dateWithNaturalLanguageString: locales: + */ ++ (NSDictionary *)getMySQLLocales +{ + NSMutableDictionary *theLocalDict = [NSMutableDictionary dictionaryWithCapacity:12]; + + [theLocalDict setObject:@"." forKey:@"NSDecimalSeparator"]; + + return [NSDictionary dictionaryWithDictionary:theLocalDict]; +} + +/** + * Gets a proper NSStringEncoding according to the given MySQL charset. + */ ++ (NSStringEncoding) encodingForMySQLEncoding:(const char *)mysqlEncoding +{ + // Unicode encodings: + if (!strncmp(mysqlEncoding, "utf8", 4)) { + return NSUTF8StringEncoding; + } + if (!strncmp(mysqlEncoding, "ucs2", 4)) { + return NSUnicodeStringEncoding; + } + + // Roman alphabet encodings: + if (!strncmp(mysqlEncoding, "ascii", 5)) { + return NSASCIIStringEncoding; + } + if (!strncmp(mysqlEncoding, "latin1", 6)) { + return NSISOLatin1StringEncoding; + } + if (!strncmp(mysqlEncoding, "macroman", 8)) { + return NSMacOSRomanStringEncoding; + } + + // Roman alphabet with central/east european additions: + if (!strncmp(mysqlEncoding, "latin2", 6)) { + return NSISOLatin2StringEncoding; + } + if (!strncmp(mysqlEncoding, "cp1250", 6)) { + return NSWindowsCP1250StringEncoding; + } + if (!strncmp(mysqlEncoding, "win1250", 7)) { + return NSWindowsCP1250StringEncoding; + } + if (!strncmp(mysqlEncoding, "cp1257", 6)) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingWindowsBalticRim); + } + + // Additions for Turkish: + if (!strncmp(mysqlEncoding, "latin5", 6)) { + return NSWindowsCP1254StringEncoding; + } + + // Greek: + if (!strncmp(mysqlEncoding, "greek", 5)) { + return NSWindowsCP1253StringEncoding; + } + + // Cyrillic: + if (!strncmp(mysqlEncoding, "win1251ukr", 6)) { + return NSWindowsCP1251StringEncoding; + } + if (!strncmp(mysqlEncoding, "cp1251", 6)) { + return NSWindowsCP1251StringEncoding; + } + if (!strncmp(mysqlEncoding, "koi8_ru", 6)) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingKOI8_R); + } + if (!strncmp(mysqlEncoding, "koi8_ukr", 6)) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingKOI8_R); + } + + // Arabic: + if (!strncmp(mysqlEncoding, "cp1256", 6)) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingWindowsArabic); + } + + // Hebrew: + if (!strncmp(mysqlEncoding, "hebrew", 6)) { + CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingISOLatinHebrew); + } + + // Asian: + if (!strncmp(mysqlEncoding, "ujis", 4)) { + return NSJapaneseEUCStringEncoding; + } + if (!strncmp(mysqlEncoding, "sjis", 4)) { + return NSShiftJISStringEncoding; + } + if (!strncmp(mysqlEncoding, "big5", 4)) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingBig5); + } + if (!strncmp(mysqlEncoding, "euc_kr", 6)) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingEUC_KR); + } + if (!strncmp(mysqlEncoding, "euckr", 5)) { + return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingEUC_KR); + } + + // Default to iso latin 1, even if it is not exact (throw an exception?) + NSLog(@"WARNING: unknown name for MySQL encoding '%s'!\n\t\tFalling back to iso-latin1.", mysqlEncoding); + + return NSISOLatin1StringEncoding; +} + +/** + * Returns the default charset of the library mysqlclient used. + */ ++ (NSStringEncoding)defaultMySQLEncoding +{ + return [MCPConnection encodingForMySQLEncoding:"utf8_general_ci"]; +} + +#pragma mark - +#pragma mark Class maintenance + +/** + * + */ ++ (void)setTruncateLongFieldInLogs:(BOOL)iTruncFlag +{ + sTruncateLongFieldInLogs = iTruncFlag; +} + +/** + * + */ ++ (BOOL)truncateLongField +{ + return sTruncateLongFieldInLogs; +} + +/** + * This method is to be used for getting special option for a connection, in which case the MCPConnection + * has to be inited with the init method, then option are selected, finally connection is done using one + * of the connect methods: + * + * MCPConnection *theConnect = [[MCPConnection alloc] init]; + * + * [theConnect setConnectionOption: option toValue: value]; + * [theConnect connectToHost:albert.com withLogin:@"toto" password:@"albert" port:0]; + * + */ +- (BOOL)setConnectionOption:(int)option toValue:(BOOL)value +{ + // So far do nothing except for testing if it's proper time for setting option + // What about if some option where setted and a connection is made again with connectTo... + if ((mConnected) || (! mConnection)) { + return FALSE; + } + + if (value) { //Set this option to true + mConnectionFlags |= option; + } + else { //Set this option to false + mConnectionFlags &= (! option); + } + + return YES; +} + +/** + * The method used by !{initToHost:withLogin:password:usingPort:} and !{initToSocket:withLogin:password:}. Same information and use of the parameters: + * + * - login is the user name + * - pass is the password corresponding to the user name + * - host is the hostname or IP adress + * - port is the TCP port to use to connect. If port = 0, uses the default port from mysql.h + * - socket is the path to the socket (for the localhost) + * + * The socket is used if the host is set to !{@"localhost"}, to an empty or a !{nil} string + * For the moment the implementation might not be safe if you have a nil pointer to one of the NSString* variables (underestand: I don't know what the result will be). + */ +- (BOOL)connectWithLogin:(NSString *)login password:(NSString *)pass host:(NSString *)host port:(int)port socket:(NSString *)socket +{ + const char *theLogin = [self cStringFromString:login]; + const char *theHost = [self cStringFromString:host]; + const char *thePass = [self cStringFromString:pass]; + const char *theSocket = [self cStringFromString:socket]; + void *theRet; + + if (mConnected) { + // Disconnect if it was already connected + mysql_close(mConnection); + mConnection = NULL; + mConnected = NO; + [self init]; + } + + if ([host isEqualToString:@""]) { + theHost = NULL; + } + + if (theSocket == NULL) { + theSocket = kMCPConnectionDefaultSocket; + } + + theRet = mysql_real_connect(mConnection, theHost, theLogin, thePass, NULL, port, theSocket, mConnectionFlags); + if (theRet != mConnection) { + return mConnected = NO; + } + + mConnected = YES; + mEncoding = [MCPConnection encodingForMySQLEncoding:mysql_character_set_name(mConnection)]; + + // Getting the timezone used by the server. + [self timeZone]; + + return mConnected; +} + +/** + * Selects a database to work with. + * + * The MCPConnection object needs to be properly inited and connected to a server. + * If a connection is not yet set or the selection of the database didn't work, returns NO. Returns YES in normal cases where the database is properly selected. + * + * So far, if dbName is a nil pointer it will return NO (as if it cannot connect), most likely this will throw an exception in the future. + */ +- (BOOL)selectDB:(NSString *) dbName +{ + if (!mConnected) return NO; + + [self stopKeepAliveTimer]; + + if (![self checkConnection]) { + return NO; + } + + if (dbName == nil) { + // Here we should throw an exception, impossible to select a databse if the string is indeed a nil pointer + return NO; + } + + if (mConnected) { + const char *theDBName = [self cStringFromString:dbName]; + [queryLock lock]; + if (0 == mysql_select_db(mConnection, theDBName)) { + [queryLock unlock]; + [self startKeepAliveTimerResettingState:YES]; + + return YES; + } + [queryLock unlock]; + } + + [self setLastErrorMessage:nil]; + + lastQueryErrorId = mysql_errno(mConnection); + + if (connectionProxy) { + [connectionProxy disconnect]; + } + + return NO; +} + +#pragma mark - +#pragma mark Error information + +/** + * Returns a string with the last MySQL error message on the connection. + */ +- (NSString *)getLastErrorMessage +{ + return lastQueryErrorMessage; +} + +/** + * Sets the string for the last MySQL error message on the connection, + * managing memory as appropriate. Supply a nil string to store the + * last error on the connection. + */ +- (void)setLastErrorMessage:(NSString *)theErrorMessage +{ + if (!theErrorMessage) theErrorMessage = [self stringWithCString:mysql_error(mConnection)]; + + if (lastQueryErrorMessage) [lastQueryErrorMessage release], lastQueryErrorMessage = nil; + lastQueryErrorMessage = [[NSString alloc] initWithString:theErrorMessage]; +} + +/** + * Returns the ErrorID of the last MySQL error on the connection. + */ +- (unsigned int)getLastErrorID +{ + return lastQueryErrorId; +} + +/** + * Determines whether a supplied error number can be classed as a connection error. + */ ++ (BOOL)isErrorNumberConnectionError:(int)theErrorNumber +{ + + switch (theErrorNumber) { + 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 Queries + +/** + * Takes a NSData object and transform it in a proper string for sending to the server in between quotes. + */ +- (NSString *)prepareBinaryData:(NSData *)theData +{ + const char *theCDataBuffer = [theData bytes]; + unsigned long theLength = [theData length]; + char *theCEscBuffer = (char *)calloc(sizeof(char),(theLength*2) + 1); + NSString *theReturn; +// unsigned long theEscapedLength; + +// Using the mysql_hex_string function : (NO other solution found to be able to support blobs while using UTF-8 charset). +// theEscapedLength = mysql_hex_string(theCEscBuffer, theCDataBuffer, theLength); + mysql_hex_string(theCEscBuffer, theCDataBuffer, theLength); + theReturn = [NSString stringWithFormat:@"%s", theCEscBuffer]; + free (theCEscBuffer); + return theReturn; +} + +/** + * Takes a string and escape any special character (like single quote : ') so that the string can be used directly in a query. + */ +- (NSString *)prepareString:(NSString *)theString +{ + NSData *theCData = [theString dataUsingEncoding:mEncoding allowLossyConversion:YES]; + unsigned long theLength = [theCData length]; + // const char *theCStringBuffer = [self cStringFromString:theString]; + // unsigned long theLength = [theString length]; + char *theCEscBuffer; + NSString *theReturn; + unsigned long theEscapedLength; + + if (theString == nil) { + // In the mean time, no one should call this method on a nil string, the test should be done before by the user of this method. + return @""; + } + + // theLength = strlen(theCStringBuffer); + theCEscBuffer = (char *)calloc(sizeof(char),(theLength * 2) + 1); + theEscapedLength = mysql_real_escape_string(mConnection, theCEscBuffer, [theCData bytes], theLength); + theReturn = [[NSString alloc] initWithData:[NSData dataWithBytes:theCEscBuffer length:theEscapedLength] encoding:mEncoding]; + // theReturn = [self stringWithCString:theCEscBuffer]; + free(theCEscBuffer); + + return theReturn; +} + +/** + * Use the class of the theObject to know how it should be prepared for usage with the database. + * If theObject is a string, this method will put single quotes to both its side and escape any necessary + * character using prepareString: method. If theObject is NSData, the prepareBinaryData: method will be + * used instead. + * + * For NSNumber object, the number is just quoted, for calendar dates, the calendar date is formatted in + * the preferred format for the database. + */ +- (NSString *)quoteObject:(id)theObject +{ + if ((! theObject) || ([theObject isNSNull])) { + return @"NULL"; + } + + if ([theObject isKindOfClass:[NSData class]]) { + return [NSString stringWithFormat:@"X'%@'", [self prepareBinaryData:(NSData *) theObject]]; + } + + if ([theObject isKindOfClass:[NSString class]]) { + return [NSString stringWithFormat:@"'%@'", [self prepareString:(NSString *) theObject]]; + } + + if ([theObject isKindOfClass:[NSNumber class]]) { + return [NSString stringWithFormat:@"%@", theObject]; + } + + if ([theObject isKindOfClass:[NSCalendarDate class]]) { + return [NSString stringWithFormat:@"'%@'", [(NSCalendarDate *)theObject descriptionWithCalendarFormat:@"%Y-%m-%d %H:%M:%S"]]; + } + + return [NSString stringWithFormat:@"'%@'", [self prepareString:[theObject description]]]; +} + +/** + * Takes a query string and return an MCPResult object holding the result of the query. + * The returned MCPResult is not retained, the client is responsible for that (it's autoreleased before being returned). If no field are present in the result (like in an insert query), will return nil (#{difference from previous version implementation}). Though, if their is at least one field the result will be non nil (even if no row are selected). + * + * Note that if you want to use this method with binary data (in the query), you should use !{prepareBinaryData:} to include the binary data in the query string. Also if you want to include in your query a string containing any special character (\, ', " ...) then you should use !{prepareString}. + */ +- (MCPResult *)queryString:(NSString *)query +{ + return [self queryString:query usingEncoding:mEncoding]; +} + +/** + * Error checks connection extensively - if this method fails due to a connection error, it will ask how to + * proceed and loop depending on the status, not returning control until either the query has been executed + * and the result can be returned or the connection and document have been closed. + */ +- (MCPResult *)queryString:(NSString *) query usingEncoding:(NSStringEncoding) encoding +{ + MCPResult *theResult = nil; + double queryStartTime, queryExecutionTime; + const char *theCQuery; + unsigned long theCQueryLength; + int queryResultCode; + int queryErrorId = 0; + my_ulonglong queryAffectedRows = 0; + int currentMaxAllowedPacket = -1; + BOOL isQueryRetry = NO; + NSString *queryErrorMessage = nil; + + // If no connection is present, return nil. + if (!mConnected) { + // Write a log entry + if ([delegate respondsToSelector:@selector(queryGaveError:connection:)]) [delegate queryGaveError:@"No connection available!" connection:self]; + // Notify that the query has been performed + [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self]; + // Show an error alert while resetting + NSBeginAlertSheet(NSLocalizedString(@"Error", @"error"), @"No connection available!", + nil, nil, [delegate valueForKeyPath:@"tableWindow"], self, nil, nil, nil, @"No connection available!"); + return nil; + } + + (void)(*stopKeepAliveTimerPtr)(self, stopKeepAliveTimerSEL); + + // Inform the delegate about the query if logging is enabled and delegate responds to willQueryString:connection: + if (delegateQueryLogging && delegateResponseToWillQueryString) + (void)(NSString*)(*willQueryStringPtr)(delegate, willQueryStringSEL, query); + + // If thirty seconds have elapsed since the last query, check the connection. This provides + // a balance between keeping high read/write timeouts for long queries, network issues, and + // minimising the impact of performing lots of additional checks. + if ([self timeConnected] - lastQueryExecutedAtTime > 30 + && ![self checkConnection]) return nil; + + // Derive the query string in the correct encoding + NSData *d = NSStringDataUsingLossyEncoding(query, encoding, 1); + theCQuery = [d bytes]; + // Set the length of the current query + theCQueryLength = [d length]; + + // Check query length against max_allowed_packet; if it is larger, the + // query would error, so if max_allowed_packet is editable for the user + // increase it for the current session and reconnect. + if (maxAllowedPacketSize < theCQueryLength) { + + if (isMaxAllowedPacketEditable) { + + currentMaxAllowedPacket = maxAllowedPacketSize; + [self setMaxAllowedPacketTo:strlen(theCQuery)+1024 resetSize:NO]; + [self reconnect]; + + } else { + + NSString *errorMessage = [NSString stringWithFormat:NSLocalizedString(@"The query length of %d bytes is larger than max_allowed_packet size (%d).", + @"error message if max_allowed_packet < query size"), + theCQueryLength, maxAllowedPacketSize]; + + // Write a log entry and update the connection error messages for those uses that check it + if ([delegate respondsToSelector:@selector(queryGaveError:connection:)]) [delegate queryGaveError:errorMessage connection:self]; + [self setLastErrorMessage:errorMessage]; + + // Notify that the query has been performed + [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self]; + // Show an error alert while resetting + NSBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), + nil, nil, [delegate valueForKeyPath:@"tableWindow"], self, nil, nil, nil, errorMessage); + + return nil; + } + } + + // In a loop to allow one reattempt, perform the query. + while (1) { + + // If this query has failed once already, check the connection + if (isQueryRetry) { + if (![self checkConnection]) { + + // Notify that the query has been performed + [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self]; + return nil; + } + } + + [queryLock lock]; + + // Run (or re-run) the query, timing the execution time of the query - note + // that this time will include network lag. + queryStartTime = [self timeConnected]; + queryResultCode = mysql_real_query(mConnection, theCQuery, theCQueryLength); + lastQueryExecutedAtTime = [self timeConnected]; + queryExecutionTime = lastQueryExecutedAtTime - queryStartTime; + + // On success, capture the results + if (0 == queryResultCode) { + + if (mysql_field_count(mConnection) != 0) { + theResult = [[MCPResult alloc] initWithMySQLPtr:mConnection encoding:mEncoding timeZone:mTimeZone]; + + [queryLock unlock]; + + // Ensure no problem occurred during the result fetch + if (mysql_errno(mConnection) != 0) { + queryErrorMessage = [[NSString alloc] initWithString:[self stringWithCString:mysql_error(mConnection)]]; + queryErrorId = mysql_errno(mConnection); + break; + } + } else { + [queryLock unlock]; + } + + queryErrorMessage = [[NSString alloc] initWithString:@""]; + queryErrorId = 0; + queryAffectedRows = mysql_affected_rows(mConnection); + + // On failure, set the error messages and IDs + } else { + [queryLock unlock]; + + queryErrorMessage = [[NSString alloc] initWithString:[self stringWithCString:mysql_error(mConnection)]]; + queryErrorId = mysql_errno(mConnection); + + // If the error was a connection error, retry once + if (!isQueryRetry && retryAllowed && [MCPConnection isErrorNumberConnectionError:queryErrorId]) { + isQueryRetry = YES; + continue; + } + } + + break; + } + + // If the mysql thread id has changed as a result of a connection error, + // ensure connection details are still correct + if (connectionThreadId != mConnection->thread_id) [self restoreConnectionDetails]; + + // If max_allowed_packet was changed, reset it to default + if(currentMaxAllowedPacket > -1) + [self setMaxAllowedPacketTo:currentMaxAllowedPacket resetSize:YES]; + + // Update error strings and IDs + lastQueryErrorId = queryErrorId; + [self setLastErrorMessage:queryErrorMessage?queryErrorMessage:@""]; + if (queryErrorMessage) [queryErrorMessage release]; + lastQueryAffectedRows = queryAffectedRows; + lastQueryExecutionTime = queryExecutionTime; + + // If an error occurred, inform the delegate + if (queryResultCode & delegateResponseToWillQueryString) + [delegate queryGaveError:lastQueryErrorMessage connection:self]; + + (void)(*startKeepAliveTimerResettingStatePtr)(self, startKeepAliveTimerResettingStateSEL, YES); + + if (!theResult) return nil; + return [theResult autorelease]; +} + +/** + * Return the time taken to execute the last query. This should be close to the time it took + * the server to run the query, but will include network lag and some client library overhead. + */ +- (double)lastQueryExecutionTime +{ + return lastQueryExecutionTime; +} + +/** + * Returns the number of affected rows by the last query. + */ +- (my_ulonglong)affectedRows +{ + if (mConnected) { + return mysql_affected_rows(mConnection); + } + + return 0; +} + +/** + * If the last query was an insert in a table having a autoindex column, returns the ID + * (autoindexed field) of the last row inserted. + */ +- (my_ulonglong)insertId +{ + if (mConnected) { + return mysql_insert_id(mConnection); + } + + return 0; +} + +#pragma mark - +#pragma mark Database structure + +/** + * Just a fast wrapper for the more complex !{listDBsWithPattern:} method. + */ +- (MCPResult *)listDBs +{ + return [self listDBsLike:nil]; +} + +/** + * Returns a list of database which name correspond to the SQL regular expression in 'pattern'. + * The comparison is done with wild card extension : % and _. + * The result should correspond to the queryString:@"SHOW databases [LIKE wild]"; but implemented with mysql_list_dbs. + * If an empty string or nil is passed as pattern, all databases will be shown. + */ +- (MCPResult *)listDBsLike:(NSString *)dbsName +{ + if (!mConnected) return NO; + + MCPResult *theResult = [MCPResult alloc]; + MYSQL_RES *theResPtr; + + [self stopKeepAliveTimer]; + + if (![self checkConnection]) return [[[MCPResult alloc] init] autorelease]; + + [self startKeepAliveTimerResettingState:YES]; + + [queryLock lock]; + if ((dbsName == nil) || ([dbsName isEqualToString:@""])) { + if (theResPtr = mysql_list_dbs(mConnection, NULL)) { + [theResult initWithResPtr: theResPtr encoding: mEncoding timeZone:mTimeZone]; + } + else { + [theResult init]; + } + } + else { + const char *theCDBsName = (const char *)[self cStringFromString:dbsName]; + + if (theResPtr = mysql_list_dbs(mConnection, theCDBsName)) { + [theResult initWithResPtr: theResPtr encoding: mEncoding timeZone:mTimeZone]; + } + else { + [theResult init]; + } + } + [queryLock unlock]; + + if (theResult) { + [theResult autorelease]; + } + + return theResult; +} + +/** + * Make sure a DB is selected (with !{selectDB:} method) first. + */ +- (MCPResult *)listTables +{ + return [self listTablesLike:nil]; +} + +/** + * From within a database, give back the list of table which name correspond to tablesName + * (with wild card %, _ extension). Correspond to queryString:@"SHOW tables [LIKE wild]"; uses mysql_list_tables function. + * + * If an empty string or nil is passed as tablesName, all tables will be shown. + * + * WARNING: #{produce an error if no databases are selected} (with !{selectDB:} for example). + */ +- (MCPResult *)listTablesLike:(NSString *)tablesName +{ + if (!mConnected) return NO; + + MCPResult *theResult = [MCPResult alloc]; + MYSQL_RES *theResPtr; + + [self stopKeepAliveTimer]; + + if (![self checkConnection]) return [[[MCPResult alloc] init] autorelease]; + + [self startKeepAliveTimerResettingState:YES]; + + [queryLock lock]; + if ((tablesName == nil) || ([tablesName isEqualToString:@""])) { + if (theResPtr = mysql_list_tables(mConnection, NULL)) { + [theResult initWithResPtr: theResPtr encoding: mEncoding timeZone:mTimeZone]; + } + else { + [theResult init]; + } + } + else { + const char *theCTablesName = (const char *)[self cStringFromString:tablesName]; + if (theResPtr = mysql_list_tables(mConnection, theCTablesName)) { + [theResult initWithResPtr: theResPtr encoding: mEncoding timeZone:mTimeZone]; + } + else { + [theResult init]; + } + } + [queryLock unlock]; + + if (theResult) { + [theResult autorelease]; + } + return theResult; +} + +/** + * List tables in DB specified by dbName and corresponding to pattern. + * This method indeed issues a !{SHOW TABLES FROM dbName LIKE ...} query to the server. + * This is done this way to make sure the selected DB is not changed by this method. + */ +- (MCPResult *)listTablesFromDB:(NSString *)dbName like:(NSString *)tablesName +{ + MCPResult *theResult; + + if ((tablesName == nil) || ([tablesName isEqualToString:@""])) { + NSString *theQuery = [NSString stringWithFormat:@"SHOW TABLES FROM %@", dbName]; + theResult = [self queryString:theQuery]; + } + else { + NSString *theQuery = [NSString stringWithFormat:@"SHOW TABLES FROM %@ LIKE '%@'", dbName, tablesName]; + theResult = [self queryString:theQuery]; + } + + return theResult; +} + +/** + * Just a fast wrapper for the more complex list !{listFieldsWithPattern:forTable:} method. + */ +- (MCPResult *)listFieldsFromTable:(NSString *)tableName +{ + return [self listFieldsFromTable:tableName like:nil]; +} + +/** + * Show all the fields of the table tableName which name correspond to pattern (with wild card expansion : %,_). + * Indeed, and as recommanded from mysql reference, this method is NOT using mysql_list_fields but the !{queryString:} method. + * If an empty string or nil is passed as fieldsName, all fields (of tableName) will be returned. + */ +- (MCPResult *)listFieldsFromTable:(NSString *)tableName like:(NSString *)fieldsName +{ + MCPResult *theResult; + + if ((fieldsName == nil) || ([fieldsName isEqualToString:@""])) { + NSString *theQuery = [NSString stringWithFormat:@"SHOW COLUMNS FROM %@", tableName]; + theResult = [self queryString:theQuery]; + } + else { + NSString *theQuery = [NSString stringWithFormat:@"SHOW COLUMNS FROM %@ LIKE '%@'", tableName, fieldsName]; + theResult = [self queryString:theQuery]; + } + + return theResult; +} + +#pragma mark - +#pragma mark Server information + +/** + * Returns a string giving the client library version. + */ +- (NSString *)clientInfo +{ + return [self stringWithCString:mysql_get_client_info()]; +} + +/** + * Returns a string giving information on the host of the DB server. + */ +- (NSString *)hostInfo +{ + return [self stringWithCString:mysql_get_host_info(mConnection)]; +} + +/** + * Returns a string giving the server version. + */ +- (NSString *)serverInfo +{ + if (mConnected) { + return [self stringWithCString: mysql_get_server_info(mConnection)]; + } + + return @""; +} + +/** + * Returns the number of the protocole used to transfer info from server to client + */ +- (NSNumber *)protoInfo +{ + return [MCPNumber numberWithUnsignedInt:mysql_get_proto_info(mConnection)]; +} + +/** + * Lists active process + */ +- (MCPResult *)listProcesses +{ + MCPResult *theResult = [MCPResult alloc]; + MYSQL_RES *theResPtr; + + [queryLock lock]; + if (theResPtr = mysql_list_processes(mConnection)) { + [theResult initWithResPtr:theResPtr encoding:mEncoding timeZone:mTimeZone]; + } + else { + [theResult init]; + } + [queryLock unlock]; + + if (theResult) { + [theResult autorelease]; + } + + return theResult; +} + +/** + * Kills the process with the given pid. + * The users needs the !{Process_priv} privilege. + */ +- (BOOL)killProcess:(unsigned long)pid +{ + int theErrorCode = mysql_kill(mConnection, pid); + + return (theErrorCode) ? NO : YES; +} + +/* + * Check some common locations for the presence of a MySQL socket file, returning + * it if successful. + */ +- (NSString *)findSocketPath +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + + NSArray *possibleSocketLocations = [NSArray arrayWithObjects: + @"/tmp/mysql.sock", // Default + @"/var/run/mysqld/mysqld.sock", // As used on Debian/Gentoo + @"/var/tmp/mysql.sock", // As used on FreeBSD + @"/var/lib/mysql/mysql.sock", // As used by Fedora + @"/opt/local/lib/mysql/mysql.sock", // Alternate fedora + @"/opt/local/var/run/mysqld/mysqld.sock", // Darwinports MySQL + @"/opt/local/var/run/mysql4/mysqld.sock", // Darwinports MySQL 4 + @"/opt/local/var/run/mysql5/mysqld.sock", // Darwinports MySQL 5 + @"/Applications/MAMP/tmp/mysql/mysql.sock", // MAMP default location + nil]; + + for (int i = 0; i < [possibleSocketLocations count]; i++) + { + if ([fileManager fileExistsAtPath:[possibleSocketLocations objectAtIndex:i]]) + return [possibleSocketLocations objectAtIndex:i]; + } + + return nil; +} + +#pragma mark - +#pragma mark Encoding + +/** + * Sets the encoding used by the server for data transfer. + * Used to make sure the output of the query result is ok even for non-ascii characters + * The character set (encoding) used by the db is passed to the MCPConnection object upon connection, + * so most likely the encoding (from -encoding) method is already the proper one. + * That is to say : It's unlikely you will need to call this method directly, and #{if ever you use it, do it at your own risks}. + */ +- (void)setEncoding:(NSStringEncoding)theEncoding +{ + mEncoding = theEncoding; +} + +/** + * Gets the encoding for the connection + */ +- (NSStringEncoding)encoding +{ + return mEncoding; +} + +#pragma mark - +#pragma mark Time Zone + +/** + * Setting the time zone to be used with the server. + */ +- (void)setTimeZone:(NSTimeZone *)iTimeZone +{ + if (iTimeZone != mTimeZone) { + [mTimeZone release]; + mTimeZone = [iTimeZone retain]; + } + + if ([self checkConnection]) { + if (mTimeZone) { + [self queryString:[NSString stringWithFormat:@"SET time_zone = '%@'", [mTimeZone name]]]; + } + else { + [self queryString:@"SET time_zone = 'SYSTEM'"]; + } + } +} + +/** + * Getting the currently used time zone (in communication with the DB server). + */ +- (NSTimeZone *)timeZone +{ + if ([self checkConnection]) { + MCPResult *theSessionTZ = [self queryString:@"SHOW VARIABLES LIKE '%time_zone'"]; + NSArray *theRow; + id theTZName; + NSTimeZone *theTZ; + + [theSessionTZ dataSeek:1ULL]; + theRow = [theSessionTZ fetchRowAsArray]; + theTZName = [theRow objectAtIndex:1]; + + if ( [theTZName isKindOfClass:[NSData class]] ) { + // MySQL 4.1.14 returns the mysql variables as NSData + theTZName = [self stringWithText:theTZName]; + } + + if ([theTZName isEqualToString:@"SYSTEM"]) { + [theSessionTZ dataSeek:0ULL]; + theRow = [theSessionTZ fetchRowAsArray]; + theTZName = [theRow objectAtIndex:1]; + + if ( [theTZName isKindOfClass:[NSData class]] ) { + // MySQL 4.1.14 returns the mysql variables as NSData + theTZName = [self stringWithText:theTZName]; + } + } + + if (theTZName) { // Old versions of the server does not support there own time zone ? + theTZ = [NSTimeZone timeZoneWithName:theTZName]; + } else { + // By default set the time zone to the local one.. + // Try to get the name using the previously available variable: + theSessionTZ = [self queryString:@"SHOW VARIABLES LIKE 'timezone'"]; + [theSessionTZ dataSeek:0ULL]; + theRow = [theSessionTZ fetchRowAsArray]; + theTZName = [theRow objectAtIndex:1]; + if (theTZName) { + // Finally we found one ... + theTZ = [NSTimeZone timeZoneWithName:theTZName]; + } else { + theTZ = [NSTimeZone defaultTimeZone]; + //theTZ = [NSTimeZone systemTimeZone]; + NSLog(@"The time zone is not defined on the server, set it to the default one : %@", theTZ); + } + } + + if (theTZ != mTimeZone) { + [mTimeZone release]; + mTimeZone = [theTZ retain]; + } + } + + return mTimeZone; +} + +#pragma mark - +#pragma mark Packet size + +/** + * Retrieve the max_allowed_packet size from the server; returns + * false if the query fails. + */ +- (BOOL)fetchMaxAllowedPacket +{ + char *queryString; + + if ([self serverMajorVersion] == 3) queryString = "SHOW VARIABLES LIKE 'max_allowed_packet'"; + else queryString = "SELECT @@global.max_allowed_packet"; + + [queryLock lock]; + if (0 == mysql_query(mConnection, queryString)) { + if (mysql_field_count(mConnection) != 0) { + MCPResult *r = [[MCPResult alloc] initWithMySQLPtr:mConnection encoding:mEncoding timeZone:mTimeZone]; + NSArray *a = [r fetchRowAsArray]; + [r autorelease]; + if([a count]) { + maxAllowedPacketSize = [[a objectAtIndex:([self serverMajorVersion] == 3)?1:0] intValue]; + [queryLock unlock]; + return true; + } + } + } + [queryLock unlock]; + + return false; +} + +/** + * Retrieves max_allowed_packet size set as global variable. + * It returns -1 if it fails. + */ +- (int)getMaxAllowedPacket +{ + MCPResult *r; + r = [self queryString:@"SELECT @@global.max_allowed_packet" usingEncoding:mEncoding]; + if (![[self getLastErrorMessage] isEqualToString:@""]) { + if ([self isConnected]) + NSRunAlertPanel(@"Error", [NSString stringWithFormat:@"An error occured while retrieving max_allowed_packet size:\n\n%@", [self getLastErrorMessage]], @"OK", nil, nil); + return -1; + } + NSArray *a = [r fetchRowAsArray]; + if([a count]) + return [[a objectAtIndex:0] intValue]; + + return -1; +} + +/* + * It sets max_allowed_packet size to newSize and it returns + * max_allowed_packet after setting it to newSize for cross-checking + * if the maximal size was reached (e.g. set it to 4GB it'll return 1GB up to now). + * If something failed it return -1; + */ +- (int)setMaxAllowedPacketTo:(int)newSize resetSize:(BOOL)reset +{ + if(![self isMaxAllowedPacketEditable] || newSize < 1024) return maxAllowedPacketSize; + + [queryLock lock]; + mysql_query(mConnection, [[NSString stringWithFormat:@"SET GLOBAL max_allowed_packet = %d", newSize] UTF8String]); + [queryLock unlock]; + + // Inform the user via a log entry about that change according to reset value + if(delegate && [delegate respondsToSelector:@selector(queryGaveError:connection:)]) + if(reset) + [delegate queryGaveError:[NSString stringWithFormat:@"max_allowed_packet was reset to %d for new session", newSize] connection:self]; + else + [delegate queryGaveError:[NSString stringWithFormat:@"Query too large; max_allowed_packet temporarily set to %d for the current session to allow query to succeed", newSize] connection:self]; + + return maxAllowedPacketSize; +} + +/** + * It returns whether max_allowed_packet is setable for the user. + */ +- (BOOL)isMaxAllowedPacketEditable +{ + BOOL isEditable; + + [queryLock lock]; + isEditable = !mysql_query(mConnection, "SET GLOBAL max_allowed_packet = @@global.max_allowed_packet"); + [queryLock unlock]; + + return isEditable; +} + +#pragma mark - +#pragma mark Data conversion + +/** + * For internal use only. Transforms a NSString to a C type string (ending with \0) using the character set from the MCPConnection. + * Lossy conversions are enabled. + */ +- (const char *)cStringFromString:(NSString *)theString +{ + NSMutableData *theData; + + if (! theString) { + return (const char *)NULL; + } + + theData = [NSMutableData dataWithData:[theString dataUsingEncoding:mEncoding allowLossyConversion:YES]]; + [theData increaseLengthBy:1]; + + return (const char *)[theData bytes]; +} + +/** + * Modified version of the original to support a supplied encoding. + * For internal use only. Transforms a NSString to a C type string (ending with \0). + * Lossy conversions are enabled. + */ +- (const char *)cStringFromString:(NSString *)theString usingEncoding:(NSStringEncoding)encoding +{ + NSMutableData *theData; + + if (! theString) { + return (const char *)NULL; + } + + theData = [NSMutableData dataWithData:[theString dataUsingEncoding:encoding allowLossyConversion:YES]]; + [theData increaseLengthBy:1]; + + return (const char *)[theData bytes]; +} + +/** + * Returns a NSString from a C style string encoded with the character set of theMCPConnection. + */ +- (NSString *)stringWithCString:(const char *)theCString +{ + NSData *theData; + NSString *theString; + + if (theCString == NULL) { + return @""; + } + + theData = [NSData dataWithBytes:theCString length:(strlen(theCString))]; + theString = [[NSString alloc] initWithData:theData encoding:mEncoding]; + + if (theString) { + [theString autorelease]; + } + + return theString; +} + +/** + * Use the string encoding to convert the returned NSData to a string (for a Text field). + */ +- (NSString *)stringWithText:(NSData *)theTextData +{ + NSString *theString; + + if (theTextData == nil) { + return nil; + } + + theString = [[NSString alloc] initWithData:theTextData encoding:mEncoding]; + + if (theString) { + [theString autorelease]; + } + + return theString; +} + +#pragma mark - + +/** + * Object deallocation. + */ +- (void) dealloc +{ + delegate = nil; + + if (lastQueryErrorMessage) [lastQueryErrorMessage release]; + if (connectionHost) [connectionHost release]; + if (connectionLogin) [connectionLogin release]; + if (connectionSocket) [connectionSocket release]; + if (connectionPassword) [connectionPassword release]; + if (lastKeepAliveSuccess) [lastKeepAliveSuccess release]; + [queryLock release]; + + [super dealloc]; +} + +@end + +@implementation MCPConnection (PrivateAPI) + +/** + * Get the server's version string + */ +- (void)_getServerVersionString +{ + if (mConnected) { + MCPResult *theResult = [self queryString:@"SHOW VARIABLES WHERE Variable_name = 'version'"]; + + if ([theResult numOfRows]) { + [theResult dataSeek:0]; + serverVersionString = [[NSString stringWithString:[[theResult fetchRowAsArray] objectAtIndex:1]] retain]; + } + } +} + +@end diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPConnectionProxy.h b/Frameworks/MCPKit/MCPFoundationKit/MCPConnectionProxy.h new file mode 100644 index 00000000..6d643bfa --- /dev/null +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPConnectionProxy.h @@ -0,0 +1,70 @@ +// +// $Id: MCPConnectionProxy.h 1052 2009-07-17 17:49:07Z stuart02 $ +// +// MCPConnectionProxy.h +// MCPKit +// +// Created by Stuart Connolly (stuconnolly.com) on July 2, 2009. +// Copyright (c) 2009 Stuart Connolly. All rights reserved. +// +// Forked by the Sequel Pro team (sequelpro.com), April 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> + +/** + * Connection proxy state constants. + */ +enum PROXY_TUNNEL_STATES +{ + PROXY_STATE_IDLE = 0, + PROXY_STATE_CONNECTING = 1, + PROXY_STATE_WAITING_FOR_AUTH = 2, + PROXY_STATE_CONNECTED = 3, + PROXY_STATE_FORWARDING_FAILED = 4 +}; + +@protocol MCPConnectionProxy <NSObject> + +/** + * Connect the proxy. + */ +- (void)connect; + +/** + * Disconnect the proxy. + */ +- (void)disconnect; + +/** + * Get the current state of the proxy. + */ +- (int)state; + +/** + * Get the local port being used by the proxy. + */ +- (int)localPort; + +/** + * Sets the method the proxy should call whenever the state of the connection changes. + */ +- (BOOL)setConnectionStateChangeSelector:(SEL)theStateChangeSelector delegate:(id)theDelegate; + +@end diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPConstants.h b/Frameworks/MCPKit/MCPFoundationKit/MCPConstants.h new file mode 100644 index 00000000..284623cd --- /dev/null +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPConstants.h @@ -0,0 +1,64 @@ +// +// $Id: MCPConstants.h 1036 2009-07-17 00:29:44Z stuart02 $ +// +// MCPConstants.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 03/06/2001. +// Copyright (c) 2001 Serge Cohen. All rights reserved. +// +// Forked by the Sequel Pro team (sequelpro.com), April 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +// Result type constants +typedef enum { + MCPTypeArray = 1, + MCPTypeDictionary = 2, + MCPTypeFlippedArray = 3, + MCPTypeFlippedDictionary = 4 +} MCPReturnType; + +// Connection check constants +typedef enum { + MCPConnectionCheckRetry = 0, + MCPConnectionCheckReconnect = 1, + MCPConnectionCheckDisconnect = 2 +} MCPConnectionCheck; + +// Charcater set mapping constants +typedef struct _OUR_CHARSET +{ + unsigned int nr; + const char *name; + const char *collation; + unsigned int char_minlen; + unsigned int char_maxlen; +} OUR_CHARSET; + +// Deafult connection option +extern const unsigned int kMCPConnectionDefaultOption; + +// Default socket (from the mysql.h used at compile time) +extern const char *kMCPConnectionDefaultSocket; + +// Added to MySQL error code +extern const unsigned int kMCPConnectionNotInited; + +// The length of the truncation if required +extern const unsigned int kLengthOfTruncationForLog; diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPFastQueries.h b/Frameworks/MCPKit/MCPFoundationKit/MCPFastQueries.h new file mode 100644 index 00000000..144d8e79 --- /dev/null +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPFastQueries.h @@ -0,0 +1,46 @@ +// +// $Id: MCPFastQueries.h 1056 2009-07-18 10:42:29Z stuart02 $ +// +// MCPFastQueries.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 03/06/2002. +// Copyright (c) 2001 Serge Cohen. All rights reserved. +// +// Forked by the Sequel Pro team (sequelpro.com), April 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> + +#import "MCPConnection.h" + +@interface MCPConnection (MCPFastQueries) + +// For insert queries, get directly the Id of the newly inserted row. +- (my_ulonglong)insertQuery:(NSString *)query; +- (my_ulonglong)updateQuery:(NSString *)query; + +// Returns directly a proper NS object, or a collection (NSArray, NSDictionary...). +- (id)getFirstFieldFromQuery:(NSString *)query; +- (id)getFirstRowFromQuery:(NSString *)query asType:(MCPReturnType)type; +- (id)getAllRowsFromQuery:(NSString *)query asType:(MCPReturnType)type; +- (NSArray *)getQuery:(NSString *)query colWithIndex:(unsigned int)col; +- (NSArray *)getQuery:(NSString *)query colWithName:(NSString *)colName; + +@end diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPFastQueries.m b/Frameworks/MCPKit/MCPFoundationKit/MCPFastQueries.m new file mode 100644 index 00000000..951d29af --- /dev/null +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPFastQueries.m @@ -0,0 +1,113 @@ +// +// $Id: MCPFastQueries.m 1056 2009-07-18 10:42:29Z stuart02 $ +// +// MCPFastQueries.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 03/06/2002. +// Copyright (c) 2001 Serge Cohen. All rights reserved. +// +// Forked by the Sequel Pro team (sequelpro.com), April 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import "MCPFastQueries.h" +#import "MCPResultPlus.h" + +/** + * This actegory is made up to keep the extra methods out or the core of the framework. + * + * Basicly this is the place to add methods which are useful, but are just wrappers to the methods of the + * core (MCPConnection, MCPResult). The purpous being to have a single line call available for current tasks + * which otherwise would need a couple of lines and object defined. + */ +@implementation MCPConnection (MCPFastQueries) + +/** + * Send the query aQuery to the server and retrieve the row id if the table have a autoincrement column. + * Returns 0 if nothing have been inserted. + */ +- (my_ulonglong)insertQuery:(NSString *)query +{ + [self queryString:query]; + + return [self insertId]; +} + +/** + * Send the query aQuery to the server and retrieve the number of affected rows (should work with !{update}, + * !{delete}, !{insert} and !{select} type of queries). + * + * NB: This can also be used with a !{select} query if you are only interested in the number of row complying + * with the query; you'll get no chance to get the result from the query, except by sending the query + * again (with !{queryString:}) + */ +- (my_ulonglong)updateQuery:(NSString *)query +{ + [self queryString:query]; + + return [self affectedRows]; +} + +/** + * Get the first field of the first row of the result from the query (aQuery). Should return nil if no object + * at all are selected. + */ +- (id)getFirstFieldFromQuery:(NSString *)query +{ + return [[[self queryString:query] fetchRowAsType:MCPTypeArray] objectAtIndex:0]; +} + +/** + * Get the firdst row of the result from the query aQuery, in a collection of type determined by aType + * (MCPTypeArray or MCPTypeDictionary) + */ +- (id) getFirstRowFromQuery:(NSString *)query asType:(MCPReturnType)type +{ + return [[self queryString:query] fetchRowAsType:type]; +} + +/** + * Get a bidimensional table of the whole rows of the result from the query aQuery. The type of the result is + * choosen by aType, it can be (MCPTypeArray, MCPTypeDictionary, MCPTypeFlippedArray & MCPTypeFlippedDictionary). + * Description of the types can be found in method !{fetch2DResultAsType:}. + */ +- (id)getAllRowsFromQuery:(NSString *)query asType:(MCPReturnType)type + +{ + return [[self queryString:query] fetch2DResultAsType:type]; +} + +/** + * Get a column (as an NSArray) of the result from the query aQuery. The column is choosen from it's index, + * starting from 0. + */ +- (NSArray *)getQuery:(NSString *)query colWithIndex:(unsigned int)col +{ + return [[self queryString:query] fetchColAtIndex:col]; +} + +/** + * Get a column (as an NSArray) of the result from the query aQuery. The column is choosen from it's name. + */ +- (NSArray *)getQuery:(NSString *)query colWithName:(NSString *)colName +{ + return [[self queryString:query] fetchColWithName:colName]; +} + +@end diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPKit.h b/Frameworks/MCPKit/MCPFoundationKit/MCPKit.h new file mode 100644 index 00000000..34df95f7 --- /dev/null +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPKit.h @@ -0,0 +1,40 @@ +// +// $Id: MCPKit.h 1058 2009-07-18 14:53:57Z stuart02 $ +// +// MCPKit.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 08/12/2001. +// Copyright (c) 2001 Serge Cohen. All rights reserved. +// +// Forked by the Sequel Pro team (sequelpro.com), April 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> + +#import <MCPKit/MCPConstants.h> +#import <MCPKit/MCPNull.h> +#import <MCPKit/MCPResult.h> +#import <MCPKit/MCPConnection.h> +#import <MCPKit/MCPNumber.h> +#import <MCPKit/MCPResultPlus.h> +#import <MCPKit/MCPFastQueries.h> +#import <MCPKit/MCPConnectionProxy.h> + +#import "mysql.h" diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPNull.h b/Frameworks/MCPKit/MCPFoundationKit/MCPNull.h new file mode 100644 index 00000000..c017d447 --- /dev/null +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPNull.h @@ -0,0 +1,35 @@ +// +// $Id: MCPNull.h 482 2009-04-05 01:38:48Z stuart02 $ +// +// MCPNull.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 02/06/2002. +// Copyright (c) 2001 Serge Cohen. All rights reserved. +// +// Forked by the Sequel Pro team (sequelpro.com), April 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> + +@interface NSObject (MCPNSNullTest) + +- (BOOL)isNSNull; + +@end diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPNull.m b/Frameworks/MCPKit/MCPFoundationKit/MCPNull.m new file mode 100644 index 00000000..d3bf2ba6 --- /dev/null +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPNull.m @@ -0,0 +1,41 @@ +// +// $Id: MCPNull.m 482 2009-04-05 01:38:48Z stuart02 $ +// +// MCPNull.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 02/06/2002. +// Copyright (c) 2001 Serge Cohen. All rights reserved. +// +// Forked by the Sequel Pro team (sequelpro.com), April 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import "MCPNull.h" + +@implementation NSObject (MCPNSNullTest) + +/** + * This Category is meant to make any kind of object the possible target to the test (isNSNull). + */ +- (BOOL) isNSNull +{ + return [self isMemberOfClass:[NSNull class]]; +} + +@end diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPNumber.h b/Frameworks/MCPKit/MCPFoundationKit/MCPNumber.h new file mode 100644 index 00000000..f959d43b --- /dev/null +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPNumber.h @@ -0,0 +1,83 @@ +// +// $Id: MCPNumber.h 1033 2009-07-16 23:09:12Z stuart02 $ +// +// MCPNumber.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 08/12/2002. +// Copyright (c) 2001 Serge Cohen. All rights reserved. +// +// Forked by the Sequel Pro team (sequelpro.com), April 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> + +@interface MCPNumber : NSNumber +{ + const char *typeCode; + NSNumber *number; +} + ++ (MCPNumber *)numberWithChar:(char)value; ++ (MCPNumber *)numberWithUnsignedChar:(unsigned char)value; ++ (MCPNumber *)numberWithShort:(short)value; ++ (MCPNumber *)numberWithUnsignedShort:(unsigned short)value; ++ (MCPNumber *)numberWithInt:(int)value; ++ (MCPNumber *)numberWithUnsignedInt:(unsigned int)value; ++ (MCPNumber *)numberWithLong:(long)value; ++ (MCPNumber *)numberWithUnsignedLong:(unsigned long)value; ++ (MCPNumber *)numberWithLongLong:(long long)value; ++ (MCPNumber *)numberWithUnsignedLongLong:(unsigned long long)value; ++ (MCPNumber *)numberWithFloat:(float)value; ++ (MCPNumber *)numberWithDouble:(double)value; ++ (MCPNumber *)numberWithBool:(BOOL)value; + +- (id)initWithChar:(char)value; +- (id)initWithUnsignedChar:(unsigned char)value; +- (id)initWithShort:(short)value; +- (id)initWithUnsignedShort:(unsigned short)value; +- (id)initWithInt:(int)value; +- (id)initWithUnsignedInt:(unsigned int)value; +- (id)initWithLong:(long)value; +- (id)initWithUnsignedLong:(unsigned long)value; +- (id)initWithLongLong:(long long)value; +- (id)initWithUnsignedLongLong:(unsigned long long)value; +- (id)initWithFloat:(float)value; +- (id)initWithDouble:(double)value; +- (id)initWithBool:(BOOL)value; + +// Important NSNumber primitive methods +- (const char *)objCType; +- (void)getValue:(void *)buffer; + +- (char)charValue; +- (unsigned char)unsignedCharValue; +- (short)shortValue; +- (unsigned short)unsignedShortValue; +- (int)intValue; +- (unsigned int)unsignedIntValue; +- (long)longValue; +- (unsigned long)unsignedLongValue; +- (long long)longLongValue; +- (unsigned long long)unsignedLongLongValue; +- (float)floatValue; +- (double)doubleValue; +- (BOOL)boolValue; + +@end diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPNumber.m b/Frameworks/MCPKit/MCPFoundationKit/MCPNumber.m new file mode 100644 index 00000000..0f422431 --- /dev/null +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPNumber.m @@ -0,0 +1,301 @@ +// +// $Id: MCPNumber.m 1033 2009-07-16 23:09:12Z stuart02 $ +// +// MCPNumber.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 08/12/2002. +// Copyright (c) 2001 Serge Cohen. All rights reserved. +// +// Forked by the Sequel Pro team (sequelpro.com), April 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import "MCPNumber.h" + +@implementation MCPNumber + +#pragma mark - +#pragma mark 'Creator' class methods + ++ (MCPNumber *)numberWithChar:(char)value +{ + return [[[MCPNumber alloc] initWithChar:value] autorelease]; +} + ++ (MCPNumber *)numberWithUnsignedChar:(unsigned char)value +{ + return [[[MCPNumber alloc] initWithUnsignedChar:value] autorelease]; +} + ++ (MCPNumber *)numberWithShort:(short)value +{ + return [[[MCPNumber alloc] initWithShort:value] autorelease]; +} + ++ (MCPNumber *)numberWithUnsignedShort:(unsigned short)value +{ + return [[[MCPNumber alloc] initWithUnsignedShort:value] autorelease]; +} + ++ (MCPNumber *)numberWithInt:(int)value +{ + return [[[MCPNumber alloc] initWithInt:value] autorelease]; +} + ++ (MCPNumber *)numberWithUnsignedInt:(unsigned int)value +{ + return [[[MCPNumber alloc] initWithUnsignedInt:value] autorelease]; +} + ++ (MCPNumber *)numberWithLong:(long)value +{ + return [[[MCPNumber alloc] initWithLong:value] autorelease]; +} + ++ (MCPNumber *)numberWithUnsignedLong:(unsigned long)value +{ + return [[[MCPNumber alloc] initWithUnsignedLong:value] autorelease]; +} + ++ (MCPNumber *)numberWithLongLong:(long long)value +{ + return [[[MCPNumber alloc] initWithLongLong:value] autorelease]; +} + ++ (MCPNumber *)numberWithUnsignedLongLong:(unsigned long long)value +{ + return [[[MCPNumber alloc] initWithUnsignedLongLong:value] autorelease]; +} + ++ (MCPNumber *)numberWithFloat:(float)value +{ + return [[[MCPNumber alloc] initWithFloat:value] autorelease]; +} + ++ (MCPNumber *)numberWithDouble:(double)value +{ + return [[[MCPNumber alloc] initWithDouble:value] autorelease]; +} + ++ (MCPNumber *)numberWithBool:(BOOL)value +{ + return [[[MCPNumber alloc] initWithBool:value] autorelease]; +} + +#pragma mark - +#pragma mark Initialilzers + +- (id)initWithChar:(char)value +{ + typeCode = @encode(char); + number = [[NSNumber alloc] initWithChar:value]; + + return self; +} + +- (id)initWithUnsignedChar:(unsigned char)value +{ + typeCode = @encode(unsigned char); + number = [[NSNumber alloc] initWithUnsignedChar:value]; + + return self; +} + +- (id)initWithShort:(short)value +{ + typeCode = @encode(short); + number = [[NSNumber alloc] initWithShort:value]; + + return self; +} + +- (id)initWithUnsignedShort:(unsigned short)value +{ + typeCode = @encode(unsigned short); + number = [[NSNumber alloc] initWithUnsignedShort:value]; + + return self; +} + +- (id)initWithInt:(int)value +{ + typeCode = @encode(int); + number = [[NSNumber alloc] initWithInt:value]; + + return self; +} + +- (id)initWithUnsignedInt:(unsigned int)value +{ + typeCode = @encode(unsigned int); + number = [[NSNumber alloc] initWithUnsignedInt:value]; + + return self; +} + +- (id)initWithLong:(long) value +{ + typeCode = @encode(long); + number = [[NSNumber alloc] initWithLong:value]; + + return self; +} + +- (id)initWithUnsignedLong:(unsigned long)value +{ + typeCode = @encode(unsigned long); + number = [[NSNumber alloc] initWithUnsignedLong:value]; + + return self; +} + +- (id)initWithLongLong:(long long)value +{ + typeCode = @encode(long long); + number = [[NSNumber alloc] initWithLongLong:value]; + + return self; +} + +- (id)initWithUnsignedLongLong:(unsigned long long)value +{ + typeCode = @encode(unsigned long long); + number = [[NSNumber alloc] initWithUnsignedLongLong:value]; + + return self; +} + +- (id)initWithFloat:(float)value +{ + typeCode = @encode(float); + number = [[NSNumber alloc] initWithFloat:value]; + + return self; +} + +- (id)initWithDouble:(double)value +{ + typeCode = @encode(double); + number = [[NSNumber alloc] initWithDouble:value]; + + return self; +} + +- (id)initWithBool:(BOOL)value +{ + typeCode = @encode(BOOL); + number = [[NSNumber alloc] initWithBool:value]; + + return self; +} + +#pragma mark - +#pragma mark NSValue primitive methods + +- (const char *)objCType +{ + return typeCode; +} + +- (void)getValue:(void *)buffer +{ + [number getValue:buffer]; +} + +#pragma mark - +#pragma mark NSNumber primitive methods + +/** + * Reparing the absence of primitive methodes in NSNumber + */ +- (char)charValue +{ + return [number charValue]; +} + +- (unsigned char)unsignedCharValue +{ + return [number unsignedCharValue]; +} + +- (short)shortValue +{ + return [number shortValue]; +} + +- (unsigned short)unsignedShortValue +{ + return [number unsignedShortValue]; +} + +- (int)intValue +{ + return [number intValue]; +} + +- (unsigned int)unsignedIntValue +{ + return [number unsignedIntValue]; +} + +- (long)longValue +{ + return [number longValue]; +} + +- (unsigned long)unsignedLongValue +{ + return [number unsignedLongValue]; +} + +- (long long)longLongValue +{ + return [number longLongValue]; +} + +- (unsigned long long)unsignedLongLongValue +{ + return [number unsignedLongLongValue]; +} + +- (float)floatValue +{ + return [number floatValue]; +} + +- (double)doubleValue +{ + return [number doubleValue]; +} + +- (BOOL)boolValue +{ + return [number boolValue]; +} + +#pragma mark - + +- (void)dealloc +{ + [number release]; + + [super dealloc]; +} + +@end diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPResult.h b/Frameworks/MCPKit/MCPFoundationKit/MCPResult.h new file mode 100644 index 00000000..b833e0ca --- /dev/null +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPResult.h @@ -0,0 +1,85 @@ +// +// $Id: MCPResult.h 1065 2009-07-19 10:58:17Z stuart02 $ +// +// MCPResult.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 08/12/2002. +// Copyright (c) 2001 Serge Cohen. All rights reserved. +// +// Forked by the Sequel Pro team (sequelpro.com), April 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> +#import "MCPConstants.h" + +#import "mysql.h" + +#define MAGIC_BINARY_CHARSET_NR 63 + +@interface MCPResult : NSObject +{ + MYSQL_RES *mResult; /* The MYSQL_RES structure of the C API. */ + NSArray *mNames; /* An NSArray holding the name of the columns. */ + NSDictionary *mMySQLLocales; /* A Locales dictionary to define the locales of MySQL. */ + NSStringEncoding mEncoding; /* The encoding used by MySQL server, to ISO-1 default. */ + unsigned int mNumOfFields; /* The number of fields in the result. */ + NSTimeZone *mTimeZone; /* The time zone of the connection when the query was made. */ +} + +// Initialization +- (id)initWithMySQLPtr:(MYSQL *)mySQLPtr encoding:(NSStringEncoding)theEncoding timeZone:(NSTimeZone *)iTimeZone; +- (id)initWithResPtr:(MYSQL_RES *)mySQLResPtr encoding:(NSStringEncoding)theEncoding timeZone:(NSTimeZone *)iTimeZone; + +// Result info +- (my_ulonglong)numOfRows; +- (unsigned int)numOfFields; + +// Rows +- (void)dataSeek:(my_ulonglong)row; +- (id)fetchRowAsType:(MCPReturnType) aType; +- (NSArray *)fetchRowAsArray; +- (NSDictionary *)fetchRowAsDictionary; + +// Columns +- (NSArray *)fetchFieldNames; +- (id)fetchTypesAsType:(MCPReturnType)aType; +- (NSArray *)fetchTypesAsArray; +- (NSDictionary *)fetchTypesAsDictionary; +- (NSArray *)fetchResultFieldsStructure; + +- (unsigned int)fetchFlagsAtIndex:(unsigned int)index; +- (unsigned int)fetchFlagsForKey:(NSString *)key; + +- (BOOL)isBlobAtIndex:(unsigned int)index; +- (BOOL)isBlobForKey:(NSString *)key; + +// Conversion +- (NSString *)stringWithText:(NSData *)theTextData; +- (const char *)cStringFromString:(NSString *)theString; +- (NSString *)stringWithCString:(const char *)theCString; + +// Other +- (NSString *)mysqlTypeToStringForType:(unsigned int)type withCharsetNr:(unsigned int)charsetnr withFlags:(unsigned int)flags withLength:(unsigned long long)length; +- (NSString *)mysqlTypeToGroupForType:(unsigned int)type withCharsetNr:(unsigned int)charsetnr withFlags:(unsigned int)flags; +- (NSString *)findCharsetName:(unsigned int)charsetnr; +- (NSString *)findCharsetCollation:(unsigned int)charsetnr; +- (unsigned int)findCharsetMaxByteLengthPerChar:(unsigned int)charsetnr; + +@end diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPResult.m b/Frameworks/MCPKit/MCPFoundationKit/MCPResult.m new file mode 100644 index 00000000..f8158f7c --- /dev/null +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPResult.m @@ -0,0 +1,1360 @@ +// +// $Id: MCPResult.m 1065 2009-07-19 10:58:17Z stuart02 $ +// +// MCPResult.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 08/12/2002. +// Copyright (c) 2001 Serge Cohen. All rights reserved. +// +// Forked by the Sequel Pro team (sequelpro.com), April 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import "MCPConnection.h" +#import "MCPNull.h" +#import "MCPNumber.h" +#import "MCPResult.h" + +NSCalendarDate *MCPYear0000; + +const OUR_CHARSET our_charsets60[] = +{ + {1, "big5","big5_chinese_ci", 1, 2}, + {3, "dec8", "dec8_swedisch_ci", 1, 1}, + {4, "cp850", "cp850_general_ci", 1, 1}, + {6, "hp8", "hp8_english_ci", 1, 1}, + {7, "koi8r", "koi8r_general_ci", 1, 1}, + {8, "latin1", "latin1_swedish_ci", 1, 1}, + {9, "latin2", "latin2_general_ci", 1, 1}, + {10, "swe7", "swe7_swedish_ci", 1, 1}, + {11, "ascii", "ascii_general_ci", 1, 1}, + {12, "ujis", "ujis_japanese_ci", 1, 3}, + {13, "sjis", "sjis_japanese_ci", 1, 2}, + {16, "hebrew", "hebrew_general_ci", 1, 1}, + {18, "tis620", "tis620_thai_ci", 1, 1}, + {19, "euckr", "euckr_korean_ci", 1, 2}, + {22, "koi8u", "koi8u_general_ci", 1, 1}, + {24, "gb2312", "gb2312_chinese_ci", 1, 2}, + {25, "greek", "greek_general_ci", 1, 1}, + {26, "cp1250", "cp1250_general_ci", 1, 1}, + {28, "gbk", "gbk_chinese_ci", 1, 2}, + {30, "latin5", "latin5_turkish_ci", 1, 1}, + {32, "armscii8", "armscii8_general_ci", 1, 1}, + {33, "utf8", "utf8_general_ci", 1, 3}, + {35, "ucs2", "ucs2_general_ci", 2, 2}, + {36, "cp866", "cp866_general_ci", 1, 1}, + {37, "keybcs2", "keybcs2_general_ci", 1, 1}, + {38, "macce", "macce_general_ci", 1, 1}, + {39, "macroman", "macroman_general_ci", 1, 1}, + {40, "cp852", "cp852_general_ci", 1, 1}, + {41, "latin7", "latin7_general_ci", 1, 1}, + {51, "cp1251", "cp1251_general_ci", 1, 1}, + {57, "cp1256", "cp1256_general_ci", 1, 1}, + {59, "cp1257", "cp1257_general_ci", 1, 1}, + {63, "binary", "binary", 1, 1}, + {92, "geostd8", "geostd8_general_ci", 1, 1}, + {95, "cp932", "cp932_japanese_ci", 1, 2}, + {97, "eucjpms", "eucjpms_japanese_ci", 1, 3}, + {2, "latin2", "latin2_czech_cs", 1, 1}, + {5, "latin1", "latin1_german_ci", 1, 1}, + {14, "cp1251", "cp1251_bulgarian_ci", 1, 1}, + {15, "latin1", "latin1_danish_ci", 1, 1}, + {17, "filename", "filename", 1, 5}, + {20, "latin7", "latin7_estonian_cs", 1, 1}, + {21, "latin2", "latin2_hungarian_ci", 1, 1}, + {23, "cp1251", "cp1251_ukrainian_ci", 1, 1}, + {27, "latin2", "latin2_croatian_ci", 1, 1}, + {29, "cp1257", "cp1257_lithunian_ci", 1, 1}, + {31, "latin1", "latin1_german2_ci", 1, 1}, + {34, "cp1250", "cp1250_czech_cs", 1, 1}, + {42, "latin7", "latin7_general_cs", 1, 1}, + {43, "macce", "macce_bin", 1, 1}, + {44, "cp1250", "cp1250_croatian_ci", 1, 1}, + {45, "utf8", "utf8_general_ci", 1, 1}, + {46, "utf8", "utf8_bin", 1, 1}, + {47, "latin1", "latin1_bin", 1, 1}, + {48, "latin1", "latin1_general_ci", 1, 1}, + {49, "latin1", "latin1_general_cs", 1, 1}, + {50, "cp1251", "cp1251_bin", 1, 1}, + {52, "cp1251", "cp1251_general_cs", 1, 1}, + {53, "macroman", "macroman_bin", 1, 1}, + {58, "cp1257", "cp1257_bin", 1, 1}, + {60, "armascii8", "armascii8_bin", 1, 1}, + {65, "ascii", "ascii_bin", 1, 1}, + {66, "cp1250", "cp1250_bin", 1, 1}, + {67, "cp1256", "cp1256_bin", 1, 1}, + {68, "cp866", "cp866_bin", 1, 1}, + {69, "dec8", "dec8_bin", 1, 1}, + {70, "greek", "greek_bin", 1, 1}, + {71, "hebew", "hebrew_bin", 1, 1}, + {72, "hp8", "hp8_bin", 1, 1}, + {73, "keybcs2", "keybcs2_bin", 1, 1}, + {74, "koi8r", "koi8r_bin", 1, 1}, + {75, "koi8u", "koi8u_bin", 1, 1}, + {77, "latin2", "latin2_bin", 1, 1}, + {78, "latin5", "latin5_bin", 1, 1}, + {79, "latin7", "latin7_bin", 1, 1}, + {80, "cp850", "cp850_bin", 1, 1}, + {81, "cp852", "cp852_bin", 1, 1}, + {82, "swe7", "swe7_bin", 1, 1}, + {93, "geostd8", "geostd8_bin", 1, 1}, + {83, "utf8", "utf8_bin", 1, 3}, + {84, "big5", "big5_bin", 1, 2}, + {85, "euckr", "euckr_bin", 1, 2}, + {86, "gb2312", "gb2312_bin", 1, 2}, + {87, "gbk", "gbk_bin", 1, 2}, + {88, "sjis", "sjis_bin", 1, 2}, + {89, "tis620", "tis620_bin", 1, 1}, + {90, "ucs2", "ucs2_bin", 2, 2}, + {91, "ujis", "ujis_bin", 1, 3}, + {94, "latin1", "latin1_spanish_ci", 1, 1}, + {96, "cp932", "cp932_bin", 1, 2}, + {99, "cp1250", "cp1250_polish_ci", 1, 1}, + {98, "eucjpms", "eucjpms_bin", 1, 3}, + {128, "ucs2", "ucs2_unicode_ci", 2, 2}, + {129, "ucs2", "ucs2_icelandic_ci", 2, 2}, + {130, "ucs2", "ucs2_latvian_ci", 2, 2}, + {131, "ucs2", "ucs2_romanian_ci", 2, 2}, + {132, "ucs2", "ucs2_slovenian_ci", 2, 2}, + {133, "ucs2", "ucs2_polish_ci", 2, 2}, + {134, "ucs2", "ucs2_estonian_ci", 2, 2}, + {135, "ucs2", "ucs2_spanish_ci", 2, 2}, + {136, "ucs2", "ucs2_swedish_ci", 2, 2}, + {137, "ucs2", "ucs2_turkish_ci", 2, 2}, + {138, "ucs2", "ucs2_czech_ci", 2, 2}, + {139, "ucs2", "ucs2_danish_ci", 2, 2}, + {140, "ucs2", "ucs2_lithunian_ci", 2, 2}, + {141, "ucs2", "ucs2_slovak_ci", 2, 2}, + {142, "ucs2", "ucs2_spanish2_ci", 2, 2}, + {143, "ucs2", "ucs2_roman_ci", 2, 2}, + {144, "ucs2", "ucs2_persian_ci", 2, 2}, + {145, "ucs2", "ucs2_esperanto_ci", 2, 2}, + {146, "ucs2", "ucs2_hungarian_ci", 2, 2}, + {147, "ucs2", "ucs2_sinhala_ci", 2, 2}, + {192, "utf8mb3", "utf8mb3_general_ci", 1, 3}, + {193, "utf8mb3", "utf8mb3_icelandic_ci", 1, 3}, + {194, "utf8mb3", "utf8mb3_latvian_ci", 1, 3}, + {195, "utf8mb3", "utf8mb3_romanian_ci", 1, 3}, + {196, "utf8mb3", "utf8mb3_slovenian_ci", 1, 3}, + {197, "utf8mb3", "utf8mb3_polish_ci", 1, 3}, + {198, "utf8mb3", "utf8mb3_estonian_ci", 1, 3}, + {119, "utf8mb3", "utf8mb3_spanish_ci", 1, 3}, + {200, "utf8mb3", "utf8mb3_swedish_ci", 1, 3}, + {201, "utf8mb3", "utf8mb3_turkish_ci", 1, 3}, + {202, "utf8mb3", "utf8mb3_czech_ci", 1, 3}, + {203, "utf8mb3", "utf8mb3_danish_ci", 1, 3}, + {204, "utf8mb3", "utf8mb3_lithunian_ci", 1, 3}, + {205, "utf8mb3", "utf8mb3_slovak_ci", 1, 3}, + {206, "utf8mb3", "utf8mb3_spanish2_ci", 1, 3}, + {207, "utf8mb3", "utf8mb3_roman_ci", 1, 3}, + {208, "utf8mb3", "utf8mb3_persian_ci", 1, 3}, + {209, "utf8mb3", "utf8mb3_esperanto_ci", 1, 3}, + {210, "utf8mb3", "utf8mb3_hungarian_ci", 1, 3}, + {211, "utf8mb3", "utf8mb3_sinhala_ci", 1, 3}, + {224, "utf8", "utf8_unicode_ci", 1, 3}, + {225, "utf8", "utf8_icelandic_ci", 1, 3}, + {226, "utf8", "utf8_latvian_ci", 1, 3}, + {227, "utf8", "utf8_romanian_ci", 1, 3}, + {228, "utf8", "utf8_slovenian_ci", 1, 3}, + {229, "utf8", "utf8_polish_ci", 1, 3}, + {230, "utf8", "utf8_estonian_ci", 1, 3}, + {231, "utf8", "utf8_spanish_ci", 1, 3}, + {232, "utf8", "utf8_swedish_ci", 1, 3}, + {233, "utf8", "utf8_turkish_ci", 1, 3}, + {234, "utf8", "utf8_czech_ci", 1, 3}, + {235, "utf8", "utf8_danish_ci", 1, 3}, + {236, "utf8", "utf8_lithuanian_ci", 1, 3}, + {237, "utf8", "utf8_slovak_ci", 1, 3}, + {238, "utf8", "utf8_spanish2_ci", 1, 3}, + {239, "utf8", "utf8_roman_ci", 1, 3}, + {240, "utf8", "utf8_persian_ci", 1, 3}, + {241, "utf8", "utf8_esperanto_ci", 1, 3}, + {242, "utf8", "utf8_hungarian_ci", 1, 3}, + {243, "utf8", "utf8_sinhala_ci", 1, 3}, + {254, "utf8mb3", "utf8mb3_general_cs", 1, 3}, + {0, NULL, NULL, 0, 0} +}; + +@implementation MCPResult + +/** + * Hold the results of a query to a MySQL database server. It correspond to the MYSQL_RES structure of the C API, and to the statement handle of the PERL DBI/DBD. + * + * Uses the !{mysql_store_result()} function from the C API. + * + * This object is generated only by a MCPConnection object, in this way (see #{MCPConnection} documentation): + * + * MCPConnection *theConnec = [MCPConnection alloc]; + * MCPResult *theRes; + * NSDictionary *theDict; + * NSArray *theColNames; + * int i, j; + * + * theConnec = [theConnec initToHost:@"albert.com" withLogin:@"toto" password:@"albert" usingPort:0]; + * [theConnec selectDB:@"db1"]; + * theRes = [theConnec queryString:@"select * from table1"]; + * theColNames = [theRes fetchFiedlsName]; + * i = 0; + * + * while (theDict = [theRes fetchRowAsDictionary]) { + * NSLog(@"Row : %d\n", i); + * for (j=0; j<[theColNames count]; j++) { + * NSLog(@" Field : %@, contain : %@\n", [theColNames objectAtIndex:j], [theDict objectForKey:[theColNames objectAtIndex:j]]); + * } + * i++; + * } + */ + +/** + * Initialize the class version to 3.0.1 + */ ++ (void)initialize +{ + if (self = [MCPResult class]) { + [self setVersion:030001]; // Ma.Mi.Re -> MaMiRe + MCPYear0000 = [[NSCalendarDate dateWithTimeIntervalSinceReferenceDate:-63146822400.0] retain]; + [MCPYear0000 setCalendarFormat:@"%Y"]; + } +} + +#pragma mark - +#pragma mark Initialisation + +/** + * Empty init, normaly of NO use to the user, again, MCPResult should be made through calls to MCPConnection + */ +- (id)init +{ + if ((self = [super init])) { + mEncoding = [MCPConnection defaultMySQLEncoding]; + + if (mResult) { + mysql_free_result(mResult); + mResult = NULL; + } + + if (mNames) { + [mNames release]; + mNames = NULL; + } + + if (mMySQLLocales == NULL) { + mMySQLLocales = [[MCPConnection getMySQLLocales] retain]; + } + + mNumOfFields = 0; + } + + return self; +} + +/** + * Initialise a MCPResult, it is used internally by MCPConnection !{queryString:} method: the only proper + * way to get a running MCPResult object. + */ +- (id)initWithMySQLPtr:(MYSQL *)mySQLPtr encoding:(NSStringEncoding)iEncoding timeZone:(NSTimeZone *)iTimeZone +{ + if ((self = [super init])) { + mEncoding = iEncoding; + mTimeZone = [iTimeZone retain]; + + if (mResult) { + mysql_free_result(mResult); + mResult = NULL; + } + + if (mNames) { + [mNames release]; + mNames = NULL; + } + + mResult = mysql_store_result(mySQLPtr); + + if (mResult) { + mNumOfFields = mysql_num_fields(mResult); + } + else { + mNumOfFields = 0; + } + + if (mMySQLLocales == NULL) { + mMySQLLocales = [[MCPConnection getMySQLLocales] retain]; + } + } + + return self; +} + +/** + * This metod is used internally by MCPConnection object when it have already a MYSQL_RES object to initialise + * MCPResult object. Initialise a MCPResult with the MYSQL_RES pointer (returned by such a function as mysql_list_dbs). + * NB: MCPResult should be made by using one of the method of MCPConnection. + */ +- (id)initWithResPtr:(MYSQL_RES *)mySQLResPtr encoding:(NSStringEncoding)iEncoding timeZone:(NSTimeZone *)iTimeZone +{ + if ((self = [super init])) { + mEncoding = iEncoding; + mTimeZone = [iTimeZone retain]; + + if (mResult) { + mysql_free_result(mResult); + mResult = NULL; + } + + if (mNames) { + [mNames release]; + mNames = NULL; + } + + mResult = mySQLResPtr; + + if (mResult) { + mNumOfFields = mysql_num_fields(mResult); + } + else { + mNumOfFields = 0; + } + + if (mMySQLLocales == NULL) { + mMySQLLocales = [[MCPConnection getMySQLLocales] retain]; + } + } + + return self; +} + +#pragma mark - +#pragma mark Result info + +/** + * Return the number of rows selected by the query. + */ +- (my_ulonglong)numOfRows +{ + if (mResult) { + return mysql_num_rows(mResult); + } + + return 0; +} + +/** + * Return the number of fields selected by the query. As a side effect it forces an update of the number of fields. + */ +- (unsigned int)numOfFields +{ + if (mResult) { + return mNumOfFields = mysql_num_fields(mResult); + } + + return mNumOfFields = 0; +} + +#pragma mark - +#pragma mark Rows + +/** + * Go to a precise row in the selected result. 0 is the very first row. + */ +- (void)dataSeek:(my_ulonglong)row +{ + my_ulonglong theRow = (row < 0)? 0 : row; + theRow = (theRow < [self numOfRows]) ? theRow : ([self numOfRows] - 1); + mysql_data_seek(mResult,theRow); +} + +/** + * + */ +- (id)fetchRowAsType:(MCPReturnType)aType +{ + MYSQL_ROW theRow; + unsigned long *theLengths; + MYSQL_FIELD *theField; + int i; + id theReturn; + + if (mResult == NULL) { + // If there is no results, returns nil, as after the last row... + return nil; + } + + theRow = mysql_fetch_row(mResult); + + if (theRow == NULL) { + return nil; + } + + switch (aType) { + case MCPTypeArray: + theReturn = [NSMutableArray arrayWithCapacity:mNumOfFields]; + break; + case MCPTypeDictionary: + if (mNames == nil) { + [self fetchFieldNames]; + } + theReturn = [NSMutableDictionary dictionaryWithCapacity:mNumOfFields]; + break; + default : + NSLog (@"Unknown type : %d, will return an Array!\n", aType); + theReturn = [NSMutableArray arrayWithCapacity:mNumOfFields]; + break; + } + + theLengths = mysql_fetch_lengths(mResult); + theField = mysql_fetch_fields(mResult); + + for (i=0; i<mNumOfFields; i++) { + id theCurrentObj; + + if (theRow[i] == NULL) { + theCurrentObj = [NSNull null]; + } else { + char *theData = calloc(sizeof(char),theLengths[i]+1); + //char *theUselLess; + memcpy(theData, theRow[i],theLengths[i]); + theData[theLengths[i]] = '\0'; + + switch (theField[i].type) { + case FIELD_TYPE_TINY: + case FIELD_TYPE_SHORT: + case FIELD_TYPE_INT24: + case FIELD_TYPE_LONG: + case FIELD_TYPE_LONGLONG: + case FIELD_TYPE_DECIMAL: + case FIELD_TYPE_NEWDECIMAL: + case FIELD_TYPE_FLOAT: + case FIELD_TYPE_DOUBLE: + case FIELD_TYPE_TIMESTAMP: + case FIELD_TYPE_DATE: + case FIELD_TYPE_TIME: + case FIELD_TYPE_DATETIME: + case FIELD_TYPE_YEAR: + case FIELD_TYPE_VAR_STRING: + case FIELD_TYPE_STRING: + case FIELD_TYPE_SET: + case FIELD_TYPE_ENUM: + case FIELD_TYPE_NEWDATE: // Don't know what the format for this type is... + theCurrentObj = [self stringWithCString:theData]; + break; + + case FIELD_TYPE_BIT: + theCurrentObj = [NSString stringWithFormat:@"%u", theData[0]]; + break; + + case FIELD_TYPE_TINY_BLOB: + case FIELD_TYPE_BLOB: + case FIELD_TYPE_MEDIUM_BLOB: + case FIELD_TYPE_LONG_BLOB: + theCurrentObj = [NSData dataWithBytes:theData length:theLengths[i]]; + if (!(theField[i].flags & BINARY_FLAG)) { // It is TEXT and NOT BLOB... + theCurrentObj = [self stringWithText:theCurrentObj]; + } // #warning Should check for TEXT (using theField[i].flag BINARY_FLAG) + break; + + case FIELD_TYPE_NULL: + theCurrentObj = [NSNull null]; + break; + + default: + NSLog (@"in fetchRowAsType : Unknown type : %d for column %d, send back a NSData object", (int)theField[i].type, (int)i); + theCurrentObj = [NSData dataWithBytes:theData length:theLengths[i]]; + break; + } + + free(theData); + + // Some of the creators return nil object... + if (theCurrentObj == nil) { + theCurrentObj = [NSNull null]; + } + } + + switch (aType) { + case MCPTypeDictionary : + [theReturn setObject:theCurrentObj forKey:[mNames objectAtIndex:i]]; + break; + + case MCPTypeArray : + default : + [theReturn addObject:theCurrentObj]; + break; + } + } + + return theReturn; +} + +/** + * Return the next row of the result as an array, the index in select field order, the object a proper object + * for handling the information in the field (NSString, NSNumber ...). + * + * Just a #{typed} wrapper for method !{fetchRosAsType:} (with arg MCPTypeArray). + * + * NB: Returned object is immutable. + */ +- (NSArray *)fetchRowAsArray +{ + NSMutableArray *theArray = [self fetchRowAsType:MCPTypeArray]; + + return (theArray) ? [NSArray arrayWithArray:theArray] : nil; +} + +/** + * Return the next row of the result as a dictionary, the key being the field name, the object a proper object + * for handling the information in the field (NSString, NSNumber ...). + * + * Just a #{typed} wrapper for method !{fetchRosAsType:} (with arg MCPTypeDictionary). + * + * NB: Returned object is immutable. + */ +- (NSDictionary *)fetchRowAsDictionary +{ + NSMutableDictionary *theDict = [self fetchRowAsType:MCPTypeDictionary]; + + return (theDict) ? [NSDictionary dictionaryWithDictionary:theDict] : nil; +} + +#pragma mark - +#pragma mark Columns + +/** + * Generate the mNames if not already generated, and return it. + * + * mNames is a NSArray holding the names of the fields(columns) of the results. + */ +- (NSArray *)fetchFieldNames +{ + int i; + unsigned int theNumFields; + NSMutableArray *theNamesArray; + MYSQL_FIELD *theField; + + if (mNames) { + return mNames; + } + + if (mResult == NULL) { + // If no results, give an empty array. Maybe it's better to give a nil pointer? + return (mNames = [[NSArray array] retain]); + } + + theNumFields = [self numOfFields]; + theNamesArray = [NSMutableArray arrayWithCapacity: theNumFields]; + theField = mysql_fetch_fields(mResult); + + for (i=0; i<theNumFields; i++) { + NSString *theName = [self stringWithCString:theField[i].name]; + if ((theName) && (![theName isEqualToString:@""])) { + [theNamesArray addObject:theName]; + } + else { + [theNamesArray addObject:[NSString stringWithFormat:@"Column %d", i]]; + } + } + + return (mNames = [[NSArray arrayWithArray:theNamesArray] retain]); +} + +/** + * Return a collection of the fields's type. The type of collection is choosen by the aType variable + * (MCPTypeArray or MCPTypeDictionary). + * + * This method returned directly the #{mutable} object generated while going through all the columns + */ +- (id)fetchTypesAsType:(MCPReturnType)aType +{ + int i; + id theTypes; + MYSQL_FIELD *theField; + + if (mResult == NULL) { + // If no results, give an empty array. Maybe it's better to give a nil pointer? + return nil; + } + + switch (aType) { + case MCPTypeArray: + theTypes = [NSMutableArray arrayWithCapacity:mNumOfFields]; + break; + case MCPTypeDictionary: + if (mNames == nil) { + [self fetchFieldNames]; + } + theTypes = [NSMutableDictionary dictionaryWithCapacity:mNumOfFields]; + break; + default : + NSLog (@"Unknown type : %d, will return an Array!\n", aType); + theTypes = [NSMutableArray arrayWithCapacity:mNumOfFields]; + break; + } + + theField = mysql_fetch_fields(mResult); + + for (i=0; i<mNumOfFields; i++) { + NSString *theType; + switch (theField[i].type) { + case FIELD_TYPE_TINY: + theType = @"tiny"; + break; + case FIELD_TYPE_SHORT: + theType = @"short"; + break; + case FIELD_TYPE_LONG: + theType = @"long"; + break; + case FIELD_TYPE_INT24: + theType = @"int24"; + break; + case FIELD_TYPE_LONGLONG: + theType = @"longlong"; + break; + case FIELD_TYPE_DECIMAL: + theType = @"decimal"; + break; + case FIELD_TYPE_FLOAT: + theType = @"float"; + break; + case FIELD_TYPE_DOUBLE: + theType = @"double"; + break; + case FIELD_TYPE_TIMESTAMP: + theType = @"timestamp"; + break; + case FIELD_TYPE_DATE: + theType = @"date"; + break; + case FIELD_TYPE_TIME: + theType = @"time"; + break; + case FIELD_TYPE_DATETIME: + theType = @"datetime"; + break; + case FIELD_TYPE_YEAR: + theType = @"year"; + break; + case FIELD_TYPE_VAR_STRING: + theType = @"varstring"; + break; + case FIELD_TYPE_STRING: + theType = @"string"; + break; + case FIELD_TYPE_TINY_BLOB: + theType = @"tinyblob"; + break; + case FIELD_TYPE_BLOB: + theType = @"blob"; + break; + case FIELD_TYPE_MEDIUM_BLOB: + theType = @"mediumblob"; + break; + case FIELD_TYPE_LONG_BLOB: + theType = @"longblob"; + break; + case FIELD_TYPE_SET: + theType = @"set"; + break; + case FIELD_TYPE_ENUM: + theType = @"enum"; + break; + case FIELD_TYPE_NULL: + theType = @"null"; + break; + case FIELD_TYPE_NEWDATE: + theType = @"newdate"; + break; + default: + theType = @"unknown"; + NSLog (@"in fetchTypesAsArray : Unknown type for column %d of the MCPResult, type = %d", (int)i, (int)theField[i].type); + break; + } + + switch (aType) { + case MCPTypeArray : + [theTypes addObject:theType]; + break; + case MCPTypeDictionary : + [theTypes setObject:theType forKey:[mNames objectAtIndex:i]]; + break; + default : + [theTypes addObject:theType]; + break; + } + } + + return theTypes; +} + +/** + * Return an array of the fields' types. + * + * NB: Returned object is immutable. + */ +- (NSArray *)fetchTypesAsArray +{ + NSMutableArray *theArray = [self fetchTypesAsType:MCPTypeArray]; + + return (theArray) ? [NSArray arrayWithArray:theArray] : nil; +} + +/** + * Return a dictionnary of the fields' types (keys are the fields' names). + * + * NB: Returned object is immutable. + */ +- (NSDictionary*) fetchTypesAsDictionary +{ + NSMutableDictionary *theDict = [self fetchTypesAsType:MCPTypeDictionary]; + + return (theDict) ? [NSDictionary dictionaryWithDictionary:theDict] : nil; +} + +/** + * Return an array of dicts containg column data of the last executed query + */ +- (NSArray *)fetchResultFieldsStructure +{ + MYSQL_FIELD *theField; + + NSMutableArray *structureResult = [NSMutableArray array]; + + unsigned int i; + unsigned int numFields = mysql_num_fields(mResult); + + if (mResult == NULL) return nil; + + theField = mysql_fetch_fields(mResult); + + for (i=0; i < numFields; i++) + { + NSMutableDictionary *fieldStructure = [NSMutableDictionary dictionaryWithCapacity:39]; + + /* Original column position */ + [fieldStructure setObject:[NSNumber numberWithInt:i] forKey:@"datacolumnindex"]; + + /* Name of column */ + [fieldStructure setObject:[self stringWithCString:theField[i].name] forKey:@"name"]; + // [fieldStructure setObject:[NSNumber numberWithUnsignedInt:theField[i].name_length] forKey:@"name_length"]; + + /* Original column name, if an alias */ + [fieldStructure setObject:[self stringWithCString:theField[i].org_name] forKey:@"org_name"]; + // [fieldStructure setObject:[NSNumber numberWithUnsignedInt:theField[i].org_name_length] forKey:@"org_name_length"]; + + /* Table of column if column was a field */ + [fieldStructure setObject:[self stringWithCString:theField[i].table] forKey:@"table"]; + // [fieldStructure setObject:[NSNumber numberWithUnsignedInt:theField[i].table_length] forKey:@"table_length"]; + + /* Org table name, if table was an alias */ + [fieldStructure setObject:[self stringWithCString:theField[i].org_table] forKey:@"org_table"]; + // [fieldStructure setObject:[NSNumber numberWithUnsignedInt:theField[i].org_table_length] forKey:@"org_table_length"]; + + /* Database for table */ + [fieldStructure setObject:[self stringWithCString:theField[i].db] forKey:@"db"]; + // [fieldStructure setObject:[NSNumber numberWithUnsignedInt:theField[i].db_length] forKey:@"db_length"]; + + /* Catalog for table */ + // [fieldStructure setObject:[self stringWithCString:theField[i].catalog] forKey:@"catalog"]; + // [fieldStructure setObject:[NSNumber numberWithUnsignedInt:theField[i].catalog_length] forKey:@"catalog_length"]; + + /* Default value (set by mysql_list_fields) */ + // [fieldStructure setObject:[self stringWithCString:theField[i].def] forKey:@"def"]; + // [fieldStructure setObject:[NSNumber numberWithUnsignedInt:theField[i].def_length] forKey:@"def_length"]; + + /* Width of column (real length in bytes) */ + [fieldStructure setObject:[NSNumber numberWithUnsignedLongLong:theField[i].length] forKey:@"byte_length"]; + /* Width of column (as in create)*/ + [fieldStructure setObject:[NSNumber numberWithUnsignedLongLong:theField[i].length/[self findCharsetMaxByteLengthPerChar:theField[i].charsetnr]] + forKey:@"char_length"]; + /* Max width (bytes) for selected set */ + [fieldStructure setObject:[NSNumber numberWithUnsignedLongLong:theField[i].max_length] forKey:@"max_byte_length"]; + /* Max width (chars) for selected set */ + // [fieldStructure setObject:[NSNumber numberWithUnsignedLongLong:theField[i].max_length/[self find_charsetMaxByteLengthPerChar:theField[i].charsetnr]] + // forKey:@"max_char_length"]; + + /* Div flags */ + [fieldStructure setObject:[NSNumber numberWithUnsignedInt:theField[i].flags] forKey:@"flags"]; + [fieldStructure setObject:[NSNumber numberWithBool:(theField[i].flags & NOT_NULL_FLAG) ? YES : NO] forKey:@"null"]; + [fieldStructure setObject:[NSNumber numberWithBool:(theField[i].flags & PRI_KEY_FLAG) ? YES : NO] forKey:@"PRI_KEY_FLAG"]; + [fieldStructure setObject:[NSNumber numberWithBool:(theField[i].flags & UNIQUE_KEY_FLAG) ? YES : NO] forKey:@"UNIQUE_KEY_FLAG"]; + [fieldStructure setObject:[NSNumber numberWithBool:(theField[i].flags & MULTIPLE_KEY_FLAG) ? YES : NO] forKey:@"MULTIPLE_KEY_FLAG"]; + [fieldStructure setObject:[NSNumber numberWithBool:(theField[i].flags & BLOB_FLAG) ? YES : NO] forKey:@"BLOB_FLAG"]; + [fieldStructure setObject:[NSNumber numberWithBool:(theField[i].flags & UNSIGNED_FLAG) ? YES : NO] forKey:@"UNSIGNED_FLAG"]; + [fieldStructure setObject:[NSNumber numberWithBool:(theField[i].flags & ZEROFILL_FLAG) ? YES : NO] forKey:@"ZEROFILL_FLAG"]; + [fieldStructure setObject:[NSNumber numberWithBool:(theField[i].flags & BINARY_FLAG) ? YES : NO] forKey:@"BINARY_FLAG"]; + [fieldStructure setObject:[NSNumber numberWithBool:(theField[i].flags & ENUM_FLAG) ? YES : NO] forKey:@"ENUM_FLAG"]; + [fieldStructure setObject:[NSNumber numberWithBool:(theField[i].flags & AUTO_INCREMENT_FLAG) ? YES : NO] forKey:@"AUTO_INCREMENT_FLAG"]; + [fieldStructure setObject:[NSNumber numberWithBool:(theField[i].flags & SET_FLAG) ? YES : NO] forKey:@"SET_FLAG"]; + [fieldStructure setObject:[NSNumber numberWithBool:(theField[i].flags & NUM_FLAG) ? YES : NO] forKey:@"NUM_FLAG"]; + [fieldStructure setObject:[NSNumber numberWithBool:(theField[i].flags & PART_KEY_FLAG) ? YES : NO] forKey:@"PART_KEY_FLAG"]; + // [fieldStructure setObject:[NSNumber numberWithInt:(theField[i].flags & GROUP_FLAG) ? 1 : 0] forKey:@"GROUP_FLAG"]; + // [fieldStructure setObject:[NSNumber numberWithInt:(theField[i].flags & UNIQUE_FLAG) ? 1 : 0] forKey:@"UNIQUE_FLAG"]; + // [fieldStructure setObject:[NSNumber numberWithInt:(theField[i].flags & BINCMP_FLAG) ? 1 : 0] forKey:@"BINCMP_FLAG"]; + + /* Number of decimals in field */ + [fieldStructure setObject:[NSNumber numberWithUnsignedInt:theField[i].decimals] forKey:@"decimals"]; + + /* Character set */ + [fieldStructure setObject:[NSNumber numberWithUnsignedInt:theField[i].charsetnr] forKey:@"charsetnr"]; + [fieldStructure setObject:[self findCharsetName:theField[i].charsetnr] forKey:@"charset_name"]; + [fieldStructure setObject:[self findCharsetCollation:theField[i].charsetnr] forKey:@"charset_collation"]; + + /* Table type */ + [fieldStructure setObject:[self mysqlTypeToStringForType:theField[i].type + withCharsetNr:theField[i].charsetnr + withFlags:theField[i].flags + withLength:theField[i].length + ] forKey:@"type"]; + + /* Table type group*/ + [fieldStructure setObject:[self mysqlTypeToGroupForType:theField[i].type + withCharsetNr:theField[i].charsetnr + withFlags:theField[i].flags + ] forKey:@"typegrouping"]; + + [structureResult addObject:fieldStructure]; + + } + + return structureResult; + +} + +/** + * Return the MySQL flags of the column at the given index... Can be used to check if a number is signed or not... + */ +- (unsigned int)fetchFlagsAtIndex:(unsigned int)index +{ + unsigned int theRet; + unsigned int theNumFields; + MYSQL_FIELD *theField; + + if (mResult == NULL) { + // If no results, give an empty array. Maybe it's better to give a nil pointer? + return (0); + } + + theNumFields = [self numOfFields]; + theField = mysql_fetch_fields(mResult); + + if (index >= theNumFields) { + // Out of range... should raise an exception + theRet = 0; + } + else { + theRet = theField[index].flags; + } + + return theRet; +} + +/** + * + */ +- (unsigned int)fetchFlagsForKey:(NSString *)key +{ + unsigned int theRet; + unsigned int theNumFields, index; + MYSQL_FIELD *theField; + + if (mResult == NULL) { + // If no results, give an empty array. Maybe it's better to give a nil pointer? + return (0); + } + + if (mNames == NULL) { + [self fetchFieldNames]; + } + + theNumFields = [self numOfFields]; + theField = mysql_fetch_fields(mResult); + + if ([mNames indexOfObject:key] == NSNotFound) { + // Non existent key... should raise an exception + theRet = 0; + } + else { + index = [mNames indexOfObject:key]; + + theRet = theField[index].flags; + } + + return theRet; +} + +/** + * Return YES if the field with the given index is a BLOB. It should be used to discriminates between BLOBs + * and TEXTs. + * + * #{DEPRECATED}, This method is not consistent with the C API which is supposed to return YES for BOTH + * text and blob (and BTW is also deprecated)... + * + * #{NOTE} That the current version handles properly TEXT, and returns those as NSString (and not NSData as + * it used to be). + */ +- (BOOL)isBlobAtIndex:(unsigned int)index +{ + BOOL theRet; + unsigned int theNumFields; + MYSQL_FIELD *theField; + + if (mResult == NULL) { + // If no results, give an empty array. Maybe it's better to give a nil pointer? + return (NO); + } + + theNumFields = [self numOfFields]; + theField = mysql_fetch_fields(mResult); + + if (index >= theNumFields) { + // Out of range... should raise an exception + theRet = NO; + } + else { + switch(theField[index].type) { + case FIELD_TYPE_TINY_BLOB: + case FIELD_TYPE_BLOB: + case FIELD_TYPE_MEDIUM_BLOB: + case FIELD_TYPE_LONG_BLOB: + theRet = (theField[index].flags & BINARY_FLAG); + break; + default: + theRet = NO; + break; + } + } + + return theRet; +} + +/** + * Return YES if the field (by name) with the given index is a BLOB. It should be used to discriminates + * between BLOBs and TEXTs. + * + * #{DEPRECATED}, This method is not consistent with the C API which is supposed to return YES for BOTH + * text and blob (and BTW is also deprecated)... + * + * #{NOTE} That the current version handles properly TEXT, and returns those as NSString (and not NSData + * as it used to be). + */ +- (BOOL)isBlobForKey:(NSString *)key +{ + BOOL theRet; + unsigned int theNumFields, index; + MYSQL_FIELD *theField; + + if (mResult == NULL) { + // If no results, give an empty array. Maybe it's better to give a nil pointer? + return (NO); + } + + if (mNames == NULL) { + [self fetchFieldNames]; + } + + theNumFields = [self numOfFields]; + theField = mysql_fetch_fields(mResult); + + if ([mNames indexOfObject:key] == NSNotFound) { + // Non existent key... should raise an exception + theRet = NO; + } + else { + index = [mNames indexOfObject:key]; + + switch(theField[index].type) { + case FIELD_TYPE_TINY_BLOB: + case FIELD_TYPE_BLOB: + case FIELD_TYPE_MEDIUM_BLOB: + case FIELD_TYPE_LONG_BLOB: + theRet = (theField[index].flags & BINARY_FLAG); + break; + default: + theRet = NO; + break; + } + } + + return theRet; +} + +#pragma mark - +#pragma mark Conversion + +/** + * Use the string encoding to convert the returned NSData to a string (for a TEXT field). + */ +- (NSString *)stringWithText:(NSData *)theTextData +{ + NSString *theString; + + if (theTextData == nil) { + return nil; + } + + theString = [[NSString alloc] initWithData:theTextData encoding:mEncoding]; + + if (theString) { + [theString autorelease]; + } + + return theString; +} + +/** + * Return a (long) string containing the table of results, first line being the fields name, next line(s) + * the row(s). Useful to have NSLog logging a MCPResult (example). + */ +- (NSString *)description +{ + if (mResult == NULL) { + return @"This is an empty MCPResult\n"; + } + else { + NSMutableString *theString = [NSMutableString stringWithCapacity:0]; + int i; + NSArray *theRow; + MYSQL_ROW_OFFSET thePosition; + BOOL trunc = [MCPConnection truncateLongField]; + + // First line, saying we are displaying a MCPResult + [theString appendFormat:@"MCPResult: (encoding : %d, dim %d x %d)\n", (long)mEncoding, (long)mNumOfFields, (long)[self numOfRows]]; + // Second line: the field names, tab separated + [self fetchFieldNames]; + + for (i=0; i<(mNumOfFields-1); i++) { + [theString appendFormat:@"%@\t", [mNames objectAtIndex:i]]; + } + + [theString appendFormat:@"%@\n", [mNames objectAtIndex:i]]; + // Next lines, the records (saving current position to put it back after the full display) + thePosition = mysql_row_tell(mResult); + [self dataSeek:0]; + + while (theRow = [self fetchRowAsArray]) + { + id theField = [theRow objectAtIndex:i]; + + if (trunc) { + if (([theField isKindOfClass:[NSString class]]) && (kLengthOfTruncationForLog < [(NSString *)theField length])) { + theField = [theField substringToIndex:kLengthOfTruncationForLog]; + } + else if (([theField isKindOfClass:[NSData class]]) && (kLengthOfTruncationForLog < [(NSData *)theField length])) { + theField = [NSData dataWithBytes:[theField bytes] length:kLengthOfTruncationForLog]; + } + } + + for (i=0; i<(mNumOfFields - 1); i++) + { + [theString appendFormat:@"%@\t", theField]; + } + + [theString appendFormat:@"%@\n", theField]; + } + + // Returning to the proper row + mysql_row_seek(mResult, thePosition); + + return theString; + } +} + +/** + * For internal use only. Transform a NSString to a C type string (ended with \0) using ethe character set + * from the MCPConnection. Lossy conversions are enabled. + */ +- (const char *)cStringFromString:(NSString *)theString +{ + NSMutableData *theData; + + if (!theString) { + return (const char *)NULL; + } + + theData = [NSMutableData dataWithData:[theString dataUsingEncoding:mEncoding allowLossyConversion:YES]]; + [theData increaseLengthBy:1]; + + return (const char *)[theData bytes]; +} + +/** + * Return a NSString from a C style string encoded with the character set of theMCPConnection. + */ +- (NSString *)stringWithCString:(const char *)theCString +{ + NSData *theData; + NSString *theString; + + if (theCString == NULL) { + return @""; + } + + theData = [NSData dataWithBytes:theCString length:(strlen(theCString))]; + theString = [[NSString alloc] initWithData:theData encoding:mEncoding]; + + if (theString) { + [theString autorelease]; + } + + return theString; +} + +#pragma mark - +#pragma mark Other + +/** + * Convert a mysql_type to a string + */ +- (NSString *)mysqlTypeToStringForType:(unsigned int)type withCharsetNr:(unsigned int)charsetnr withFlags:(unsigned int)flags withLength:(unsigned long long)length +{ + // BOOL isUnsigned = (flags & UNSIGNED_FLAG) != 0; + // BOOL isZerofill = (flags & ZEROFILL_FLAG) != 0; + + switch (type) { + case FIELD_TYPE_BIT: + return @"BIT"; + case MYSQL_TYPE_DECIMAL: + //return isUnsigned ? (isZerofill? @"DECIMAL UNSIGNED ZEROFILL" : @"DECIMAL UNSIGNED"): + return @"DECIMAL"; + case MYSQL_TYPE_TINY: + // return isUnsigned ? (isZerofill? @"TINYINT UNSIGNED ZEROFILL" : @"TINYINT UNSIGNED"): + return @"TINYINT"; + case MYSQL_TYPE_SHORT: + // return isUnsigned ? (isZerofill? @"SMALLINT UNSIGNED ZEROFILL" : @"SMALLINT UNSIGNED"): + return @"SMALLINT"; + case MYSQL_TYPE_LONG: + // return isUnsigned ? (isZerofill? @"INT UNSIGNED ZEROFILL" : @"INT UNSIGNED"): + return @"INT"; + case MYSQL_TYPE_FLOAT: + // return isUnsigned ? (isZerofill? @"FLOAT UNSIGNED ZEROFILL" : @"FLOAT UNSIGNED"): + return @"FLOAT"; + case MYSQL_TYPE_DOUBLE: + // return isUnsigned ? (isZerofill? @"DOUBLE UNSIGNED ZEROFILL" : @"DOUBLE UNSIGNED"): + return @"DOUBLE"; + case MYSQL_TYPE_NULL: + return @"NULL"; + case MYSQL_TYPE_TIMESTAMP: + return @"TIMESTAMP"; + case MYSQL_TYPE_LONGLONG: + // return isUnsigned ? (isZerofill? @"BIGINT UNSIGNED ZEROFILL" : @"BIGINT UNSIGNED") : + return @"BIGINT"; + case MYSQL_TYPE_INT24: + // return isUnsigned ? (isZerofill? @"MEDIUMINT UNSIGNED ZEROFILL" : @"MEDIUMINT UNSIGNED") : + return @"MEDIUMINT"; + case MYSQL_TYPE_DATE: + return @"DATE"; + case MYSQL_TYPE_TIME: + return @"TIME"; + case MYSQL_TYPE_DATETIME: + return @"DATETIME"; + case MYSQL_TYPE_TINY_BLOB:// should no appear over the wire + case MYSQL_TYPE_MEDIUM_BLOB:// should no appear over the wire + case MYSQL_TYPE_LONG_BLOB:// should no appear over the wire + case MYSQL_TYPE_BLOB: + { + BOOL isBlob = (charsetnr == MAGIC_BINARY_CHARSET_NR); + switch ((int)length/[self findCharsetMaxByteLengthPerChar:charsetnr]) { + case 255: return isBlob? @"TINYBLOB":@"TINYTEXT"; + case 65535: return isBlob? @"BLOB":@"TEXT"; + case 16777215: return isBlob? @"MEDIUMBLOB":@"MEDIUMTEXT"; + case 4294967295: return isBlob? @"LONGBLOB":@"LONGTEXT"; + default: + switch (length) { + case 255: return isBlob? @"TINYBLOB":@"TINYTEXT"; + case 65535: return isBlob? @"BLOB":@"TEXT"; + case 16777215: return isBlob? @"MEDIUMBLOB":@"MEDIUMTEXT"; + case 4294967295: return isBlob? @"LONGBLOB":@"LONGTEXT"; + default: + return @"UNKNOWN"; + } + } + } + case MYSQL_TYPE_VAR_STRING: + if (flags & ENUM_FLAG) { + return @"ENUM"; + } + if (flags & SET_FLAG) { + return @"SET"; + } + if (charsetnr == MAGIC_BINARY_CHARSET_NR) { + return @"VARBINARY"; + } + return @"VARCHAR"; + case MYSQL_TYPE_STRING: + if (flags & ENUM_FLAG) { + return @"ENUM"; + } + if (flags & SET_FLAG) { + return @"SET"; + } + if ((flags & BINARY_FLAG) && charsetnr == MAGIC_BINARY_CHARSET_NR) { + return @"BINARY"; + } + return @"CHAR"; + case MYSQL_TYPE_ENUM: + /* This should never happen */ + return @"ENUM"; + case MYSQL_TYPE_YEAR: + return @"YEAR"; + case MYSQL_TYPE_SET: + /* This should never happen */ + return @"SET"; + case MYSQL_TYPE_GEOMETRY: + return @"GEOMETRY"; + default: + return @"UNKNOWN"; + } +} + +/** + * Merge mysql_types into type groups + */ +- (NSString *)mysqlTypeToGroupForType:(unsigned int)type withCharsetNr:(unsigned int)charsetnr withFlags:(unsigned int)flags +{ + switch(type){ + case FIELD_TYPE_BIT: + return @"bit"; + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_INT24: + return @"integer"; + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_DECIMAL: + return @"float"; + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TIMESTAMP: + return @"date"; + case MYSQL_TYPE_VAR_STRING: + if (flags & ENUM_FLAG) { + return @"enum"; + } + if (flags & SET_FLAG) { + return @"enum"; + } + if (charsetnr == MAGIC_BINARY_CHARSET_NR) { + return @"binary"; + } + return @"string"; + case MYSQL_TYPE_STRING: + if (flags & ENUM_FLAG) { + return @"enum"; + } + if (flags & SET_FLAG) { + return @"enum"; + } + if ((flags & BINARY_FLAG) && charsetnr == MAGIC_BINARY_CHARSET_NR) { + return @"binary"; + } + return @"string"; + case MYSQL_TYPE_TINY_BLOB: // should no appear over the wire + case MYSQL_TYPE_MEDIUM_BLOB: // should no appear over the wire + case MYSQL_TYPE_LONG_BLOB: // should no appear over the wire + case MYSQL_TYPE_BLOB: + { + if (charsetnr == MAGIC_BINARY_CHARSET_NR) { + return @"blobdata"; + } else { + return @"textdata"; + } + } + case MYSQL_TYPE_GEOMETRY: + return @"geometry"; + default: + return @"blobdata"; + + } +} + +/** + * Convert a mysql_charsetnr into a charset name as string + */ +- (NSString *)findCharsetName:(unsigned int)charsetnr +{ + const OUR_CHARSET * c = our_charsets60; + + do { + if (c->nr == charsetnr) + return [self stringWithCString:c->name]; + ++c; + } while (c[0].nr != 0); + + return @"UNKNOWN"; +} + +/** + * Convert a mysql_charsetnr into a collation name as string + */ +- (NSString *)findCharsetCollation:(unsigned int)charsetnr +{ + const OUR_CHARSET * c = our_charsets60; + + do { + if (c->nr == charsetnr) + return [self stringWithCString:c->collation]; + ++c; + } while (c[0].nr != 0); + + return @"UNKNOWN"; +} + +/** + * Return the max byte length to store a char by using + * a specific mysql_charsetnr + */ +- (unsigned int)findCharsetMaxByteLengthPerChar:(unsigned int)charsetnr +{ + const OUR_CHARSET * c = our_charsets60; + + do { + if (c->nr == charsetnr) + return c->char_maxlen; + ++c; + } while (c[0].nr != 0); + + return 1; +} + +#pragma mark - + +/** + * Do one really needs an explanation for this method? Which by the way you should not use... + */ +- (void) dealloc +{ + if (mResult) { + mysql_free_result(mResult); + } + + if (mNames) { + [mNames autorelease]; + } + + if (mMySQLLocales) { + [mMySQLLocales autorelease]; + } + + [super dealloc]; +} + +@end diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPResultPlus.h b/Frameworks/MCPKit/MCPFoundationKit/MCPResultPlus.h new file mode 100644 index 00000000..5d836a03 --- /dev/null +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPResultPlus.h @@ -0,0 +1,42 @@ +// +// $Id: MCPResultPlus.h 1056 2009-07-18 10:42:29Z stuart02 $ +// +// MCPResultPlus.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 03/06/2002. +// Copyright (c) 2001 Serge Cohen. All rights reserved. +// +// Forked by the Sequel Pro team (sequelpro.com), April 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> + +#import "MCPResult.h" + +@interface MCPResult (MCPResultPlus) + +// Getting a complete column as an array +- (NSArray *)fetchColAtIndex:(unsigned int)col; +- (NSArray *)fetchColWithName:(NSString *)colName; + +// Getting the complete result as 2D array +- (id)fetch2DResultAsType:(MCPReturnType)type; + +@end diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPResultPlus.m b/Frameworks/MCPKit/MCPFoundationKit/MCPResultPlus.m new file mode 100644 index 00000000..bee4442a --- /dev/null +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPResultPlus.m @@ -0,0 +1,188 @@ +// +// $Id: MCPResultPlus.m 1056 2009-07-18 10:42:29Z stuart02 $ +// +// MCPResultPlus.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 03/06/2002. +// Copyright (c) 2001 Serge Cohen. All rights reserved. +// +// Forked by the Sequel Pro team (sequelpro.com), April 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import "MCPResultPlus.h" + +/** + * This Category is provided to get shortcuts reformat the table obtained by a MCPResult + * (fetching a column, a 2D array...). + */ +@implementation MCPResult (MCPResultPlus) + +/** + * Getting a complete column into a NSArray (1D). The index starts from 0 (first column). + * + * The index 0 of the returned array always correspond to the first row (ie: returned NSArray is indexed + * by row number), the read position is restored after to it's initial position after the read. + */ +- (NSArray *)fetchColAtIndex:(unsigned int)col +{ + NSMutableArray *theCol = [NSMutableArray arrayWithCapacity:[self numOfRows]]; + MYSQL_ROW_OFFSET thePosition; + NSArray *theRow; + + if ((mResult == NULL) || ([self numOfRows] == 0)) { + // If there is no results, returns nil. + return nil; + } + if (col >= mNumOfFields) { + // Bad column number + NSLog (@"The index : %d is not within the range 0 - %d\n", (long)col, (long)mNumOfFields); + return nil; + } + + thePosition = mysql_row_tell(mResult); + + [self dataSeek:0]; + + // One might want to have optimized code here. Maybe in later versions + while (theRow = [self fetchRowAsType:MCPTypeArray]) + { + [theCol addObject:[theRow objectAtIndex:col]]; + } + + // Returning to the proper row + mysql_row_seek(mResult, thePosition); + + return [NSArray arrayWithArray:theCol]; +} + +/** + * The same as !{fetchColAtIndex:}, but the choice of the column is done by it's field name. Indeed it is just + * a wrapper to !{fetchColAtIndex}. + */ +- (NSArray *)fetchColWithName:(NSString *)colName +{ + unsigned int theCol; + + if (mResult == NULL) { + // If there is no results, returns nil. + return nil; + } + + if (mNames == nil) { + [self fetchFieldNames]; + } + + if ([mNames indexOfObject:colName] == NSNotFound) { + NSLog(@"No column have been found with name : %@\n", colName); + return nil; + } + + theCol = [mNames indexOfObject:colName]; + + return [self fetchColAtIndex:theCol]; +} + +/** + * Returns the complete result table in a 2D object, which type depends on aType: + * + * - !{MCPTypeArray} : a NSArray of rows as NSArray, + * + * - !{MCPTypeDictionary} : a NSArray of rows as NSDictionary, + * + * - !{MCPTypeFlippedArray} : a NSArray of columns (as NSArray), + * + * - !{MCPTypeFlippedDictionary} : a NSDictionary of columns (as NSArray) + * + * In any case the read position is restored at the end of the call (hence a fetchRow will get the same row + * wether this method is called before it or not). + */ +- (id) fetch2DResultAsType:(MCPReturnType)type; +{ + id theTable, theVect; + MYSQL_ROW_OFFSET thePosition; + unsigned int i; + + if (mResult == NULL) { + // If there is no results, returns nil. + return nil; + } + + thePosition = mysql_row_tell(mResult); + + [self dataSeek:0]; + + switch (type) + { + case MCPTypeArray : + theTable = [NSMutableArray arrayWithCapacity:[self numOfRows]]; + + while (theVect = [self fetchRowAsArray]) + { + [theTable addObject:theVect]; + } + + theTable = [NSArray arrayWithArray:theTable]; + break; + case MCPTypeDictionary : + theTable = [NSMutableArray arrayWithCapacity:[self numOfRows]]; + + while (theVect = [self fetchRowAsDictionary]) + { + [theTable addObject:theVect]; + } + + theTable = [NSArray arrayWithArray:theTable]; + break; + case MCPTypeFlippedArray : + theTable = [NSMutableArray arrayWithCapacity:mNumOfFields]; + + for (i=0; i<mNumOfFields; i++) + { + [theTable addObject:[self fetchColAtIndex:i]]; + } + + theTable = [NSArray arrayWithArray:theTable]; + break; + case MCPTypeFlippedDictionary : + theTable = [NSMutableDictionary dictionaryWithCapacity:mNumOfFields]; + + if (mNames == nil) { + [self fetchFieldNames]; + } + + for (i=0; i<mNumOfFields; i++) + { + [theTable setObject:[self fetchColAtIndex:i] forKey:[mNames objectAtIndex:i]]; + } + + theTable = [NSDictionary dictionaryWithDictionary:theTable]; + break; + default : + NSLog (@"Unknown MCPReturnType : %d; return nil\n", (int)type); + theTable = nil; + break; + } + + mysql_row_seek(mResult, thePosition); + + return theTable; +} + +@end |