sope/sope-gdl1/PostgreSQL/PostgreSQL72Channel.m

790 lines
22 KiB
Objective-C

/*
PostgreSQL72Channel.m
Copyright (C) 1999 MDlink online service center GmbH and Helge Hess
Copyright (C) 2000-2008 SKYRIX Software AG and Helge Hess
Author: Helge Hess (helge.hess@opengroupware.org)
This file is part of the PostgreSQL72 Adaptor Library
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; see the file COPYING.LIB.
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <ctype.h>
#include <string.h>
#include <strings.h>
#import "common.h"
#import "PostgreSQL72Channel.h"
#import "PostgreSQL72Adaptor.h"
#import "PostgreSQL72Exception.h"
#import "NSString+PostgreSQL72.h"
#import "PostgreSQL72Values.h"
#import "EOAttribute+PostgreSQL72.h"
#include "PGConnection.h"
#include "pgconfig.h"
#import <NGExtensions/NSObject+Logs.h>
#ifndef MIN
# define MIN(x, y) ((x > y) ? y : x)
#endif
// TODO: what does this do?
#define MAX_CHAR_BUF 16384
@interface PostgreSQL72Channel(Privates)
- (void)_resetEvaluationState;
@end
@implementation PostgreSQL72Channel
#if NG_SET_CLIENT_ENCODING
static NSString *PGClientEncoding = @"UTF8";
#endif
static int MaxOpenConnectionCount = -1;
static BOOL debugOn = NO;
static NSNull *null = nil;
static NSNumber *yesObj = nil;
static Class StringClass = Nil;
static Class MDictClass = Nil;
+ (void)initialize {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
if (null == nil) null = [[NSNull null] retain];
if (yesObj == nil) yesObj = [[NSNumber numberWithBool:YES] retain];
StringClass = [NSString class];
MDictClass = [NSMutableDictionary class];
MaxOpenConnectionCount = [ud integerForKey:@"PGMaxOpenConnectionCount"];
if (MaxOpenConnectionCount < 2)
MaxOpenConnectionCount = 50;
debugOn = [ud boolForKey:@"PGDebugEnabled"];
}
- (id)initWithAdaptorContext:(EOAdaptorContext*)_adaptorContext {
if ((self = [super initWithAdaptorContext:_adaptorContext])) {
[self setDebugEnabled:debugOn];
self->_attributesForTableName = [[MDictClass alloc] initWithCapacity:16];
self->_primaryKeysNamesForTableName =
[[MDictClass alloc] initWithCapacity:16];
}
return self;
}
/* collection */
- (void)dealloc {
[self _resetEvaluationState];
if ([self isOpen])
[self closeChannel];
[self->_attributesForTableName release];
[self->_primaryKeysNamesForTableName release];
[super dealloc];
}
/* NSCopying methods */
- (id)copyWithZone:(NSZone *)zone {
return [self retain];
}
/* debugging */
- (void)setDebugEnabled:(BOOL)_flag {
self->isDebuggingEnabled = _flag;
}
- (BOOL)isDebugEnabled {
return self->isDebuggingEnabled;
}
- (void)receivedMessage:(NSString *)_message {
[self logWithFormat: @"message: %@", _message];
}
static void _pgMessageProcessor(void *_channel, const char *_msg)
__attribute__((unused));
static void _pgMessageProcessor(void *_channel, const char *_msg) {
[(id)_channel receivedMessage:
_msg ? [StringClass stringWithUTF8String:_msg] : nil];
}
/* cleanup */
- (void)_resetResults {
[self->resultSet clear];
[self->resultSet release];
self->resultSet = nil;
}
/* open/close */
static int openConnectionCount = 0;
- (BOOL)isOpen {
return [self->connection isValid];
}
- (BOOL)openChannel {
PostgreSQL72Adaptor *adaptor;
if ([self->connection isValid]) {
NSLog(@"%s: Connection already open !!!", __PRETTY_FUNCTION__);
return NO;
}
adaptor = (PostgreSQL72Adaptor *)[adaptorContext adaptor];
if (![super openChannel])
return NO;
#if HEAVY_DEBUG
NSLog(@"+++++++++ %s: openConnectionCount %d", __PRETTY_FUNCTION__,
openConnectionCount);
#endif
if (openConnectionCount > MaxOpenConnectionCount) {
[PostgreSQL72CouldNotOpenChannelException raise:@"NoMoreConnections"
format:
@"cannot open a additional connection !"];
return NO;
}
self->connection =
[[PGConnection alloc] initWithHostName:[adaptor serverName]
port:[(id)[adaptor port] stringValue]
options:[adaptor options]
tty:[adaptor tty] database:[adaptor databaseName]
login:[adaptor loginName]
password:[adaptor loginPassword]];
if (![self->connection isValid]) {
// could not login ..
NSLog(@"WARNING: could not open pgsql channel to %@@%@ host %@:%@",
[adaptor loginName],
[adaptor databaseName], [adaptor serverName], [adaptor port]);
return NO;
}
/* PQstatus */
if (![self->connection isConnectionOK]) {
NSLog(@"could not open channel to %@@%@",
[adaptor databaseName], [adaptor serverName]);
[self->connection finish];
[self->connection release];
self->connection = nil;
return NO;
}
/* set message callback */
[self->connection setNoticeProcessor:_pgMessageProcessor context:self];
/* set client encoding */
#if NG_SET_CLIENT_ENCODING
if (![self->connection setClientEncoding:PGClientEncoding]) {
NSLog(@"WARNING: could not set client encoding to: '%@'",
PGClientEncoding);
}
#endif
/* log */
if (isDebuggingEnabled)
NSLog(@"PostgreSQL72 connection established: %@", self->connection);
#if HEAVY_DEBUG
NSLog(@"---------- %s: %@ opens channel count[%d]", __PRETTY_FUNCTION__,
self, openConnectionCount);
#endif
openConnectionCount++;
if (isDebuggingEnabled) {
NSLog(@"PostgreSQL72 channel 0x%p opened (connection=%@)",
self, self->connection);
}
return YES;
}
- (void)primaryCloseChannel {
self->tupleCount = 0;
self->fieldCount = 0;
self->containsBinaryData = NO;
if (self->fieldInfo) {
free(self->fieldInfo);
self->fieldInfo = NULL;
}
[self _resetResults];
[self->cmdStatus release]; self->cmdStatus = nil;
[self->cmdTuples release]; self->cmdTuples = nil;
if (self->connection) {
[self->connection finish];
#if HEAVY_DEBUG
NSLog(@"---------- %s: %@ close channel count[%d]", __PRETTY_FUNCTION__,
self, openConnectionCount);
#endif
openConnectionCount--;
if (isDebuggingEnabled) {
fprintf(stderr,
"PostgreSQL72 connection dropped 0x%p (channel=0x%p)\n",
self->connection, self);
}
[self->connection release];
self->connection = nil;
}
}
- (void)closeChannel {
[super closeChannel];
[self primaryCloseChannel];
}
/* fetching rows */
- (void)cancelFetch {
if (![self isOpen]) {
[PostgreSQL72Exception raise:@"ChannelNotOpenException"
format:@"No fetch in progress, connection is not open"
@" (channel=%@)", self];
}
#if HEAVY_DEBUG
NSLog(@"canceling fetch (%i tuples remaining).",
(self->tupleCount - self->currentTuple));
#endif
self->tupleCount = 0;
self->currentTuple = 0;
self->fieldCount = 0;
self->containsBinaryData = NO;
if (self->fieldInfo) {
free(self->fieldInfo);
self->fieldInfo = NULL;
}
[self _resetResults];
[self->cmdStatus release]; self->cmdStatus = nil;
[self->cmdTuples release]; self->cmdTuples = nil;
/* new caches which require a constant _attributes argument */
if (self->fieldIndices) free(self->fieldIndices); self->fieldIndices = NULL;
if (self->fieldKeys) free(self->fieldKeys); self->fieldKeys = NULL;
if (self->fieldValues) free(self->fieldValues); self->fieldValues = NULL;
[super cancelFetch];
}
- (NSArray *)describeResults:(BOOL)_beautifyNames {
int cnt;
NSMutableArray *result;
NSMutableDictionary *usedNames;
if (![self isFetchInProgress]) {
[PostgreSQL72Exception raise:@"NoFetchInProgress"
format:@"No fetch in progress (channel=%@)", self];
}
result = [[NSMutableArray alloc] initWithCapacity:self->fieldCount];
usedNames = [[MDictClass alloc] initWithCapacity:self->fieldCount];
for (cnt = 0; cnt < self->fieldCount; cnt++) {
EOAttribute *attribute = nil;
NSString *columnName;
NSString *attrName;
columnName =
[[StringClass alloc] initWithUTF8String:self->fieldInfo[cnt].name];
attrName = _beautifyNames
? [columnName _pgModelMakeInstanceVarName]
: columnName;
if ([[usedNames objectForKey:attrName] boolValue]) {
// TODO: move name generation code to different method!
int cnt2 = 0;
char buf[64];
NSString *newAttrName = nil;
for (cnt2 = 2; cnt2 < 100; cnt2++) {
NSString *s;
sprintf(buf, "%i", cnt2);
s = [[StringClass alloc] initWithUTF8String:buf];
newAttrName = [attrName stringByAppendingString:s];
[s release]; s= nil;
if (![[usedNames objectForKey:newAttrName] boolValue]) {
attrName = newAttrName;
break;
}
}
}
[usedNames setObject:yesObj forKey:attrName];
attribute = [[EOAttribute alloc] init];
[attribute setName:attrName];
[attribute setColumnName:columnName];
//NSLog(@"column: %@", columnName);
[attribute loadValueClassAndTypeUsingPostgreSQLType:
self->fieldInfo[cnt].type
size:self->fieldInfo[cnt].size
modification:self->fieldInfo[cnt].modification
binary:self->containsBinaryData];
[result addObject:attribute];
[columnName release]; columnName = nil;
[attribute release]; attribute = nil;
}
[usedNames release];
usedNames = nil;
return [result autorelease];
}
- (void)_fillFieldNamesForAttributes:(NSArray *)_attributes
count:(unsigned)attrCount
{
// Note: this optimization requires that the "_attributes" array does
// note change between invocations!
// TODO: should add a sanity check for that!
NSMutableArray *fieldNames;
unsigned nFields, i;
unsigned cnt;
if (self->fieldIndices)
return;
self->fieldIndices = calloc(attrCount + 2, sizeof(int));
// TODO: we could probably cache the field-name array for much more speed !
fieldNames = [[NSMutableArray alloc] initWithCapacity:32];
nFields = [self->resultSet fieldCount];
for (i = 0; i < nFields; i++)
[fieldNames addObject:[self->resultSet fieldNameAtIndex:i]];
for (cnt = 0; cnt < attrCount; cnt++) {
EOAttribute *attribute;
#ifndef GDL_USE_PQFNUMBER_INDEX
NSUInteger res;
#endif
attribute = [_attributes objectAtIndex:cnt];
#if GDL_USE_PQFNUMBER_INDEX
self->fieldIndices[cnt] =
[self->resultSet indexOfFieldNamed:[attribute columnName]];
if (self->fieldIndices[cnt] == -1) {
[PostgreSQL72Exception raiseWithFormat:
@"attribute %@ not covered by query",
attribute];
}
#else
res = [fieldNames indexOfObject:[attribute columnName]];
if (res == NSNotFound) {
[PostgreSQL72Exception raiseWithFormat:
@"attribute %@ not covered by query",
attribute];
}
self->fieldIndices[cnt] = (int)res;
#endif
[fieldNames replaceObjectAtIndex:self->fieldIndices[cnt] withObject:null];
}
[fieldNames release]; fieldNames = nil;
}
- (NSMutableDictionary *)primaryFetchAttributes:(NSArray *)_attributes
withZone:(NSZone *)_zone
{
NSMutableDictionary *row;
unsigned attrCount;
int *indices;
unsigned cnt, fieldDictCount;
if (self->currentTuple == self->tupleCount) {
if (self->resultSet != nil) [self cancelFetch];
return nil;
}
attrCount = [_attributes count];
[self _fillFieldNamesForAttributes:_attributes count:attrCount];
indices = self->fieldIndices;
if (self->fieldKeys == NULL)
self->fieldKeys = calloc(attrCount + 1, sizeof(NSString *));
if (self->fieldValues == NULL)
self->fieldValues = calloc(attrCount + 1, sizeof(id));
fieldDictCount = 0;
for (cnt = 0; cnt < attrCount; cnt++) {
EOAttribute *attribute;
NSString *attrName;
id value = nil;
Class valueClass = Nil;
const char *pvalue;
int vallen;
attribute = [_attributes objectAtIndex:cnt];
attrName = [attribute name];
if ([self->resultSet isNullTuple:self->currentTuple atIndex:indices[cnt]]){
self->fieldKeys[fieldDictCount] = attrName;
self->fieldValues[fieldDictCount] = null;
fieldDictCount++;
continue;
}
valueClass = NSClassFromString([attribute valueClassName]);
if (valueClass == Nil) {
NSLog(@"ERROR(%s): %@: got no value class for column:\n"
@" attribute=%@\n type=%@",
__PRETTY_FUNCTION__, self,
attrName, [attribute externalType]);
continue;
}
pvalue = [self->resultSet rawValueOfTuple:self->currentTuple
atIndex:indices[cnt]];
vallen = [self->resultSet lengthOfTuple:self->currentTuple
atIndex:indices[cnt]];
if (self->containsBinaryData) {
// pvalue is stored in internal representation
value = [valueClass valueFromBytes:pvalue length:vallen
postgreSQLType:[attribute externalType]
attribute:attribute
adaptorChannel:self];
}
else {
// pvalue is ascii string
value = [valueClass valueFromCString:pvalue length:vallen
postgreSQLType:[attribute externalType]
attribute:attribute
adaptorChannel:self];
}
if (value == nil) {
NSLog(@"ERROR(%s): %@: got no value for column: attribute=%@\n valueClass=%@\n type=%@",
__PRETTY_FUNCTION__, self,
attrName, NSStringFromClass(valueClass),
[attribute externalType]);
continue;
}
/* add to dictionary */
self->fieldKeys[fieldDictCount] = attrName;
self->fieldValues[fieldDictCount] = value;
fieldDictCount++;
}
self->currentTuple++;
// TODO: we would need to have a copy on write dict here, ideally with
// the keys being reused for each fetch-loop
row = [[MDictClass alloc] initWithObjects:self->fieldValues
forKeys:self->fieldKeys
count:fieldDictCount];
return [row autorelease];
}
/* sending sql to server */
- (void)_resetEvaluationState {
self->isFetchInProgress = NO;
self->tupleCount = 0;
self->fieldCount = 0;
self->currentTuple = 0;
self->containsBinaryData = NO;
if (self->fieldInfo) {
free(self->fieldInfo);
self->fieldInfo = NULL;
}
/* new caches which require a constant _attributes argument */
if (self->fieldIndices) free(self->fieldIndices); self->fieldIndices = NULL;
if (self->fieldKeys) free(self->fieldKeys); self->fieldKeys = NULL;
if (self->fieldValues) free(self->fieldValues); self->fieldValues = NULL;
}
- (NSException *)_processEvaluationTuplesOKForExpression:(NSString *)_sql {
int i;
self->isFetchInProgress = YES;
self->tupleCount = [self->resultSet tupleCount];
self->fieldCount = [self->resultSet fieldCount];
self->containsBinaryData = [self->resultSet containsBinaryTuples];
self->fieldInfo =
calloc(self->fieldCount + 1, sizeof(PostgreSQL72FieldInfo));
for (i = 0; i < self->fieldCount; i++) {
self->fieldInfo[i].name = PQfname(self->resultSet->results, i);
self->fieldInfo[i].type = PQftype(self->resultSet->results, i);
self->fieldInfo[i].size = [self->resultSet fieldSizeAtIndex:i];
self->fieldInfo[i].modification = [self->resultSet modifierAtIndex:i];
}
self->cmdStatus = [[self->resultSet commandStatus] copy];
self->cmdTuples = [[self->resultSet commandTuples] copy];
if (delegateRespondsTo.didEvaluateExpression)
[delegate adaptorChannel:self didEvaluateExpression:_sql];
#if HEAVY_DEBUG
NSLog(@"tuples %i fields %i status %@",
self->tupleCount, self->fieldCount, self->cmdStatus);
#endif
return nil;
}
- (NSException *)_handleBadResponseError {
NSString *s;
[self _resetResults];
s = [NSString stringWithFormat:@"bad pgsql response (channel=%@): %@",
self, [self->connection errorMessage]];
return [PostgreSQL72Exception exceptionWithName:@"PostgreSQL72BadResponse"
reason:s userInfo:nil];
}
- (NSException *)_handleNonFatalEvaluationError {
NSString *s;
[self _resetResults];
s = [NSString stringWithFormat:@"pgsql error (channel=%@): %@",
self, [self->connection errorMessage]];
return [PostgreSQL72Exception exceptionWithName:@"PostgreSQL72Error"
reason:s userInfo:nil];
}
- (NSException *)_handleFatalEvaluationError {
NSString *s;
[self _resetResults];
s = [NSString stringWithFormat:@"fatal pgsql error (channel=%@): %@",
self, [self->connection errorMessage]];
return [PostgreSQL72Exception exceptionWithName:@"PostgreSQL72FatalError"
reason:s userInfo:nil];
}
- (NSException *)evaluateExpressionX:(NSString *)_expression {
BOOL result;
*(&result) = YES;
if (_expression == nil) {
return [NSException exceptionWithName:NSInvalidArgumentException
reason:@"parameter for evaluateExpression: "
@"must not be null"
userInfo:nil];
}
*(&_expression) = [[_expression mutableCopy] autorelease];
if (delegateRespondsTo.willEvaluateExpression) {
EODelegateResponse response;
response = [delegate adaptorChannel:self
willEvaluateExpression:
(NSMutableString *)_expression];
if (response == EODelegateRejects) {
return [NSException exceptionWithName:@"EODelegateRejects"
reason:@"delegate rejected insert"
userInfo:nil];
}
if (response == EODelegateOverrides)
return nil;
}
if (![self isOpen]) {
return [PostgreSQL72Exception exceptionWithName:@"ChannelNotOpenException"
reason:
@"PostgreSQL72 connection is not open"
userInfo:nil];
}
if (self->resultSet != nil) {
return [PostgreSQL72Exception exceptionWithName:
@"CommandInProgressException"
reason:@"an evaluation is in progress"
userInfo:nil];
}
if (isDebuggingEnabled)
NSLog(@"PG0x%p SQL: %@", self, _expression);
[self _resetEvaluationState];
self->resultSet = [[self->connection execute:_expression] retain];
if (self->resultSet == nil) {
return [self _handleFatalEvaluationError];
}
/* process results */
switch (PQresultStatus(self->resultSet->results)) {
case PGRES_EMPTY_QUERY:
case PGRES_COMMAND_OK:
[self _resetResults];
if (delegateRespondsTo.didEvaluateExpression)
[delegate adaptorChannel:self didEvaluateExpression:_expression];
return nil;
case PGRES_TUPLES_OK:
return [self _processEvaluationTuplesOKForExpression:_expression];
case PGRES_COPY_OUT:
case PGRES_COPY_IN:
[self _resetResults];
return [PostgreSQL72Exception exceptionWithName:@"UnsupportedOperation"
reason:@"copy(out|in) not supported"
userInfo:nil];
case PGRES_BAD_RESPONSE:
return [self _handleBadResponseError];
case PGRES_NONFATAL_ERROR:
return [self _handleNonFatalEvaluationError];
case PGRES_FATAL_ERROR:
return [self _handleFatalEvaluationError];
default:
return [NSException exceptionWithName:@"PostgreSQLEvalFailed"
reason:@"generic reason"
userInfo:nil];
}
}
- (BOOL)evaluateExpression:(NSString *)_sql {
NSException *e;
NSString *n;
if ((e = [self evaluateExpressionX:_sql]) == nil)
return YES;
/* for compatibility with non-X methods, translate some errors to a bool */
n = [e name];
if ([n isEqualToString:@"EOEvaluationError"])
return NO;
if ([n isEqualToString:@"EODelegateRejects"])
return NO;
[e raise];
return NO;
}
/* description */
- (NSString *)description {
NSMutableString *ms;
ms = [NSMutableString stringWithCapacity:128];
[ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
if (self->connection)
[ms appendFormat:@" connection=%@", self->connection];
else
[ms appendString:@" not-connected"];
[ms appendString:@">"];
return ms;
}
@end /* PostgreSQL72Channel */
@implementation PostgreSQL72Channel(PrimaryKeyGeneration)
- (NSDictionary *)primaryKeyForNewRowWithEntity:(EOEntity *)_entity {
NSArray *pkeys;
PostgreSQL72Adaptor *adaptor;
NSString *seqName, *seq;
NSDictionary *pkey;
pkeys = [_entity primaryKeyAttributeNames];
adaptor = (id)[[self adaptorContext] adaptor];
seqName = [adaptor primaryKeySequenceName];
pkey = nil;
seq = nil;
seq = [seqName length] > 0
? [StringClass stringWithFormat:@"SELECT NEXTVAL ('%@')", seqName]
: (id)[adaptor newKeyExpression];
// TODO: since we use evaluateExpressionX, we should not see exceptions?
NS_DURING {
if ([self evaluateExpressionX:seq] == nil) {
id key = nil;
if (self->tupleCount > 0) {
if ([self->resultSet isNullTuple:0 atIndex:0])
key = [null retain];
else {
const char *pvalue;
if (self->containsBinaryData) {
#if COCOA_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
NSLog(@"%s: binary data not implemented!", __PRETTY_FUNCTION__);
#else
[self notImplemented:_cmd];
#endif
}
pvalue = [self->resultSet rawValueOfTuple:0 atIndex:0];
if (pvalue)
key = [[NSNumber alloc] initWithInt:atoi(pvalue)];
}
}
[self cancelFetch];
if (key) {
pkey = [NSDictionary dictionaryWithObject:key
forKey:[pkeys objectAtIndex:0]];
[key release]; key = nil;
}
}
}
NS_HANDLER
pkey = nil;
NS_ENDHANDLER;
return pkey;
}
@end /* PostgreSQL72Channel(PrimaryKeyGeneration) */
void __link_PostgreSQL72Channel() {
// used to force linking of object file
__link_PostgreSQL72Channel();
}