/* 
   SybaseChannel.m

   Copyright (C) 1996 Free Software Foundation, Inc.

   Author: Ovidiu Predescu <ovidiu@bx.logicnet.ro>
   Date: October 1996

   Portions of the code were inspired by a database access library written by
   Stefan Popescu <fane@xpro.pcnet.ro>.

   This file is part of the GNUstep Database 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 <Foundation/NSArray.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSString.h>
#include <Foundation/NSObjCRuntime.h>
#include <Foundation/NSUtilities.h>

#include <extensions/NSException.h>
#include <extensions/exceptions/GeneralExceptions.h>

#include <eoaccess/common.h>
#include <eoaccess/EOModel.h>
#include <eoaccess/EOEntity.h>
#include <eoaccess/EOAttribute.h>
#include <eoaccess/EONull.h>
#include <eoaccess/EOCustomValues.h>
#include <eoaccess/EOSQLExpression.h>
#include <eoaccess/EOQualifier.h>
#include <eoaccess/exceptions/EOFExceptions.h>

#include "SybaseChannel.h"
#include "SybaseExceptions.h"
#include "SybaseValues.h"

extern NSString *_sybaseErrorMessage;

static void __dummy_function_used_for_linking (void)
{
    extern __sybase_values_linking_function (void);

    __sybase_values_linking_function ();
    __dummy_function_used_for_linking ();
}

static void
setAttributeValueClassAndType (EOAttribute* attribute, DBINT sybaseType)
{
    switch (sybaseType) {
	case SYBCHAR:
	    [attribute setExternalType:@"char"];
	    [attribute setValueClassName:@"NSString"];
	    break;
	case SYBVARCHAR:
	    /* This seems to not work, the type is SYBCHAR instead */
	    [attribute setExternalType:@"varchar"];
	    [attribute setValueClassName:@"NSString"];
	    break;
	case SYBTEXT:
	    [attribute setExternalType:@"text"];
	    [attribute setValueClassName:@"NSString"];
	    break;
	case SYBBINARY:
	    [attribute setExternalType:@"binary"];
	    [attribute setValueClassName:@"NSData"];
	    break;
	case SYBVARBINARY:
	    /* This seems to not work, the type is SYBBINARY instead */
	    [attribute setExternalType:@"varbinary"];
	    [attribute setValueClassName:@"NSData"];
	    break;
	case SYBIMAGE:
	    [attribute setExternalType:@"image"];
	    [attribute setValueClassName:@"NSData"];
	    break;
	case SYBINT1:
	    [attribute setExternalType:@"tinyint"];
	    [attribute setValueClassName:@"NSNumber"];
	    [attribute setValueType:@"c"];
	    break;
	case SYBINT2:
	    [attribute setExternalType:@"smallint"];
	    [attribute setValueClassName:@"NSNumber"];
	    [attribute setValueType:@"s"];
	    break;
	case SYBINT4:
	    [attribute setExternalType:@"int"];
	    [attribute setValueClassName:@"NSNumber"];
	    [attribute setValueType:@"i"];
	    break;
	case SYBFLT8:
	    [attribute setExternalType:@"float"];
	    [attribute setValueClassName:@"NSNumber"];
	    [attribute setValueType:@"d"];
	    break;
	case SYBBIT:
	    [attribute setExternalType:@"bit"];
	    [attribute setValueClassName:@"NSNumber"];
	    [attribute setValueType:@"c"];
	    break;
	case SYBMONEY:
	    [attribute setExternalType:@"money"];
	    [attribute setValueClassName:@"NSNumber"];
	    [attribute setValueType:@"d"];
	    break;
	case SYBDATETIME:
	    [attribute setExternalType:@"datetime"];
	    [attribute setValueClassName:@"NSCalendarDate"];
	    break;
	default:
	    THROW ([[InvalidArgumentException alloc]
		    initWithFormat:@"invalid sybase type %d passed to "
			    @"setAttributeValueClassAndType()", sybaseType]);
    }
}

