diff options
Diffstat (limited to 'Frameworks/PostgresKit/Source/PGPostgresConnectionQueryExecution.m')
-rw-r--r-- | Frameworks/PostgresKit/Source/PGPostgresConnectionQueryExecution.m | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/Frameworks/PostgresKit/Source/PGPostgresConnectionQueryExecution.m b/Frameworks/PostgresKit/Source/PGPostgresConnectionQueryExecution.m new file mode 100644 index 00000000..4dec74a6 --- /dev/null +++ b/Frameworks/PostgresKit/Source/PGPostgresConnectionQueryExecution.m @@ -0,0 +1,314 @@ +// +// $Id: PGPostgresConnectionQueryExecution.m 3841 2012-09-10 08:52:00Z stuart02 $ +// +// PGPostgresConnectionQueryExecution.h +// 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 "PGPostgresConnectionQueryExecution.h" +#import "PGPostgresKitPrivateAPI.h" +#import "PGPostgresConnectionTypeHandling.h" +#import "PGPostgresConnectionDelegate.h" +#import "PGPostgresTypeHandlerProtocol.h" +#import "PGPostgresConnection.h" +#import "PGPostgresException.h" +#import "PGPostgresResult.h" +#import "PGPostgresStatement.h" +#import "PGPostgresError.h" + +// Constants +static int PGPostgresResultsAsBinary = 1; + +// Internal query structure +typedef struct +{ + int paramNum; + const void **paramValues; + PGPostgresOid* paramTypes; + int *paramLengths; + int *paramFormats; +} +PGQueryParamData; + +@interface PGPostgresConnection () + +- (PGPostgresResult *)_execute:(NSObject *)query values:(NSArray *)values; +- (BOOL)_queryDidError:(PGresult *)result; +- (PGQueryParamData *)_createParameterDataStructureWithCount:(int)paramNum; +- (void)_destroyParamDataStructure:(PGQueryParamData *)paramData; + +@end + +@implementation PGPostgresConnection (PGPostgresConnectionQueryExecution) + +#pragma mark - +#pragma mark Synchronous Interface + +- (PGPostgresResult *)execute:(NSString *)query +{ + return [self _execute:query values:nil]; +} + +- (PGPostgresResult *)execute:(NSString *)query value:(NSObject *)value +{ + return [self _execute:query values:[NSArray arrayWithObject:value]]; +} + +- (PGPostgresResult *)executePrepared:(PGPostgresStatement *)statement value:(NSObject *)value +{ + return [self _execute:statement values:[NSArray arrayWithObject:value]]; +} + +- (PGPostgresResult *)executePrepared:(PGPostgresStatement *)statement +{ + return [self _execute:statement values:nil]; +} + +- (PGPostgresResult *)executePrepared:(PGPostgresStatement *)statement values:(NSArray *)values +{ + return [self _execute:statement values:values]; +} + +- (PGPostgresResult *)execute:(NSString *)query values:(NSArray *)values +{ + return [self _execute:query values:values]; +} + +- (PGPostgresResult *)executeWithFormat:(NSString *)query, ... +{ + va_list argumentList; + va_start(argumentList, query); + + NSMutableString *string = [[NSMutableString alloc] init]; + + CFStringAppendFormatAndArguments((CFMutableStringRef)string, (CFDictionaryRef)nil, (CFStringRef)query, argumentList); + + va_end(argumentList); + + PGPostgresResult *result = [self _execute:string values:nil]; + + [string release]; + + return result; +} + +#pragma mark - +#pragma mark Asynchronous Interface + +#pragma mark - +#pragma mark Private API + +- (PGPostgresResult *)_execute:(NSObject *)query values:(NSArray *)values +{ + _lastQueryWasCancelled = NO; + + if (![self isConnected] || !query || ![query isKindOfClass:[NSString class]] || [query isKindOfClass:[PGPostgresStatement class]]) return nil; + + // Notify the delegate + if (_delegate && _delegateSupportsWillExecute) { + [_delegate connection:self willExecute:query withValues:values]; + } + + PGQueryParamData *paramData = [self _createParameterDataStructureWithCount:values ? (int)[values count] : 0]; + + if (!paramData) return nil; + + // Fill the data structures + for (int i = 0; i < paramData->paramNum; i++) + { + id nativeObject = [values objectAtIndex:i]; + + // Deterime if bound value is an NSNull + if ([nativeObject isKindOfClass:[NSNull class]]) { + paramData->paramValues[i] = NULL; + paramData->paramTypes[i] = 0; + paramData->paramLengths[i] = 0; + paramData->paramFormats[i] = 0; + + continue; + } + + // Obtain correct handler for this class + id <PGPostgresTypeHandlerProtocol> typeHandler = [self typeHandlerForClass:[nativeObject class]]; + + if (!typeHandler) { + [self _destroyParamDataStructure:paramData]; + + // TODO: get rid of exceptions + [PGPostgresException raise:PGPostgresConnectionErrorDomain reason:[NSString stringWithFormat:@"Parameter $%u unsupported class %@", (i + 1), NSStringFromClass([nativeObject class])]]; + return nil; + } + + NSData *data = nil; // Sending parameters as binary is not implemented yet + PGPostgresOid type = 0; + + if (!data) { + [self _destroyParamDataStructure:paramData]; + + // TODO: get rid of exceptions + [PGPostgresException raise:PGPostgresConnectionErrorDomain reason:[NSString stringWithFormat:@"Parameter $%u cannot be converted into a bound value", (i + 1)]]; + return nil; + } + + // Check length of data + if ([data length] > INT_MAX) { + [self _destroyParamDataStructure:paramData]; + + // TODO: get rid of exceptions + [PGPostgresException raise:PGPostgresConnectionErrorDomain reason:[NSString stringWithFormat:@"Bound value $%u exceeds maximum size", (i + 1)]]; + return nil; + } + + // Assign data + paramData->paramTypes[i] = type; + + // NOTE: if data length is zero, we encode as text instead, as NSData returns 0 for + // empty data, and it gets encoded as a NULL. + if ([data length] == 0) { + paramData->paramValues[i] = ""; + paramData->paramFormats[i] = 0; + paramData->paramLengths[i] = 0; + } + else { + // Send as binary data + paramData->paramValues[i] = [data bytes]; + paramData->paramLengths[i] = (int)[data length]; + paramData->paramFormats[i] = 1; + } + } + + // Execute the command - return data in binary + PGresult *result = nil; + + if ([query isKindOfClass:[NSString class]]) { + + result = PQexecParams(_connection, + [(NSString *)query UTF8String], + paramData->paramNum, + paramData->paramTypes, + (const char **)paramData->paramValues, + (const int *)paramData->paramLengths, + (const int *)paramData->paramFormats, + PGPostgresResultsAsBinary); + } + else if ([query isKindOfClass:[PGPostgresStatement class]]) { + PGPostgresStatement *statement = (PGPostgresStatement *)query; + + // Statement has not been prepared yet, so prepare it with the given parameter types + if (![statement name]) { + BOOL prepareResult = [self _prepare:statement num:paramData->paramNum types:paramData->paramTypes]; + + if (!prepareResult || ![statement name]) return nil; + } + + result = PQexecPrepared(_connection, + [statement UTF8Name], + paramData->paramNum, + (const char **)paramData->paramValues, + (const int *)paramData->paramLengths, + (const int *)paramData->paramFormats, + PGPostgresResultsAsBinary); + } + + [self _destroyParamDataStructure:paramData]; + + if (!result || [self _queryDidError:result]) return nil; + + return [[[PGPostgresResult alloc] initWithResult:result connection:self] autorelease]; +} + +/** + * Determines whether or not the supplied result indicates an error occurred. + * + * @param result The result to examine. + * + * @return A BOOL indicating if an error occurred. + */ +- (BOOL)_queryDidError:(PGresult *)result +{ + ExecStatusType status = PQresultStatus(result); + + if (status == PGRES_BAD_RESPONSE || status == PGRES_FATAL_ERROR) { + if (_lastError) [_lastError release], _lastError = nil; + + _lastError = [[PGPostgresError alloc] initWithResult:result]; + + PQclear(result); + + return YES; + } + + return NO; +} + +/** + * Creates the internal query parameter data structure. + * + * @note This method will throw an exception if it can't allocated the required memory. + * + * @param paramNum The number of parameters the structure should accommodate. + * + * @return The data structure or nil if an exception occurred. + */ +- (PGQueryParamData *)_createParameterDataStructureWithCount:(int)paramNum +{ + PGQueryParamData *paramData = malloc(sizeof(PGQueryParamData)); + + paramData->paramNum = paramNum; + paramData->paramValues = NULL; + paramData->paramTypes = NULL; + paramData->paramLengths = NULL; + paramData->paramFormats = NULL; + + if (paramData->paramNum) { + paramData->paramValues = malloc(sizeof(void *) * paramData->paramNum); + paramData->paramTypes = malloc(sizeof(PGPostgresOid) * paramData->paramNum); + paramData->paramLengths = malloc(sizeof(int) * paramData->paramNum); + paramData->paramFormats = malloc(sizeof(int) * paramData->paramNum); + + if (!paramData->paramValues || !paramData->paramLengths || !paramData->paramFormats) { + [self _destroyParamDataStructure:paramData]; + + // Probably justifies throwing an exception if we can't allocate any memory! + [PGPostgresException raise:PGPostgresConnectionErrorDomain reason:@"Memory allocation error"]; + + return nil; + } + } + + return paramData; +} + +/** + * Frees the memory associated with the supplied parameter data structure. + * + * @param paramData The parameter data to destroy. + */ +- (void)_destroyParamDataStructure:(PGQueryParamData *)paramData +{ + if (!paramData) return; + + if (paramData->paramValues) free(paramData->paramValues); + if (paramData->paramTypes) free(paramData->paramTypes); + if (paramData->paramLengths) free(paramData->paramLengths); + if (paramData->paramFormats) free(paramData->paramFormats); + + free(paramData); +} + +@end |