aboutsummaryrefslogtreecommitdiffstats
path: root/Frameworks/PostgresKit/Source/FLXPostgresConnection.m
diff options
context:
space:
mode:
authorstuconnolly <stuart02@gmail.com>2012-09-03 10:22:17 +0000
committerstuconnolly <stuart02@gmail.com>2012-09-03 10:22:17 +0000
commite124a1d0fb576c311a6ac601b1c08e6ce51bcd30 (patch)
tree5a3350fc75bcad1f5c8d07f8a66ea53b0a07805f /Frameworks/PostgresKit/Source/FLXPostgresConnection.m
parentd4e8474b3437771fd6891def8324981d82186a88 (diff)
downloadsequelpro-e124a1d0fb576c311a6ac601b1c08e6ce51bcd30.tar.gz
sequelpro-e124a1d0fb576c311a6ac601b1c08e6ce51bcd30.tar.bz2
sequelpro-e124a1d0fb576c311a6ac601b1c08e6ce51bcd30.zip
Initial commit of PostgresKit, our new Postgres framework as a start towards adding PostgreSQL support to Sequel Pro.
Note, that the framerwork is by no means feature complete and in it's current state has quite a few limitations: - No support for Postgres' asynchronous query API - Only supports the very basic data types (char/text and numerics) - No support (outide of libpq) for re-establishing dropped connections Current feature support includes: - Basic connection handling - Query execution - Prepared statement execution - Encoding support similar to SPMySQL's
Diffstat (limited to 'Frameworks/PostgresKit/Source/FLXPostgresConnection.m')
-rw-r--r--Frameworks/PostgresKit/Source/FLXPostgresConnection.m460
1 files changed, 460 insertions, 0 deletions
diff --git a/Frameworks/PostgresKit/Source/FLXPostgresConnection.m b/Frameworks/PostgresKit/Source/FLXPostgresConnection.m
new file mode 100644
index 00000000..3bd24bb2
--- /dev/null
+++ b/Frameworks/PostgresKit/Source/FLXPostgresConnection.m
@@ -0,0 +1,460 @@
+//
+// $Id$
+//
+// FLXPostgresConnection.m
+// PostgresKit
+//
+// Copyright (c) 2008-2009 David Thorpe, djt@mutablelogic.com
+//
+// Forked by the Sequel Pro Team on July 22, 2012.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#import "FLXPostgresConnection.h"
+#import "FLXPostgresConnectionParameters.h"
+#import "FLXPostgresConnectionTypeHandling.h"
+#import "FLXPostgresConnectionPrivateAPI.h"
+#import "FLXPostgresTypeNumberHandler.h"
+#import "FLXPostgresTypeStringHandler.h"
+#import "FLXPostgresException.h"
+#import "FLXPostgresStatement.h"
+#import "FLXPostgresResult.h"
+
+#import "pthread.h"
+
+// Connection default constants
+static NSUInteger FLXPostgresConnectionDefaultTimeout = 30;
+static NSUInteger FLXPostgresConnectionDefaultServerPort = 5432;
+static NSUInteger FLXPostgresConnectionDefaultKeepAlive = 60;
+
+// libpq connection parameters
+static const char *FLXPostgresApplicationName = "PostgresKit";
+static const char *FLXPostgresApplicationParam = "application_name";
+static const char *FLXPostgresUserParam = "user";
+static const char *FLXPostgresHostParam = "host";
+static const char *FLXPostgresPasswordParam = "password";
+static const char *FLXPostgresPortParam = "port";
+static const char *FLXPostgresDatabaseParam = "dbname";
+static const char *FLXPostgresConnectionTimeoutParam = "conect_timeout";
+static const char *FLXPostgresClientEncodingParam = "client_encoding";
+static const char *FLXPostgresKeepAliveParam = "keepalives";
+static const char *FLXPostgresKeepAliveIntervalParam = "keepalives_interval";
+
+@interface FLXPostgresConnection ()
+
+- (void)_pollConnection;
+- (void)_loadDatabaseParameters;
+- (void)_createConnectionParameters;
+
+// libpq callback
+static void _FLXPostgresConnectionNoticeProcessor(void *arg, const char *message);
+
+@end
+
+@implementation FLXPostgresConnection
+
+@synthesize port = _port;
+@synthesize host = _host;
+@synthesize user = _user;
+@synthesize database = _database;
+@synthesize password = _password;
+@synthesize useSocket = _useSocket;
+@synthesize socketPath = _socketPath;
+@synthesize delegate = _delegate;
+@synthesize timeout = _timeout;
+@synthesize useKeepAlive = _useKeepAlive;
+@synthesize keepAliveInterval = _keepAliveInterval;
+@synthesize lastQueryWasCancelled = _lastQueryWasCancelled;
+@synthesize lastErrorMessage = _lastErrorMessage;
+@synthesize encoding = _encoding;
+@synthesize stringEncoding = _stringEncoding;
+@synthesize parameters = _parameters;
+
+#pragma mark -
+#pragma mark Initialisation
+
+- (id)init
+{
+ return [self initWithDelegate:nil];
+}
+
+/**
+ * Initialise a new connection with the supplied delegate.
+ *
+ * @param delegate The delegate this connection should use.
+ *
+ * @return The new connection instance.
+ */
+- (id)initWithDelegate:(NSObject <FLXPostgresConnectionDelegate> *)delegate
+{
+ if ((self = [super init])) {
+
+ _delegate = delegate;
+
+ _port = FLXPostgresConnectionDefaultServerPort;
+ _timeout = FLXPostgresConnectionDefaultTimeout;
+
+ _useKeepAlive = YES;
+ _keepAliveInterval = FLXPostgresConnectionDefaultKeepAlive;
+
+ _connection = nil;
+ _lastErrorMessage = nil;
+ _lastQueryWasCancelled = NO;
+
+ _stringEncoding = FLXPostgresConnectionDefaultStringEncoding;
+ _encoding = [NSString stringWithString:FLXPostgresConnectionDefaultEncoding];
+
+ _delegateSupportsWillExecute = [_delegate respondsToSelector:@selector(connection:willExecute:values:)];
+
+ _typeMap = [[NSMutableDictionary alloc] init];
+
+ [self registerTypeHandlers];
+ }
+
+ return self;
+}
+
+#pragma mark -
+#pragma mark Accessors
+
+- (PGconn *)postgresConnection
+{
+ return _connection;
+}
+
+#pragma mark -
+#pragma mark Connection Handling
+
+/**
+ * Does this connection have an underlying connection established with the server.
+ *
+ * @return A BOOL indicating the result of the query.
+ */
+- (BOOL)isConnected
+{
+ if (!_connection) return NO;
+
+ return PQstatus(_connection) == CONNECTION_OK;
+}
+
+/**
+ * Attempts to disconnect the underlying connection with the server.
+ */
+- (void)disconnect
+{
+ if (!_connection) return;
+
+ [self cancelCurrentQuery:nil];
+
+ PQfinish(_connection);
+
+ _connection = nil;
+
+ if (_delegate && [_delegate respondsToSelector:@selector(connectionDisconnected:)]) {
+ [_delegate connectionDisconnected:self];
+ }
+}
+
+/**
+ * Initiates the underlying connection to the server asynchronously.
+ *
+ * Note, that if no user, host or database is set when connect is called, then libpq's defaults are used.
+ * For no host, this means a socket connection to /tmp is attempted.
+ *
+ * @return A BOOL indicating the success of requesting the connection. Note, that this does not indicate
+ * that a successful connection has been made, only that it has successfullly been requested.
+ */
+- (BOOL)connect
+{
+ if ([self isConnected]) {
+ [FLXPostgresException raise:FLXPostgresConnectionErrorDomain reason:@"Attempt to initiate a connection that is already active"];
+
+ return NO;
+ }
+
+ [self _createConnectionParameters];
+
+ // Perform the connection
+ _connection = PQconnectStartParams(_connectionParamNames, _connectionParamValues, 0);
+
+ if (!_connection || PQstatus(_connection) == CONNECTION_BAD) {
+
+ // TODO: implement reconnection attempt
+ return NO;
+ }
+
+ [self performSelectorInBackground:@selector(_pollConnection) withObject:nil];
+
+ return YES;
+}
+
+/**
+ * Attempts the reset the underlying connection.
+ *
+ * @note A return value of NO means that the connection is not currently
+ * connected to be reset and YES means the reset request was successful,
+ * not that the connection re-establishment has succeeded. Use -isConnected
+ * to check this.
+ *
+ * @return A BOOL indicating the success of the call.
+ */
+- (BOOL)reset
+{
+ if (![self isConnected]) return NO;
+
+ PQreset(_connection);
+
+ return YES;
+}
+
+/**
+ * Returns the PostgreSQL client library (libpq) version being used.
+ *
+ * @return The library version (e.g. version 9.1 is 90100).
+ */
+- (NSUInteger)clientVersion
+{
+ return PQlibVersion();
+}
+
+/**
+ * Returns the version of the server we're connected to.
+ *
+ * @return The server version (e.g. version 9.1 is 90100). Zero is returned if there's no connection.
+ */
+- (NSUInteger)serverVersion
+{
+ if (![self isConnected]) return 0;
+
+ return PQserverVersion(_connection);
+}
+
+/**
+ * Returns the ID of the process handling this connection on the remote host.
+ *
+ * @return The process ID or -1 if no connection is available.
+ */
+- (NSUInteger)serverProcessId
+{
+ if (![self isConnected]) return -1;
+
+ return PQbackendPID(_connection);
+}
+
+/**
+ * Attempts to cancel the query currently executing on this connection.
+ *
+ * @param error Populated if query was unabled to be cancelled.
+ *
+ * @return A BOOL indicating the success of the request
+ */
+- (BOOL)cancelCurrentQuery:(NSError **)error
+{
+ if (![self isConnected]) return NO;
+
+ PGcancel *cancel = PQgetCancel(_connection);
+
+ if (!cancel) return NO;
+
+ char errorBuf[256];
+
+ int result = PQcancel(cancel, errorBuf, 256);
+
+ PQfreeCancel(cancel);
+
+ if (!result) {
+ if (error != NULL) {
+ *error = [NSError errorWithDomain:FLXPostgresConnectionErrorDomain
+ code:0
+ userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithUTF8String:errorBuf] forKey:NSLocalizedDescriptionKey]];
+ }
+
+ return NO;
+ }
+
+ _lastQueryWasCancelled = YES;
+
+ return YES;
+}
+
+#pragma mark -
+#pragma mark Private API
+
+/**
+ * Polls the connection that was previously requested via -connect and waits for meaninful status.
+ *
+ * @note This method should be called on a background thread as it will block waiting for the connection.
+ */
+- (void)_pollConnection
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ BOOL failed = NO;
+ BOOL connected = NO;
+
+ while (!connected && !failed)
+ {
+ switch (PQconnectPoll(_connection))
+ {
+ case PGRES_POLLING_READING:
+ case PGRES_POLLING_WRITING:
+ case PGRES_POLLING_ACTIVE: // Obsolete so we don't really care about it
+ break;
+ case PGRES_POLLING_OK:
+ connected = YES;
+ break;
+ case PGRES_POLLING_FAILED:
+ failed = YES;
+ break;
+ }
+ }
+
+ if (connected) {
+ PQsetNoticeProcessor(_connection, _FLXPostgresConnectionNoticeProcessor, self);
+
+ [self _loadDatabaseParameters];
+
+ if (_delegate && [_delegate respondsToSelector:@selector(connectionEstablished:)]) {
+ [_delegate performSelectorOnMainThread:@selector(connectionEstablished:) withObject:self waitUntilDone:NO];
+ }
+ }
+
+ [pool release];
+}
+
+/**
+ * Loads the database parameters.
+ */
+- (void)_loadDatabaseParameters
+{
+ if (_parameters) [_parameters release];
+
+ _parameters = [[FLXPostgresConnectionParameters alloc] initWithConnection:self];
+
+ BOOL success = [_parameters loadParameters];
+
+ if (!success) {
+ NSLog(@"PostgresKit: Warning: Failed to load database parameters.");
+ }
+}
+
+/**
+ * libpq notice processor function. Simply passes the message onto the connection delegate.
+ *
+ * @param arg The calling connection.
+ * @param message The message that was sent.
+ */
+static void _FLXPostgresConnectionNoticeProcessor(void *arg, const char *message)
+{
+ FLXPostgresConnection *connection = (FLXPostgresConnection *)arg;
+
+ if ([connection isKindOfClass:[FLXPostgresConnection class]]) {
+
+ if ([connection delegate] && [[connection delegate] respondsToSelector:@selector(connection:notice:)]) {
+ [[connection delegate] connection:connection notice:[NSString stringWithUTF8String:message]];
+ }
+ }
+}
+
+/**
+ * Creates the parameter arrays required to establish a connection.
+ */
+- (void)_createConnectionParameters
+{
+ BOOL hasUser = NO;
+ BOOL hasHost = NO;
+ BOOL hasPassword = NO;
+ BOOL hasDatabase = NO;
+
+ if (_connectionParamNames) free(_connectionParamNames);
+ if (_connectionParamValues) free(_connectionParamValues);
+
+ int paramCount = 6;
+
+ if (_user && [_user length]) paramCount++, hasUser = YES;
+ if (_host && [_host length]) paramCount++, hasHost = YES;
+ if (_password && [_password length]) paramCount++, hasPassword = YES;
+ if (_database && [_database length]) paramCount++, hasDatabase = YES;
+
+ _connectionParamNames = malloc(paramCount * sizeof(*_connectionParamNames));
+ _connectionParamValues = malloc(paramCount * sizeof(*_connectionParamValues));
+
+ _connectionParamNames[0] = FLXPostgresApplicationParam;
+ _connectionParamValues[0] = FLXPostgresApplicationName;
+
+ _connectionParamNames[1] = FLXPostgresPortParam;
+ _connectionParamValues[1] = [[[NSNumber numberWithUnsignedInteger:_port] stringValue] UTF8String];
+
+ _connectionParamNames[2] = FLXPostgresConnectionTimeoutParam;
+ _connectionParamValues[2] = [[[NSNumber numberWithUnsignedInteger:_timeout] stringValue] UTF8String];
+
+ _connectionParamNames[3] = FLXPostgresClientEncodingParam;
+ _connectionParamValues[3] = [_encoding UTF8String];
+
+ _connectionParamNames[4] = FLXPostgresKeepAliveParam;
+ _connectionParamValues[4] = _useKeepAlive ? "1" : "0";
+
+ _connectionParamNames[5] = FLXPostgresKeepAliveIntervalParam;
+ _connectionParamValues[5] = [[[NSNumber numberWithUnsignedInteger:_keepAliveInterval] stringValue] UTF8String];
+
+ NSUInteger i = 6;
+
+ if (hasUser) {
+ _connectionParamNames[i] = FLXPostgresUserParam;
+ _connectionParamValues[i] = [_user UTF8String];
+
+ i++;
+ }
+
+ if (hasHost) {
+ _connectionParamNames[i] = FLXPostgresHostParam;
+ _connectionParamValues[i] = [_host UTF8String];
+
+ i++;
+ }
+
+ if (hasPassword) {
+ _connectionParamNames[i] = FLXPostgresPasswordParam;
+ _connectionParamValues[i] = [_password UTF8String];
+
+ i++;
+ }
+
+ if (hasDatabase) {
+ _connectionParamNames[i] = FLXPostgresDatabaseParam;
+ _connectionParamValues[i] = [_database UTF8String];
+ }
+}
+
+#pragma mark -
+
+- (void)dealloc
+{
+ [_typeMap release];
+
+ [self disconnect];
+
+ [self setHost:nil];
+ [self setUser:nil];
+ [self setDatabase:nil];
+
+ if (_connectionParamNames) free(_connectionParamNames);
+ if (_connectionParamValues) free(_connectionParamValues);
+
+ if (_parameters) [_parameters release], _parameters = nil;
+ if (_lastErrorMessage) [_lastErrorMessage release], _lastErrorMessage = nil;
+
+ [super dealloc];
+}
+
+@end