static void
attributeSetDefaultClassForExternalType (EOAttribute* attribute,
					 NSString* externalType)
{
    if ([externalType isEqual:@"tinyint"]) {
	[attribute setValueClassName:@"NSNumber"];
	[attribute setValueType:@"c"];
    }
    else if ([externalType isEqual:@"smallint"]) {
	[attribute setValueClassName:@"NSNumber"];
	[attribute setValueType:@"s"];
    }
    else if ([externalType isEqual:@"int"]) {
	[attribute setValueClassName:@"NSNumber"];
	[attribute setValueType:@"i"];
    }
    else if ([externalType isEqual:@"float"]
	     || [externalType isEqual:@"money"]) {
	[attribute setValueClassName:@"NSNumber"];
	[attribute setValueType:@"d"];
    }
    else if ([externalType isEqual:@"char"]
	     || [externalType isEqual:@"varchar"])
	[attribute setValueClassName:@"NSString"];
    else if ([externalType isEqual:@"binary"]
	     || [externalType isEqual:@"varbinary"])
	[attribute setValueClassName:@"NSData"];
    else if ([externalType isEqual:@"datetime"])
	[attribute setValueClassName:@"NSCalendarDate"];
    else if ([externalType isEqual:@"bit"]) {
	[attribute setValueClassName:@"NSNumber"];
	[attribute setValueType:@"c"];
    }
    else if ([externalType isEqual:@"text"]
	     || [externalType isEqual:@"image"])
	[attribute setValueClassName:@"NSData"];
    else
	NSLog (@"warning: value class name was not set for attribute %@ with "
	       @"external type %@", [attribute name], externalType);
}

@implementation SybaseChannel

- initWithAdaptorContext:(EOAdaptorContext*)_adaptorContext
{
    [super initWithAdaptorContext:_adaptorContext];
    ASSIGN(adaptor, (SybaseAdaptor*)[_adaptorContext adaptor]);
    return self;
}

- (void)dealloc
{
    if([self isOpen])
	[adaptor releaseDBProcess:dbProcess];
    [adaptor release];
    [sqlExpression release];
    [super dealloc];
}

- (BOOL)openChannel
{
    if(![super openChannel])
	return NO;
    dbProcess = [adaptor newDBProcess];
    return (dbProcess != NULL);
}

- (void)closeChannel
{
    [super closeChannel];
    [adaptor releaseDBProcess:dbProcess];

    /* If the adaptor does not share the DBPROCESSes then the dbProcess will be
       freed. Set the dbProcess to NULL to avoid further calls with it. */
    if (![adaptor shareDBProcesses])
	dbProcess = NULL;
}

- (void)cancelFetch
{
    dbcanquery(dbProcess);
    [super cancelFetch];
}

- (RETCODE)_evaluateCommandsUntilAFetch
{
    RETCODE retcode;

    isFetchInProgress = NO;

    /* Set the computeRowId to 0 so the following messages sent to delegate
	will have this value and not the one from the latest operation with
	compute rows. */
    computeRowId = 0;

    /* Process each command until there are no more. */
    while((retcode = dbresults(dbProcess)) != NO_MORE_RESULTS) {
	if(retcode == SUCCEED) {
	    /* The DBROWS gives us an information about the type of the 
	       current command. If DBROWS returns FAIL the command was not
	       a select operation. */
	    rowsAffected = DBCOUNT(dbProcess);
	    if (DBCMDROW(dbProcess) == SUCCEED) {
		/* The command line contained a select operation. Break the
		   loop to let the user to process the results. */
		isFetchInProgress = YES;
		[self _advanceRow];
		break;
	    }
	    else if (dbhasretstat (dbProcess) == TRUE) {
		/* If the command buffer contained a call to a stored,
		    procedure initialize the fetching. First we get the return
		    value and then the parameters. */
		isFetchInProgress = YES;
		fetchState = SybaseReturnStatusFetch;
		rowType = SybaseReturnStatusRow;
		break;
	    }
	}
	else {
	    NSString* errorString
		= [NSString stringWithFormat:@"SQL expression '%@' caused "
			    @"%@", sqlExpression, _sybaseErrorMessage];
	    [adaptor reportError:errorString];
	    return retcode;
	}
    }

    return retcode;
}

