4 Copyright (C) 1999 MDlink online service center GmbH and Helge Hess
5 Copyright (C) 2000-2004 SKYRIX Software AG and Helge Hess
7 Author: Helge Hess (helge.hess@opengroupware.org)
9 This file is part of the PostgreSQL72 Adaptor Library
11 This library is free software; you can redistribute it and/or
12 modify it under the terms of the GNU Library General Public
13 License as published by the Free Software Foundation; either
14 version 2 of the License, or (at your option) any later version.
16 This library is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Library General Public License for more details.
21 You should have received a copy of the GNU Library General Public
22 License along with this library; see the file COPYING.LIB.
23 If not, write to the Free Software Foundation,
24 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 // $Id: PostgreSQL72Channel.m 1 2004-08-20 10:38:46Z znek $
32 #import "PostgreSQL72Channel.h"
33 #import "PostgreSQL72Adaptor.h"
34 #import "PostgreSQL72Exception.h"
35 #import "NSString+PostgreSQL72.h"
36 #import "PostgreSQL72Values.h"
37 #import "EOAttribute+PostgreSQL72.h"
38 #include "PGConnection.h"
41 # define MIN(x, y) ((x > y) ? y : x)
44 #if PG_MAJOR_VERSION >= 6 && PG_MINOR_VERSION > 3
45 # define NG_HAS_NOTICE_PROCESSOR 1
46 # define NG_HAS_BINARY_TUPLES 1
47 # define NG_HAS_FMOD 1
50 #if PG_MAJOR_VERSION >= 7 && PG_MINOR_VERSION > 3
51 # define NG_SET_CLIENT_ENCODING 1
54 #define MAX_CHAR_BUF 16384
56 @interface PostgreSQL72Channel(Privates)
57 - (void)_resetEvaluationState;
60 @implementation PostgreSQL72Channel
62 #if NG_SET_CLIENT_ENCODING
63 static NSString *PGClientEncoding = @"Latin1";
65 static int MaxOpenConnectionCount = -1;
66 static BOOL debugOn = NO;
67 static NSNull *null = nil;
68 static NSNumber *yesObj = nil;
69 static Class StringClass = Nil;
70 static Class MDictClass = Nil;
73 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
75 if (null == nil) null = [[NSNull null] retain];
76 if (yesObj == nil) yesObj = [[NSNumber numberWithBool:YES] retain];
78 StringClass = [NSString class];
79 MDictClass = [NSMutableDictionary class];
81 MaxOpenConnectionCount = [ud integerForKey:@"PGMaxOpenConnectionCount"];
82 if (MaxOpenConnectionCount < 2)
83 MaxOpenConnectionCount = 50;
85 debugOn = [ud boolForKey:@"PGDebugEnabled"];
88 - (id)initWithAdaptorContext:(EOAdaptorContext*)_adaptorContext {
89 if ((self = [super initWithAdaptorContext:_adaptorContext])) {
90 [self setDebugEnabled:debugOn];
92 self->_attributesForTableName = [[MDictClass alloc] initWithCapacity:16];
93 self->_primaryKeysNamesForTableName =
94 [[MDictClass alloc] initWithCapacity:16];
102 [self _resetEvaluationState];
107 [self->_attributesForTableName release];
108 [self->_primaryKeysNamesForTableName release];
112 /* NSCopying methods */
114 - (id)copyWithZone:(NSZone *)zone {
115 return [self retain];
120 - (void)setDebugEnabled:(BOOL)_flag {
121 self->isDebuggingEnabled = _flag;
123 - (BOOL)isDebugEnabled {
124 return self->isDebuggingEnabled;
127 - (void)receivedMessage:(NSString *)_message {
128 NSLog(@"%@: message: %@", self, _message);
131 static void _pgMessageProcessor(void *_channel, const char *_msg)
132 __attribute__((unused));
134 static void _pgMessageProcessor(void *_channel, const char *_msg) {
135 [(id)_channel receivedMessage:
136 _msg ? [StringClass stringWithCString:_msg] : nil];
141 - (void)_resetResults {
142 [self->resultSet clear];
143 [self->resultSet release];
144 self->resultSet = nil;
149 static int openConnectionCount = 0;
152 return [self->connection isValid];
155 - (BOOL)openChannel {
156 PostgreSQL72Adaptor *adaptor;
158 if ([self->connection isValid]) {
159 NSLog(@"%s: Connection already open !!!", __PRETTY_FUNCTION__);
163 adaptor = (PostgreSQL72Adaptor *)[adaptorContext adaptor];
165 if (![super openChannel])
169 NSLog(@"+++++++++ %s: openConnectionCount %d", __PRETTY_FUNCTION__,
170 openConnectionCount);
173 if (openConnectionCount > MaxOpenConnectionCount) {
174 [PostgreSQL72CouldNotOpenChannelException raise:@"NoMoreConnections"
176 @"cannot open a additional connection !"];
181 [[PGConnection alloc] initWithHostName:[adaptor serverName]
182 port:[(id)[adaptor port] stringValue]
183 options:[adaptor options]
184 tty:[adaptor tty] database:[adaptor databaseName]
185 login:[adaptor loginName]
186 password:[adaptor loginPassword]];
188 if (![self->connection isValid]) {
189 // could not login ..
190 NSLog(@"WARNING: could not open pgsql channel to %@@%@ host %@:%@",
192 [adaptor databaseName], [adaptor serverName], [adaptor port]);
197 if (![self->connection isConnectionOK]) {
198 NSLog(@"could not open channel to %@@%@",
199 [adaptor databaseName], [adaptor serverName]);
200 [self->connection finish];
201 [self->connection release];
202 self->connection = nil;
206 /* set message callback */
207 [self->connection setNoticeProcessor:_pgMessageProcessor context:self];
209 /* set client encoding */
210 #if NG_SET_CLIENT_ENCODING
211 if (![self->connection setClientEncoding:PGClientEncoding]) {
212 NSLog(@"WARNING: could not set client encoding to: '%s'",
219 if (isDebuggingEnabled)
220 NSLog(@"PostgreSQL72 connection established: %@", self->connection);
223 NSLog(@"---------- %s: %@ opens channel count[%d]", __PRETTY_FUNCTION__,
224 self, openConnectionCount);
227 openConnectionCount++;
229 if (isDebuggingEnabled) {
230 NSLog(@"PostgreSQL72 channel 0x%08X opened (connection=%@)",
231 (unsigned)self, self->connection);
236 - (void)primaryCloseChannel {
237 self->tupleCount = 0;
238 self->fieldCount = 0;
239 self->containsBinaryData = NO;
241 if (self->fieldInfo) {
242 free(self->fieldInfo);
243 self->fieldInfo = NULL;
246 [self _resetResults];
248 [self->cmdStatus release]; self->cmdStatus = nil;
249 [self->cmdTuples release]; self->cmdTuples = nil;
251 if (self->connection) {
252 [self->connection finish];
254 NSLog(@"---------- %s: %@ close channel count[%d]", __PRETTY_FUNCTION__,
255 self, openConnectionCount);
257 openConnectionCount--;
259 if (isDebuggingEnabled) {
261 "PostgreSQL72 connection dropped 0x%08X (channel=0x%08X)\n",
262 (unsigned)self->connection, (unsigned)self);
264 [self->connection release];
265 self->connection = nil;
269 - (void)closeChannel {
270 [super closeChannel];
271 [self primaryCloseChannel];
276 - (void)cancelFetch {
277 if (![self isOpen]) {
278 [PostgreSQL72Exception raise:@"ChannelNotOpenException"
279 format:@"No fetch in progress, connection is not open"
280 @" (channel=%@)", self];
284 NSLog(@"canceling fetch (%i tuples remaining).",
285 (self->tupleCount - self->currentTuple));
288 self->tupleCount = 0;
289 self->currentTuple = 0;
290 self->fieldCount = 0;
291 self->containsBinaryData = NO;
293 if (self->fieldInfo) {
294 free(self->fieldInfo);
295 self->fieldInfo = NULL;
297 [self _resetResults];
299 [self->cmdStatus release]; self->cmdStatus = nil;
300 [self->cmdTuples release]; self->cmdTuples = nil;
302 /* new caches which require a constant _attributes argument */
303 if (self->fieldIndices) free(self->fieldIndices); self->fieldIndices = NULL;
304 if (self->fieldKeys) free(self->fieldKeys); self->fieldKeys = NULL;
305 if (self->fieldValues) free(self->fieldValues); self->fieldValues = NULL;
310 - (NSArray *)describeResults:(BOOL)_beautifyNames {
312 NSMutableArray *result = nil;
313 NSMutableDictionary *usedNames = nil;
315 if (![self isFetchInProgress]) {
316 [PostgreSQL72Exception raise:@"NoFetchInProgress"
317 format:@"No fetch in progress (channel=%@)", self];
320 result = [[NSMutableArray alloc] initWithCapacity:self->fieldCount];
321 usedNames = [[MDictClass alloc] initWithCapacity:self->fieldCount];
323 for (cnt = 0; cnt < self->fieldCount; cnt++) {
324 EOAttribute *attribute = nil;
325 NSString *columnName;
329 [[StringClass alloc] initWithCString:self->fieldInfo[cnt].name];
330 attrName = _beautifyNames
331 ? [columnName _pgModelMakeInstanceVarName]
334 if ([[usedNames objectForKey:attrName] boolValue]) {
335 // TODO: move name generation code to different method!
338 NSString *newAttrName = nil;
340 for (cnt2 = 2; cnt2 < 100; cnt2++) {
343 sprintf(buf, "%i", cnt2);
345 s = [[StringClass alloc] initWithCString:buf];
346 newAttrName = [attrName stringByAppendingString:s];
349 if (![[usedNames objectForKey:newAttrName] boolValue]) {
350 attrName = newAttrName;
355 [usedNames setObject:yesObj forKey:attrName];
357 attribute = [[EOAttribute alloc] init];
358 [attribute setName:attrName];
359 [attribute setColumnName:columnName];
361 //NSLog(@"column: %@", columnName);
363 [attribute loadValueClassAndTypeUsingPostgreSQLType:
364 self->fieldInfo[cnt].type
365 size:self->fieldInfo[cnt].size
366 modification:self->fieldInfo[cnt].modification
367 binary:self->containsBinaryData];
369 [result addObject:attribute];
371 [columnName release]; columnName = nil;
372 [attribute release]; attribute = nil;
378 return [result autorelease];
381 - (void)_fillFieldNamesForAttributes:(NSArray *)_attributes
382 count:(unsigned)attrCount
384 // Note: this optimization requires that the "_attributes" array does
385 // note change between invocations!
386 // TODO: should add a sanity check for that!
387 NSMutableArray *fieldNames;
391 if (self->fieldIndices)
394 self->fieldIndices = calloc(attrCount + 2, sizeof(int));
396 // TODO: we could probably cache the field-name array for much more speed !
397 fieldNames = [[NSMutableArray alloc] initWithCapacity:32];
398 nFields = [self->resultSet fieldCount];
399 for (i = 0; i < nFields; i++)
400 [fieldNames addObject:[self->resultSet fieldNameAtIndex:i]];
402 for (cnt = 0; cnt < attrCount; cnt++) {
403 EOAttribute *attribute;
405 attribute = [_attributes objectAtIndex:cnt];
406 #if GDL_USE_PQFNUMBER_INDEX
407 self->fieldIndices[cnt] =
408 [self->resultSet indexOfFieldNamed:[attribute columnName]];
410 self->fieldIndices[cnt] =
411 [fieldNames indexOfObject:[attribute columnName]];
414 if (self->fieldIndices[cnt] == NSNotFound) {
415 [PostgreSQL72Exception raiseWithFormat:
416 @"attribute %@ not covered by query",
419 [fieldNames replaceObjectAtIndex:self->fieldIndices[cnt] withObject:null];
421 [fieldNames release]; fieldNames = nil;
424 - (NSMutableDictionary *)primaryFetchAttributes:(NSArray *)_attributes
425 withZone:(NSZone *)_zone
427 NSMutableDictionary *row;
430 unsigned cnt, fieldDictCount;
432 if (self->currentTuple == self->tupleCount) {
433 if (self->resultSet != nil) [self cancelFetch];
437 attrCount = [_attributes count];
438 [self _fillFieldNamesForAttributes:_attributes count:attrCount];
439 indices = self->fieldIndices;
441 if (self->fieldKeys == NULL)
442 self->fieldKeys = calloc(attrCount + 1, sizeof(NSString *));
443 if (self->fieldValues == NULL)
444 self->fieldValues = calloc(attrCount + 1, sizeof(id));
447 for (cnt = 0; cnt < attrCount; cnt++) {
448 EOAttribute *attribute;
451 Class valueClass = Nil;
455 attribute = [_attributes objectAtIndex:cnt];
456 attrName = [attribute name];
458 if ([self->resultSet isNullTuple:self->currentTuple atIndex:indices[cnt]]){
459 self->fieldKeys[fieldDictCount] = attrName;
460 self->fieldValues[fieldDictCount] = null;
465 valueClass = NSClassFromString([attribute valueClassName]);
466 if (valueClass == Nil) {
467 NSLog(@"ERROR(%s): %@: got no value class for column:\n"
468 @" attribute=%@\n type=%@",
469 __PRETTY_FUNCTION__, self,
470 attrName, [attribute externalType]);
474 pvalue = [self->resultSet rawValueOfTuple:self->currentTuple
475 atIndex:indices[cnt]];
476 vallen = [self->resultSet lengthOfTuple:self->currentTuple
477 atIndex:indices[cnt]];
479 if (self->containsBinaryData) {
480 // pvalue is stored in internal representation
482 value = [valueClass valueFromBytes:pvalue length:vallen
483 postgreSQLType:[attribute externalType]
485 adaptorChannel:self];
488 // pvalue is ascii string
490 value = [valueClass valueFromCString:pvalue length:vallen
491 postgreSQLType:[attribute externalType]
493 adaptorChannel:self];
496 NSLog(@"ERROR(%s): %@: got no value for column:\n"
497 @" attribute=%@\n valueClass=%@\n type=%@",
498 __PRETTY_FUNCTION__, self,
499 attrName, NSStringFromClass(valueClass),
500 [attribute externalType]);
504 /* add to dictionary */
505 self->fieldKeys[fieldDictCount] = attrName;
506 self->fieldValues[fieldDictCount] = value;
510 self->currentTuple++;
512 // TODO: we would need to have a copy on write dict here, ideally with
513 // the keys being reused for each fetch-loop
514 row = [[MDictClass alloc] initWithObjects:self->fieldValues
515 forKeys:self->fieldKeys
516 count:fieldDictCount];
517 return [row autorelease];
520 /* sending sql to server */
522 - (void)_resetEvaluationState {
523 self->isFetchInProgress = NO;
524 self->tupleCount = 0;
525 self->fieldCount = 0;
526 self->currentTuple = 0;
527 self->containsBinaryData = NO;
528 if (self->fieldInfo) {
529 free(self->fieldInfo);
530 self->fieldInfo = NULL;
533 /* new caches which require a constant _attributes argument */
534 if (self->fieldIndices) free(self->fieldIndices); self->fieldIndices = NULL;
535 if (self->fieldKeys) free(self->fieldKeys); self->fieldKeys = NULL;
536 if (self->fieldValues) free(self->fieldValues); self->fieldValues = NULL;
539 - (NSException *)_processEvaluationTuplesOKForExpression:(NSString *)_sql {
542 self->isFetchInProgress = YES;
544 self->tupleCount = [self->resultSet tupleCount];
545 self->fieldCount = [self->resultSet fieldCount];
546 self->containsBinaryData = [self->resultSet containsBinaryTuples];
549 calloc(self->fieldCount + 1, sizeof(PostgreSQL72FieldInfo));
550 for (i = 0; i < self->fieldCount; i++) {
551 self->fieldInfo[i].name = PQfname(self->resultSet->results, i);
552 self->fieldInfo[i].type = PQftype(self->resultSet->results, i);
553 self->fieldInfo[i].size = [self->resultSet fieldSizeAtIndex:i];
554 self->fieldInfo[i].modification = [self->resultSet modifierAtIndex:i];
557 self->cmdStatus = [[self->resultSet commandStatus] copy];
558 self->cmdTuples = [[self->resultSet commandTuples] copy];
560 if (delegateRespondsTo.didEvaluateExpression)
561 [delegate adaptorChannel:self didEvaluateExpression:_sql];
564 NSLog(@"tuples %i fields %i status %@",
565 self->tupleCount, self->fieldCount, self->cmdStatus);
570 - (NSException *)_handleBadResponseError {
573 [self _resetResults];
575 s = [NSString stringWithFormat:@"bad pgsql response (channel=%@): %@",
576 self, [self->connection errorMessage]];
577 return [PostgreSQL72Exception exceptionWithName:@"PostgreSQL72BadResponse"
578 reason:s userInfo:nil];
580 - (NSException *)_handleNonFatalEvaluationError {
583 [self _resetResults];
585 s = [NSString stringWithFormat:@"pgsql error (channel=%@): %@",
586 self, [self->connection errorMessage]];
587 return [PostgreSQL72Exception exceptionWithName:@"PostgreSQL72Error"
588 reason:s userInfo:nil];
590 - (NSException *)_handleFatalEvaluationError {
593 [self _resetResults];
595 s = [NSString stringWithFormat:@"fatal pgsql error (channel=%@): %@",
596 self, [self->connection errorMessage]];
597 return [PostgreSQL72Exception exceptionWithName:@"PostgreSQL72FatalError"
598 reason:s userInfo:nil];
601 - (NSException *)evaluateExpressionX:(NSString *)_expression {
606 if (_expression == nil) {
607 return [NSException exceptionWithName:NSInvalidArgumentException
608 reason:@"parameter for evaluateExpression: "
613 *(&_expression) = [[_expression mutableCopy] autorelease];
615 if (delegateRespondsTo.willEvaluateExpression) {
616 EODelegateResponse response;
618 response = [delegate adaptorChannel:self
619 willEvaluateExpression:
620 (NSMutableString *)_expression];
622 if (response == EODelegateRejects) {
623 return [NSException exceptionWithName:@"EODelegateRejects"
624 reason:@"delegate rejected insert"
627 if (response == EODelegateOverrides)
631 if (![self isOpen]) {
632 return [PostgreSQL72Exception exceptionWithName:@"ChannelNotOpenException"
634 @"PostgreSQL72 connection is not open"
637 if (self->resultSet != nil) {
638 return [PostgreSQL72Exception exceptionWithName:
639 @"CommandInProgressException"
640 reason:@"an evaluation is in progress"
644 if (isDebuggingEnabled)
645 NSLog(@"PG0x%08X SQL: %@", (unsigned)self, _expression);
647 [self _resetEvaluationState];
649 self->resultSet = [[self->connection execute:_expression] retain];
650 if (self->resultSet == nil) {
651 return [PostgreSQL72Exception exceptionWithName:@"ExecutionFailed"
652 reason:@"the PQexec() failed"
656 /* process results */
658 switch (PQresultStatus(self->resultSet->results)) {
659 case PGRES_EMPTY_QUERY:
660 case PGRES_COMMAND_OK:
661 [self _resetResults];
663 if (delegateRespondsTo.didEvaluateExpression)
664 [delegate adaptorChannel:self didEvaluateExpression:_expression];
667 case PGRES_TUPLES_OK:
668 return [self _processEvaluationTuplesOKForExpression:_expression];
672 [self _resetResults];
673 return [PostgreSQL72Exception exceptionWithName:@"UnsupportedOperation"
674 reason:@"copy(out|in) not supported"
677 case PGRES_BAD_RESPONSE:
678 return [self _handleBadResponseError];
679 case PGRES_NONFATAL_ERROR:
680 return [self _handleNonFatalEvaluationError];
681 case PGRES_FATAL_ERROR:
682 return [self _handleFatalEvaluationError];
685 return [NSException exceptionWithName:@"PostgreSQLEvalFailed"
686 reason:@"generic reason"
690 - (BOOL)evaluateExpression:(NSString *)_sql {
694 if ((e = [self evaluateExpressionX:_sql]) == nil)
697 /* for compatibility with non-X methods, translate some errors to a bool */
699 if ([n isEqualToString:@"EOEvaluationError"])
701 if ([n isEqualToString:@"EODelegateRejects"])
710 - (NSString *)description {
713 ms = [NSMutableString stringWithCapacity:128];
714 [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
715 if (self->connection)
716 [ms appendFormat:@" connection=%@", self->connection];
718 [ms appendString:@" not-connected"];
719 [ms appendString:@">"];
723 @end /* PostgreSQL72Channel */
725 @implementation PostgreSQL72Channel(PrimaryKeyGeneration)
727 - (NSDictionary *)primaryKeyForNewRowWithEntity:(EOEntity *)_entity {
729 PostgreSQL72Adaptor *adaptor;
730 NSString *seqName, *seq;
733 pkeys = [_entity primaryKeyAttributeNames];
734 adaptor = (id)[[self adaptorContext] adaptor];
735 seqName = [adaptor primaryKeySequenceName];
739 seq = [seqName length] > 0
740 ? [StringClass stringWithFormat:@"SELECT NEXTVAL ('%@')", seqName]
741 : [adaptor newKeyExpression];
743 // TODO: since we use evaluateExpressionX, we should not see exceptions?
745 if ([self evaluateExpressionX:seq] == nil) {
748 if (self->tupleCount > 0) {
749 if ([self->resultSet isNullTuple:0 atIndex:0])
755 if (self->containsBinaryData) {
756 #if COCOA_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
757 NSLog(@"%s: binary data not implemented!", __PRETTY_FUNCTION__);
759 [self notImplemented:_cmd];
763 pvalue = [self->resultSet rawValueOfTuple:0 atIndex:0];
764 vallen = [self->resultSet lengthOfTuple:0 atIndex:0];
767 key = [[NSNumber alloc] initWithInt:atoi(pvalue)];
773 pkey = [NSDictionary dictionaryWithObject:key
774 forKey:[pkeys objectAtIndex:0]];
775 [key release]; key = nil;
786 @end /* PostgreSQL72Channel(PrimaryKeyGeneration) */
788 void __link_PostgreSQL72Channel() {
789 // used to force linking of object file
790 __link_PostgreSQL72Channel();