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 {
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 = [columnName _pgModelMakeInstanceVarName];
332 if ([[usedNames objectForKey:attrName] boolValue]) {
333 // TODO: move name generation code to different method!
336 NSString *newAttrName = nil;
338 for (cnt2 = 2; cnt2 < 100; cnt2++) {
341 sprintf(buf, "%i", cnt2);
343 s = [[StringClass alloc] initWithCString:buf];
344 newAttrName = [attrName stringByAppendingString:s];
347 if (![[usedNames objectForKey:newAttrName] boolValue]) {
348 attrName = newAttrName;
353 [usedNames setObject:yesObj forKey:attrName];
355 attribute = [[EOAttribute alloc] init];
356 [attribute setName:attrName];
357 [attribute setColumnName:columnName];
359 //NSLog(@"column: %@", columnName);
361 [attribute loadValueClassAndTypeUsingPostgreSQLType:
362 self->fieldInfo[cnt].type
363 size:self->fieldInfo[cnt].size
364 modification:self->fieldInfo[cnt].modification
365 binary:self->containsBinaryData];
367 [result addObject:attribute];
369 [columnName release]; columnName = nil;
370 [attribute release]; attribute = nil;
376 return [result autorelease];
379 - (void)_fillFieldNamesForAttributes:(NSArray *)_attributes
380 count:(unsigned)attrCount
382 // Note: this optimization requires that the "_attributes" array does
383 // note change between invocations!
384 // TODO: should add a sanity check for that!
385 NSMutableArray *fieldNames;
389 if (self->fieldIndices)
392 self->fieldIndices = calloc(attrCount + 2, sizeof(int));
394 // TODO: we could probably cache the field-name array for much more speed !
395 fieldNames = [[NSMutableArray alloc] initWithCapacity:32];
396 nFields = [self->resultSet fieldCount];
397 for (i = 0; i < nFields; i++)
398 [fieldNames addObject:[self->resultSet fieldNameAtIndex:i]];
400 for (cnt = 0; cnt < attrCount; cnt++) {
401 EOAttribute *attribute;
403 attribute = [_attributes objectAtIndex:cnt];
404 #if GDL_USE_PQFNUMBER_INDEX
405 self->fieldIndices[cnt] =
406 [self->resultSet indexOfFieldNamed:[attribute columnName]];
408 self->fieldIndices[cnt] =
409 [fieldNames indexOfObject:[attribute columnName]];
412 if (self->fieldIndices[cnt] == NSNotFound) {
413 [PostgreSQL72Exception raiseWithFormat:
414 @"attribute %@ not covered by query",
417 [fieldNames replaceObjectAtIndex:self->fieldIndices[cnt] withObject:null];
419 [fieldNames release]; fieldNames = nil;
422 - (NSMutableDictionary *)primaryFetchAttributes:(NSArray *)_attributes
423 withZone:(NSZone *)_zone
425 NSMutableDictionary *row;
428 unsigned cnt, fieldDictCount;
430 if (self->currentTuple == self->tupleCount) {
431 if (self->resultSet != nil) [self cancelFetch];
435 attrCount = [_attributes count];
436 [self _fillFieldNamesForAttributes:_attributes count:attrCount];
437 indices = self->fieldIndices;
439 if (self->fieldKeys == NULL)
440 self->fieldKeys = calloc(attrCount + 1, sizeof(NSString *));
441 if (self->fieldValues == NULL)
442 self->fieldValues = calloc(attrCount + 1, sizeof(id));
445 for (cnt = 0; cnt < attrCount; cnt++) {
446 EOAttribute *attribute;
449 Class valueClass = Nil;
453 attribute = [_attributes objectAtIndex:cnt];
454 attrName = [attribute name];
456 if ([self->resultSet isNullTuple:self->currentTuple atIndex:indices[cnt]]){
457 self->fieldKeys[fieldDictCount] = attrName;
458 self->fieldValues[fieldDictCount] = null;
463 valueClass = NSClassFromString([attribute valueClassName]);
464 if (valueClass == Nil) {
465 NSLog(@"ERROR(%s): %@: got no value class for column:\n"
466 @" attribute=%@\n type=%@",
467 __PRETTY_FUNCTION__, self,
468 attrName, [attribute externalType]);
472 pvalue = [self->resultSet rawValueOfTuple:self->currentTuple
473 atIndex:indices[cnt]];
474 vallen = [self->resultSet lengthOfTuple:self->currentTuple
475 atIndex:indices[cnt]];
477 if (self->containsBinaryData) {
478 // pvalue is stored in internal representation
480 value = [valueClass valueFromBytes:pvalue length:vallen
481 postgreSQLType:[attribute externalType]
483 adaptorChannel:self];
486 // pvalue is ascii string
488 value = [valueClass valueFromCString:pvalue length:vallen
489 postgreSQLType:[attribute externalType]
491 adaptorChannel:self];
494 NSLog(@"ERROR(%s): %@: got no value for column:\n"
495 @" attribute=%@\n valueClass=%@\n type=%@",
496 __PRETTY_FUNCTION__, self,
497 attrName, NSStringFromClass(valueClass),
498 [attribute externalType]);
502 /* add to dictionary */
503 self->fieldKeys[fieldDictCount] = attrName;
504 self->fieldValues[fieldDictCount] = value;
508 self->currentTuple++;
510 // TODO: we would need to have a copy on write dict here, ideally with
511 // the keys being reused for each fetch-loop
512 row = [[MDictClass alloc] initWithObjects:self->fieldValues
513 forKeys:self->fieldKeys
514 count:fieldDictCount];
515 return [row autorelease];
518 /* sending sql to server */
520 - (void)_resetEvaluationState {
521 self->isFetchInProgress = NO;
522 self->tupleCount = 0;
523 self->fieldCount = 0;
524 self->currentTuple = 0;
525 self->containsBinaryData = NO;
526 if (self->fieldInfo) {
527 free(self->fieldInfo);
528 self->fieldInfo = NULL;
531 /* new caches which require a constant _attributes argument */
532 if (self->fieldIndices) free(self->fieldIndices); self->fieldIndices = NULL;
533 if (self->fieldKeys) free(self->fieldKeys); self->fieldKeys = NULL;
534 if (self->fieldValues) free(self->fieldValues); self->fieldValues = NULL;
537 - (NSException *)_processEvaluationTuplesOKForExpression:(NSString *)_sql {
540 self->isFetchInProgress = YES;
542 self->tupleCount = [self->resultSet tupleCount];
543 self->fieldCount = [self->resultSet fieldCount];
544 self->containsBinaryData = [self->resultSet containsBinaryTuples];
547 calloc(self->fieldCount + 1, sizeof(PostgreSQL72FieldInfo));
548 for (i = 0; i < self->fieldCount; i++) {
549 self->fieldInfo[i].name = PQfname(self->resultSet->results, i);
550 self->fieldInfo[i].type = PQftype(self->resultSet->results, i);
551 self->fieldInfo[i].size = [self->resultSet fieldSizeAtIndex:i];
552 self->fieldInfo[i].modification = [self->resultSet modifierAtIndex:i];
555 self->cmdStatus = [[self->resultSet commandStatus] copy];
556 self->cmdTuples = [[self->resultSet commandTuples] copy];
558 if (delegateRespondsTo.didEvaluateExpression)
559 [delegate adaptorChannel:self didEvaluateExpression:_sql];
562 NSLog(@"tuples %i fields %i status %@",
563 self->tupleCount, self->fieldCount, self->cmdStatus);
568 - (NSException *)_handleBadResponseError {
571 [self _resetResults];
573 s = [NSString stringWithFormat:@"bad pgsql response (channel=%@): %@",
574 self, [self->connection errorMessage]];
575 return [PostgreSQL72Exception exceptionWithName:@"PostgreSQL72BadResponse"
576 reason:s userInfo:nil];
578 - (NSException *)_handleNonFatalEvaluationError {
581 [self _resetResults];
583 s = [NSString stringWithFormat:@"pgsql error (channel=%@): %@",
584 self, [self->connection errorMessage]];
585 return [PostgreSQL72Exception exceptionWithName:@"PostgreSQL72Error"
586 reason:s userInfo:nil];
588 - (NSException *)_handleFatalEvaluationError {
591 [self _resetResults];
593 s = [NSString stringWithFormat:@"fatal pgsql error (channel=%@): %@",
594 self, [self->connection errorMessage]];
595 return [PostgreSQL72Exception exceptionWithName:@"PostgreSQL72FatalError"
596 reason:s userInfo:nil];
599 - (NSException *)evaluateExpressionX:(NSString *)_expression {
604 if (_expression == nil) {
605 return [NSException exceptionWithName:NSInvalidArgumentException
606 reason:@"parameter for evaluateExpression: "
611 *(&_expression) = [[_expression mutableCopy] autorelease];
613 if (delegateRespondsTo.willEvaluateExpression) {
614 EODelegateResponse response;
616 response = [delegate adaptorChannel:self
617 willEvaluateExpression:
618 (NSMutableString *)_expression];
620 if (response == EODelegateRejects) {
621 return [NSException exceptionWithName:@"EODelegateRejects"
622 reason:@"delegate rejected insert"
625 if (response == EODelegateOverrides)
629 if (![self isOpen]) {
630 return [PostgreSQL72Exception exceptionWithName:@"ChannelNotOpenException"
632 @"PostgreSQL72 connection is not open"
635 if (self->resultSet != nil) {
636 return [PostgreSQL72Exception exceptionWithName:
637 @"CommandInProgressException"
638 reason:@"an evaluation is in progress"
642 if (isDebuggingEnabled)
643 NSLog(@"PG0x%08X SQL: %@", (unsigned)self, _expression);
645 [self _resetEvaluationState];
647 self->resultSet = [[self->connection execute:_expression] retain];
648 if (self->resultSet == nil) {
649 return [PostgreSQL72Exception exceptionWithName:@"ExecutionFailed"
650 reason:@"the PQexec() failed"
654 /* process results */
656 switch (PQresultStatus(self->resultSet->results)) {
657 case PGRES_EMPTY_QUERY:
658 case PGRES_COMMAND_OK:
659 [self _resetResults];
661 if (delegateRespondsTo.didEvaluateExpression)
662 [delegate adaptorChannel:self didEvaluateExpression:_expression];
665 case PGRES_TUPLES_OK:
666 return [self _processEvaluationTuplesOKForExpression:_expression];
670 [self _resetResults];
671 return [PostgreSQL72Exception exceptionWithName:@"UnsupportedOperation"
672 reason:@"copy(out|in) not supported"
675 case PGRES_BAD_RESPONSE:
676 return [self _handleBadResponseError];
677 case PGRES_NONFATAL_ERROR:
678 return [self _handleNonFatalEvaluationError];
679 case PGRES_FATAL_ERROR:
680 return [self _handleFatalEvaluationError];
683 return [NSException exceptionWithName:@"PostgreSQLEvalFailed"
684 reason:@"generic reason"
688 - (BOOL)evaluateExpression:(NSString *)_sql {
692 if ((e = [self evaluateExpressionX:_sql]) == nil)
695 /* for compatibility with non-X methods, translate some errors to a bool */
697 if ([n isEqualToString:@"EOEvaluationError"])
699 if ([n isEqualToString:@"EODelegateRejects"])
708 - (NSString *)description {
711 ms = [NSMutableString stringWithCapacity:128];
712 [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
713 if (self->connection)
714 [ms appendFormat:@" connection=%@", self->connection];
716 [ms appendString:@" not-connected"];
717 [ms appendString:@">"];
721 @end /* PostgreSQL72Channel */
723 @implementation PostgreSQL72Channel(PrimaryKeyGeneration)
725 - (NSDictionary *)primaryKeyForNewRowWithEntity:(EOEntity *)_entity {
727 PostgreSQL72Adaptor *adaptor;
728 NSString *seqName, *seq;
731 pkeys = [_entity primaryKeyAttributeNames];
732 adaptor = (id)[[self adaptorContext] adaptor];
733 seqName = [adaptor primaryKeySequenceName];
737 seq = [seqName length] > 0
738 ? [StringClass stringWithFormat:@"SELECT NEXTVAL ('%@')", seqName]
739 : [adaptor newKeyExpression];
741 // TODO: since we use evaluateExpressionX, we should not see exceptions?
743 if ([self evaluateExpressionX:seq] == nil) {
746 if (self->tupleCount > 0) {
747 if ([self->resultSet isNullTuple:0 atIndex:0])
753 if (self->containsBinaryData) {
754 #if COCOA_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
755 NSLog(@"%s: binary data not implemented!", __PRETTY_FUNCTION__);
757 [self notImplemented:_cmd];
761 pvalue = [self->resultSet rawValueOfTuple:0 atIndex:0];
762 vallen = [self->resultSet lengthOfTuple:0 atIndex:0];
765 key = [[NSNumber alloc] initWithInt:atoi(pvalue)];
771 pkey = [NSDictionary dictionaryWithObject:key
772 forKey:[pkeys objectAtIndex:0]];
773 [key release]; key = nil;
784 @end /* PostgreSQL72Channel(PrimaryKeyGeneration) */
786 void __link_PostgreSQL72Channel() {
787 // used to force linking of object file
788 __link_PostgreSQL72Channel();