- (BOOL)evaluateExpression:(NSString*)expression
{
    expression = [[expression mutableCopy] autorelease];
    ASSIGN(sqlExpression, expression);
    if(delegateRespondsTo.willEvaluateExpression) {
	EODelegateResponse response
	    = [delegate adaptorChannel:self
			willEvaluateExpression:(NSMutableString*)expression];
	if(response == EODelegateRejects)
	    return NO;
	else if(response == EODelegateOverrides)
	    return YES;
    }

    /* Put the expression in the command buffer */
    dbcmd(dbProcess, [expression cString]);

    if ([self isDebugEnabled])
	NSLog (@"command to be sent to SQL server:\n%s\n",
	       [expression cString]);

    /* Send the expression to the SQL server */
    if (dbsqlexec(dbProcess) == FAIL) {
	NSString* errorString
	    = [NSString stringWithFormat:@"SQL expression '%@' caused "
			@"%@", expression, _sybaseErrorMessage];
	[adaptor reportError:errorString];
	return NO;
    }

    /* The last command in the buffer failed */
    if([self _evaluateCommandsUntilAFetch] == FAIL)
	return NO;

    if(delegateRespondsTo.didEvaluateExpression)
	[delegate adaptorChannel:self didEvaluateExpression:expression];

    return YES;
}

