/* 
   PostgreSQL72Channel.m

   Copyright (C) 1999 MDlink online service center GmbH and Helge Hess
   Copyright (C) 2000-2004 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.
*/
// $Id: PostgreSQL72Channel.m 1 2004-08-20 10:38:46Z znek $

#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"

#ifndef MIN
#  define MIN(x, y) ((x > y) ? y : x)
#endif

#if PG_MAJOR_VERSION >= 6 && PG_MINOR_VERSION > 3
#  define NG_HAS_NOTICE_PROCESSOR 1
#  define NG_HAS_BINARY_TUPLES    1
#  define NG_HAS_FMOD             1
#endif

#if PG_MAJOR_VERSION >= 7 && PG_MINOR_VERSION > 3
#  define NG_SET_CLIENT_ENCODING 1
#endif

#define MAX_CHAR_BUF 16384

@interface PostgreSQL72Channel(Privates)
- (void)_resetEvaluationState;
@end

@implementation PostgreSQL72Channel

#if NG_SET_CLIENT_ENCODING
static NSString *PGClientEncoding = @"Latin1";
#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 {
  NSLog(@"%@: message: %@", self, _message);
}

static void _pgMessageProcessor(void *_channel, const char *_msg)
     __attribute__((unused));

static void _pgMessageProcessor(void *_channel, const char *_msg) {
  [(id)_channel receivedMessage:
       _msg ? [StringClass stringWithCString:_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: '%s'", 
	  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%08X opened (connection=%@)",
          (unsigned)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%08X (channel=0x%08X)\n",
              (unsigned)self->connection, (unsigned)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 {
  int                 cnt;
  NSMutableArray      *result    = nil;
  NSMutableDictionary *usedNames = nil;
  
  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] initWithCString:self->fieldInfo[cnt].name];
    attrName   = [columnName _pgModelMakeInstanceVarName];
    
    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] initWithCString: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;
    
    attribute = [_attributes objectAtIndex:cnt];
#if GDL_USE_PQFNUMBER_INDEX
    self->fieldIndices[cnt] = 
      [self->resultSet indexOfFieldNamed:[attribute columnName]];
#else
    self->fieldIndices[cnt] = 
      [fieldNames indexOfObject:[attribute columnName]];
#endif
    
    if (self->fieldIndices[cnt] == NSNotFound) {
      [PostgreSQL72Exception raiseWithFormat:
                               @"attribute %@ not covered by query",
                             attribute];
    }
    [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:\n"
            @"  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%08X SQL: %@", (unsigned)self, _expression);
  
  [self _resetEvaluationState];
  
  self->resultSet = [[self->connection execute:_expression] retain];
  if (self->resultSet == nil) {
    return [PostgreSQL72Exception exceptionWithName:@"ExecutionFailed"
				  reason:@"the PQexec() failed"
				  userInfo:nil];
  }

  /* 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%08X[%@]:", 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]
    : [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;
          int         vallen;
          
          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];
	  vallen = [self->resultSet lengthOfTuple: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();
}