- (NSMutableDictionary*)primaryFetchAttributes:(NSArray*)attributes 
  withZone:(NSZone*)zone
{
    if (fetchState == SybaseTerminatedFetch) {
	rowType = SybaseInvalidRowType;
	[self _evaluateCommandsUntilAFetch];

	if (fetchState != SybaseReturnStatusFetch) {
	    /* Return nil to indicate that the fetch operation was finished. In
	       the case of several SELECT operations sent in the same command
	       buffer the user should call fetchAttributes:withZone: again to
	       process the results of the following SELECT operation. */
	    return nil;
	}
	else if (!sybaseDelegateRespondsTo.sybaseWillFetchAttributes) {
	    fetchState = SybaseTerminatedFetch;
	    rowType = SybaseInvalidRowType;
	    [self _evaluateCommandsUntilAFetch];
	    return nil;
	}
    }

    {
	NSMutableDictionary* row;
	int i, count = 0;
	int procedureReturnStatus;
	int (*length_func)(void) = NULL;
	void* (*bytes_func)(void) = NULL;
	DBINT (*sybaseType_func)(void) = NULL;
	BOOL shouldAdvanceRow = YES;	/* This indicates if after processing
					   the current result we have to
					   advance to the next row. */

	int length_dbnumcols (void)
	{
	    return dbdatlen (dbProcess, i + 1);
	}
	int length_dbnumalts (void)
	{
	    return dbadlen (dbProcess, computeRowId, i + 1);
	}
	int length_dbretlen (void)
	{
	    return dbretlen (dbProcess, i + 1);
	}
	int length_returnStatus (void)
	{
	    return sizeof(DBINT);
	}

	void* bytes_dbdata (void)
	{
	    return dbdata (dbProcess, i + 1);
	}
	void* bytes_dbadata (void)
	{
	    return dbadata (dbProcess, computeRowId, i + 1);
	}
	void* bytes_dbretdata (void)
	{
	    return dbretdata (dbProcess, i + 1);
	}
	void* bytes_returnStatus (void)
	{
	    procedureReturnStatus = dbretstatus (dbProcess);
	    return &procedureReturnStatus;
	}

	DBINT sybaseType_dbcoltype (void)
	{
	    return dbcoltype (dbProcess, i + 1);
	}
	DBINT sybaseType_dbalttype (void)
	{
	    return dbalttype (dbProcess, computeRowId, i + 1);
	}
	DBINT sybaseType_dbrettype (void)
	{
	    return dbrettype (dbProcess, i + 1);
	}
	DBINT sybaseType_returnStatus (void)
	{
	    return SYBINT4;
	}

	if (sybaseDelegateRespondsTo.sybaseWillFetchAttributes) {
	    /* How long the delegate returns nil we skip the rows read from the
	       server. If the fetch operation finishes, we should execute all
	       the commands following it until the next fetch and return nil to
	       indicate the original fetch operation failed. */
	    while (!(attributes = [delegate sybaseChannel:self
				   willFetchAttributes:attributes
				   forRowOfType:rowType
				   withComputeRowId:computeRowId])) {
		[self _advanceRow];
		if (fetchState == SybaseTerminatedFetch) {
		    [self _evaluateCommandsUntilAFetch];
		    return nil;
		}
	    }
	}

	if (fetchState == SybaseRowFetch) {
	    if (rowType == SybaseRegularRow) {
		count = dbnumcols (dbProcess);
		length_func = length_dbnumcols;
		bytes_func = bytes_dbdata;
		sybaseType_func = sybaseType_dbcoltype;
	    }
	    else if (rowType == SybaseComputeRow) {
		count = dbnumalts (dbProcess, computeRowId);
		length_func = length_dbnumalts;
		bytes_func = bytes_dbadata;
		sybaseType_func = sybaseType_dbalttype;
	    }
	}
	else if (fetchState == SybaseReturnParameterFetch) {
	    count = dbnumrets (dbProcess);
	    length_func = length_dbretlen;
	    bytes_func = bytes_dbretdata;
	    sybaseType_func = sybaseType_dbrettype;

	    /* Here we have to finish the processing of the commands from
	       the initial buffer. Set fetchStatus to indicate this. */
	    fetchState = SybaseTerminatedFetch;
	    shouldAdvanceRow = NO;
	}
	else if (fetchState == SybaseReturnStatusFetch) {
	    count = 1;
	    length_func = length_returnStatus;
	    bytes_func = bytes_returnStatus;
	    sybaseType_func = sybaseType_returnStatus;

	    shouldAdvanceRow = YES;
	}
	else
	    NSAssert1 (0, @"fetchState is not in enum SybaseFetchState: %d",
			fetchState);

	row = [[[NSMutableDictionary allocWithZone:zone]
		    initWithCapacity:count]
		    autorelease];

	for (i = 0; i < count; i++) {
	    EOAttribute* attr = [attributes objectAtIndex:i];
	    NSString* attrName = [attr name];
	    NSString* valueClassName = [attr valueClassName];
	    Class class = NSClassFromString (valueClassName);
	    int length = (*length_func)();
	    id value;

	    /* If the column has the NULL value insert in the resulting row
		the null value and skip to the next column. */
	    if (!length) {
		[row setObject:[EONull null] forKey:attrName];
		continue;
	    }

	    if (!class)
		THROW([(UnknownClassException*)[UnknownClassException alloc]
			    setClassName:valueClassName]);
	    value = [class valueFromBytes:(*bytes_func)()
			   length:length
			   sybaseType:(*sybaseType_func)()
			   attribute:attr
			   adaptorChannel:self
			   zone:zone];
	    [row setObject:value forKey:attrName];
	}

	/* Ask the delegate if we should return this row. */
	if (sybaseDelegateRespondsTo.sybaseWillReturnRow)
	    row = [delegate sybaseChannel:self
			     willReturnRow:row
			     ofType:rowType
			     withComputeRowId:computeRowId]
		    ? row : nil;

	if (shouldAdvanceRow)
	    [self _advanceRow];

	return row;
    }
}

/* We have to override the insertRow:forEntity: method because of the way in
   which the Sybase DB Library works with binary data. Very ugly! */
- (BOOL)insertRow:(NSDictionary*)row forEntity:(EOEntity*)entity
{
    EOSQLExpression* sqlexpr;
    NSMutableDictionary* mrow = row;
    NSMutableDictionary* usualValues = nil;
    NSMutableDictionary* binaryDataRow
	    = [NSMutableDictionary dictionaryWithCapacity:[row count]];
    NSMutableArray* binaryDataAttributes
	    = [NSMutableArray arrayWithCapacity:[row count]];
    NSEnumerator* enumerator;
    NSString* attrName;

    if(!row || !entity)
	THROW([[InvalidArgumentException alloc]
	    initWithFormat:@"row and entity arguments for insertRow:forEntity:"
			   @" must not be the nil object"]);

    if([self isFetchInProgress])
	THROW([AdaptorIsFetchingException exceptionWithAdaptor:self]);

    if(![adaptorContext transactionNestingLevel])
	THROW([NoTransactionInProgressException exceptionWithAdaptor:self]);

    if(delegateRespondsTo.willInsertRow) {
	EODelegateResponse response;

	mrow = [[row mutableCopyWithZone:[row zone]] autorelease];
	response = [delegate adaptorChannel:self
			     willInsertRow:mrow
			     forEntity:entity];
	if(response == EODelegateRejects)
	    return NO;
	else if(response == EODelegateOverrides)
	    return YES;
    }

    /* Before creating the SQL INSERT expression we have to remove from the
       row the binary and text attributes */
    enumerator = [mrow keyEnumerator];
    while ((attrName = [enumerator nextObject])) {
	EOAttribute* attribute = [entity attributeNamed:attrName];
	NSString* externalType;
	int sybaseType;

	if (!attribute)
	    return NO;

	externalType = [attribute externalType];
	sybaseType = [adaptor typeForName:externalType];

	/* Insert the binary value into the binaryDataRow dictionary */
	if (sybaseType == SYBTEXT || sybaseType == SYBIMAGE) {
	    [binaryDataRow setObject:[mrow objectForKey:attrName]
			   forKey:attrName];
	    [binaryDataAttributes addObject:attribute];
	    if (!usualValues)
		usualValues = [[mrow mutableCopyWithZone:[mrow zone]]
				    autorelease];
	    /* Insert a temporary value in record instead of the real value */
	    if (sybaseType == SYBIMAGE)
		[usualValues setObject:[NSData dataWithBytes:"" length:1]
				forKey:attrName];
	    else
		[usualValues setObject:@"temp" forKey:attrName];
	}
    }
    if (usualValues)
	mrow = usualValues;

    if ([mrow count]) {
	sqlexpr = [[[adaptorContext adaptor] expressionClass]
			insertExpressionForRow:mrow
			entity:entity
			channel:self];
	if(![self evaluateExpression:[sqlexpr expressionValueForContext:nil]])
	    return NO;
    }

    /* Insert the binary data */
    if ([binaryDataRow count]
	&& ![self _modifyBinaryDataRow:binaryDataRow
		  ordinaryDataRow:mrow
		  binaryDataAttributes:binaryDataAttributes
		  forEntity:entity])
	return NO;

    if(delegateRespondsTo.didInsertRow)
	[delegate adaptorChannel:self didInsertRow:mrow forEntity:entity];

    return YES;
}

/* We have to do a similar thing like with -insertRow:forEntity: because of the
   Sybase silly interface. Maybe someone who knows better the Sybase DB-Library
   has a better idea than this one. */
- (BOOL)updateRow:(NSDictionary*)row
  describedByQualifier:(EOQualifier*)qualifier
{
    EOSQLExpression* sqlexpr;
    EOEntity* entity = [qualifier entity];
    NSMutableDictionary* mrow = row;
    NSMutableDictionary* usualValues = nil;
    NSMutableDictionary* binaryDataRow
	    = [NSMutableDictionary dictionaryWithCapacity:[row count]];
    NSMutableArray* binaryDataAttributes
	    = [NSMutableArray arrayWithCapacity:[row count]];
    NSEnumerator* enumerator;
    NSString* attrName;

    if(!row)
	THROW([[InvalidArgumentException alloc]
	    initWithFormat:@"row argument for updateRow:describedByQualifier: "
			   @"must not be the nil object"]);

    if([self isFetchInProgress])
	THROW([AdaptorIsFetchingException exceptionWithAdaptor:self]);

    if(![adaptorContext transactionNestingLevel])
	THROW([NoTransactionInProgressException exceptionWithAdaptor:self]);

    if(delegateRespondsTo.willUpdateRow) {
	EODelegateResponse response;

	mrow = [[row mutableCopyWithZone:[row zone]] autorelease];
	response = [delegate adaptorChannel:self
			     willUpdateRow:mrow
			     describedByQualifier:qualifier];
	if(response == EODelegateRejects)
	    return NO;
	else if(response == EODelegateOverrides)
	    return YES;
    }

    /* Before creating the SQL UPDATE expression we have to remove from the
       row the binary and text attributes */
    enumerator = [mrow keyEnumerator];
    while ((attrName = [enumerator nextObject])) {
	EOAttribute* attribute = [entity attributeNamed:attrName];
	NSString* externalType;
	int sybaseType;

	if (!attribute)
	    return NO;

	externalType = [attribute externalType];
	sybaseType = [adaptor typeForName:externalType];

	/* Insert the binary value into the binaryDataRow dictionary */
	if (sybaseType == SYBTEXT || sybaseType == SYBIMAGE) {
	    [binaryDataRow setObject:[mrow objectForKey:attrName]
			   forKey:attrName];
	    [binaryDataAttributes addObject:attribute];

	    if (![self _updateBinaryDataRow:binaryDataRow
		    binaryDataAttributes:binaryDataAttributes
		    qualifier:qualifier])
		return NO;

	    if (!usualValues)
		usualValues = [[mrow mutableCopyWithZone:[mrow zone]]
				    autorelease];
	    [usualValues removeObjectForKey:attrName];
	    [binaryDataRow removeAllObjects];
	    [binaryDataAttributes removeAllObjects];
	}
    }
    if (usualValues)
	mrow = usualValues;

    if ([mrow count]) {
	sqlexpr = [[[adaptorContext adaptor] expressionClass]
			    updateExpressionForRow:mrow
			    qualifier:qualifier
			    channel:self];
	if(![self evaluateExpression:[sqlexpr expressionValueForContext:nil]])
	    return NO;
	if (rowsAffected <= 0)
	    return NO;
    }

    if(delegateRespondsTo.didUpdateRow)
	[delegate adaptorChannel:self
		  didUpdateRow:mrow
		  describedByQualifier:qualifier];
    return YES;
}

/* The binaryDataRow should contain only one binary attribute */
- (BOOL)_updateBinaryDataRow:(NSMutableDictionary*)binaryDataRow
  binaryDataAttributes:(NSMutableArray*)binaryDataAttributes
  qualifier:(EOQualifier*)qualifier
{
    EOSQLExpression* sqlexpr;
    EOEntity* entity = [qualifier entity];
    NSEnumerator* enumerator;
    NSString* attrName;

    sqlexpr = [[[adaptorContext adaptor] expressionClass]
		    selectExpressionForAttributes:binaryDataAttributes
		    lock:YES
		    qualifier:qualifier
		    fetchOrder:nil
		    channel:self];
    if(![self evaluateExpression:[sqlexpr expressionValueForContext:nil]])
	return NO;

    rowsAffected = 0;

    while ([self fetchAttributes:binaryDataAttributes
		  withZone:NSDefaultMallocZone()]) {
	EOAttribute* attribute;
	NSString* externalType;
	int sybaseType;
	id value;
	char tablecol[256];
	RETCODE retcode;

	rowsAffected++;

	/* Set the binary datum in the affected rows using dbwritetext(). */
	enumerator = [binaryDataAttributes objectEnumerator];
	attribute = [enumerator nextObject];
	attrName = [attribute name];
	externalType = [attribute externalType];
	sybaseType = [adaptor typeForName:externalType];
	value = [binaryDataRow objectForKey:attrName];

	Strcpy (tablecol, [[entity externalName] cString]);
	Strcat (tablecol, ".");
	Strcat (tablecol, [[attribute columnName] cString]);
	if (sybaseType == SYBTEXT) {
	    value = [value stringForType:[attribute valueType]];
	    retcode = dbwritetext (dbProcess, tablecol,
			    dbtxptr (dbProcess, 1),
			    DBTXPLEN,
			    dbtxtimestamp (dbProcess, 1),
			    TRUE,
			    [value cStringLength], [value cString]);
	    if (retcode == FAIL)
		return NO;
	}
	else if (sybaseType == SYBIMAGE) {
	    value = [value dataForType:[attribute valueType]];
	    retcode = dbwritetext (dbProcess, tablecol,
			    dbtxptr (dbProcess, 1),
			    DBTXPLEN,
			    dbtxtimestamp (dbProcess, 1),
			    TRUE,
			    [value length], [value bytes]);
	    if (retcode == FAIL)
		return NO;
	}
	else
	    NSAssert1 (0, @"Don't call the %@ method to update values other "
		       @"than text or image: sybaseType = %d", sybaseType);
	NSAssert (![enumerator nextObject],
		  @"binaryDataRow should contain only one binary attribute");
    }

    if (rowsAffected == 0)
	return NO;

    return YES;
}

- (BOOL)_modifyBinaryDataRow:(NSMutableDictionary*)binaryDataRow
  ordinaryDataRow:(NSMutableDictionary*)mrow
  binaryDataAttributes:(NSMutableArray*)binaryDataAttributes
  forEntity:(EOEntity*)entity
{
    NSEnumerator* enumerator;
    EOAttribute* attribute;
    NSString* attrName;
    EOQualifier* qualifier = [EOQualifier qualifierForPrimaryKey:mrow
					  entity:entity];
    NSMutableDictionary* oneBinaryDataRow;
    NSMutableArray* oneBinaryAttribute;
    id value;

    oneBinaryDataRow =  [NSMutableDictionary dictionaryWithCapacity:1];
    oneBinaryAttribute = [NSMutableArray arrayWithCapacity:1];

    enumerator = [binaryDataAttributes objectEnumerator];
    while ((attribute = [enumerator nextObject])) {
	attrName = [attribute name];
	value = [binaryDataRow objectForKey:attrName];
	[oneBinaryDataRow setObject:value forKey:attrName];
	[oneBinaryAttribute addObject:attribute];

	if (![self _updateBinaryDataRow:oneBinaryDataRow
		    binaryDataAttributes:oneBinaryAttribute
		    qualifier:qualifier])
	    return NO;

	[oneBinaryDataRow removeAllObjects];
	[oneBinaryAttribute removeAllObjects];
    }

    return YES;
}

- (void)_advanceRow
{
    STATUS status;

    if (fetchState == SybaseReturnStatusFetch) {
	/* We processed the return value of a stored procedure. After
	   this we have to get the return values of parameters. So we set
	   the new value of rowType and fetchState. These values will be
	   used in a later call to -fetchAttributes: and/or
	   -describeResults. */
	fetchState = SybaseReturnParameterFetch;
	rowType = SybaseReturnParameterRow;
	return;
    }

    /* Set the status of the next row. This status will be used in the 
	next invocation of fetchAttributes:... method. */
    switch ((status = dbnextrow (dbProcess))) {
	case REG_ROW:
	    rowType = SybaseRegularRow;
	    fetchState = SybaseRowFetch;
	    break;
	case NO_MORE_ROWS:
	    fetchState = SybaseTerminatedFetch;
	    break;
	default:
	    rowType = SybaseComputeRow;
	    fetchState = SybaseRowFetch;
	    computeRowId = status;
	    break;
    }
}

- (NSArray*)describeResults
{
    int i, colsNumber = 0;
    id* attributes;

    if (![self isFetchInProgress])
	THROW ([AdaptorIsNotFetchingException new]);

    switch (rowType) {
	case SybaseRegularRow:
	    colsNumber = dbnumcols (dbProcess);
	    break;
	case SybaseComputeRow:
	    colsNumber = dbnumalts (dbProcess, computeRowId);
	    break;
	case SybaseReturnParameterRow:
	    colsNumber = dbnumrets (dbProcess);
	    break;
	case SybaseReturnStatusRow:
	    colsNumber = 1;
	    break;
	default:
	    NSAssert1 (0, @"rowType is not SybaseRowType enum: %d", rowType);
    }

    attributes = alloca (colsNumber * sizeof(id));

    for (i = 0; i < colsNumber; i++) {
	EOAttribute* attribute = [[EOAttribute new] autorelease];
	const char* externalName = NULL;
	DBINT sybaseType = 0;

	switch (rowType) {
	    case SybaseRegularRow:
		externalName = dbcolname (dbProcess, i + 1);
		sybaseType = dbcoltype (dbProcess, i + 1);
		break;
	    case SybaseComputeRow:
		externalName = "";
		sybaseType = dbalttype (dbProcess, computeRowId, i + 1);
		break;
	    case SybaseReturnParameterRow:
		externalName = dbretname (dbProcess, i + 1);
		sybaseType = dbrettype (dbProcess, i + 1);
		break;
	    case SybaseReturnStatusRow:
		externalName = "";
		sybaseType = SYBINT4;
		break;
	    default:
		NSAssert1 (0, @"invalid value of rowType: %d", rowType);
	}
	[attribute setName:[NSString stringWithFormat:@"attribute%d", i]];
	[attribute setColumnName:[NSString stringWithCString:externalName]];
	setAttributeValueClassAndType (attribute, sybaseType);
	attributes[i] = attribute;
    }

    return [[[NSArray alloc] initWithObjects:attributes count:colsNumber]
		autorelease];
}

/* The methods used to generate an model from the meta-information kept by
   the database. */

- (NSArray*)describeTableNames
{
    NSMutableArray* tableNames = nil;
    NSArray* results;
    NSString* attributeName;
    NSMutableDictionary* row;

    if (![self evaluateExpression:@"select name from sysobjects "
				  @"where type = 'U' or type = 'V'"])
	return nil;

    results = [self describeResults];
    attributeName = [(EOAttribute*)[results objectAtIndex:0] name];
    tableNames = [NSMutableArray array];

    while ((row = [self fetchAttributes:results withZone:NULL]))
	[tableNames addObject:[row objectForKey:attributeName]];

    return tableNames;
}

- (NSArray*)_attributesForTableName:(NSString*)tableName
{
    NSMutableArray* attributes = nil;
    EOAttribute* attribute;
    NSString* attributeName;
    NSArray* results;
    NSString* columnName;
    NSString* columnNameKey;
    NSString* externalType;
    NSString* externalTypeKey;
    NSMutableDictionary* row;
    NSString* expression = [NSString stringWithFormat:@"select c.name, t.name "
				@"from syscolumns c, systypes t "
				@"where c.id = object_id('%@') "
				@"  and c.usertype *= t.usertype", tableName];

    if (![self evaluateExpression:expression])
	return nil;

    results = [self describeResults];
    columnNameKey = [(EOAttribute*)[results objectAtIndex:0] name];
    externalTypeKey = [(EOAttribute*)[results objectAtIndex:1] name];
    attributes = [NSMutableArray array];

    while ((row = [self fetchAttributes:results withZone:NULL])) {
	attribute = [[EOAttribute new] autorelease];
	columnName = [row objectForKey:columnNameKey];
	externalType = [row objectForKey:externalTypeKey];
	attributeName = [columnName lowercaseString];
	[attribute setName:attributeName];
	[attribute setColumnName:columnName];
	[attribute setExternalType:externalType];
	attributeSetDefaultClassForExternalType (attribute, externalType);
	[attributes addObject:attribute];
    }

    return attributes;
}

- (EOModel*)describeModelWithTableNames:(NSArray*)tableNames
{
    int i, j, count2, count = [tableNames count];
    EOModel* model = [[EOModel new] autorelease];
    EOEntity* entity;
    NSArray* attributes;
    NSString* tableName;

    for (i = 0; i < count; i++) {
	tableName = [tableNames objectAtIndex:i];
	attributes = [self _attributesForTableName:tableName];
	entity = [[EOEntity new] autorelease];
	[model addEntity:entity];
	[entity setName:[tableName capitalizedString]];
	[entity setClassName:@"EOGenericRecord"];
	[entity setExternalName:tableName];
	for (j = 0, count2 = [attributes count]; j < count2; j++)
	    [entity addAttribute:[attributes objectAtIndex:j]];
    }

    [model setAdaptorName:@"Sybase"];
    [model setAdaptorClassName:@"SybaseAdaptor"];
    [model setConnectionDictionary:[adaptor connectionDictionary]];

    return model;
}

- (DBPROCESS*)dbProcess		{ return dbProcess; }

- (void)setDelegate:_delegate
{
    [super setDelegate:_delegate];
    sybaseDelegateRespondsTo.sybaseWillFetchAttributes = [delegate respondsToSelector:@selector(sybaseChannel:willFetchAttributes:forRowOfType:withComputeRowId:)];
    sybaseDelegateRespondsTo.sybaseWillReturnRow = [delegate respondsToSelector:@selector(sybaseChannel:willReturnRow:ofType:withComputeRowId:)];
}

@end /* SybaseChannel */
